Envelopes & dynamics
Some Jamdac parameters are static parameters, which means they do not change after the instrument is loaded. For example, FILTER_RESONANCE and DIGITAR_MODE can only be changed by reloading the instrument. Other Jamdac parameters are dynamic parameters, which means they can be changed interactively, giving interesting character or "expression" to a sound. In our DSP diagrams, dynamic parameters are shown with an asterisk by the name, for example: REVERB_LEVEL*
A dynamic parameter is configured using an IO_DYNAMIC object, which we'll call a dynamic. Here are some example usages:
- Amplitude shaping: To make a snare drum sound, we might start with white noise, and then configure its
OUTPUT_LEVELdynamic to quickly decrease to zero using anIO_ENVELOPE(explained below). This causes the noise to fade out quickly. - Frequency sweeps: To make a falling sound effect, the
OSCILLATOR_HZdynamic could decrease over time using an envelope. - Pitched notes: To make music by playing notes at different pitches, we can use
PITCH_FACTORto adjust theOSCILLATOR_HZdynamic as each note is triggered. We could also add "vibrato" by making the pitch rise and fall slightly, by applying a looping envelope toOSCILLATOR_HZas well. - Pulse modulation: A boring pulse oscillator becomes a fun retro synth if you use an envelope to repeatedly expand and contract its
PULSE_WIDTHparameter. - Timbre variation: An instrument might use
MOD_FACTORto make small changes to many different dynamics at once. In this way, even if the same note is played repeatedly, the sound will be a bit different each time. - Stereo effects: We can cause a sound to "pan" from left to right by applying an envelope to both
LEFT_LEVELandRIGHT_LEVELparameters. By reversing theLOWandHIGHrange forRIGHT_LEVEL, it will fade out asLEFT_LEVELfades in.
Defining a dynamic
# A SOUND PARAMETER THAT CAN BE CONTROLLED BY AN IO_ENVELOPE
CLASS IO_DYNAMIC # 5 BYTES
# 0..2 = INSTRUMENT ENVELOPES
# 3..4 = GLOBAL ENVELOPES
#
# +32 = ENABLE ENVELOPE, IF DISABLED THEN "HIGH" IS USED
# +64 = ENABLE PITCH_FACTOR CONTROL
# +128 = ENABLE MOD_FACTOR CONTROL
VAR CONTROL: BYTE
# THE VALUE WHEN L=0 IN THE ENVELOPE GRAPH
VAR LOW: PAIR
# THE VALUE WHEN L=255 IN THE ENVELOPE GRAPH
VAR HIGH: PAIR
END CLASS
The IO_DYNAMIC class packs all the important information into just 5 bytes:
- The dynamic has a
LOWandHIGHvalue that specify a range for the parameter. For most parameters, thePAIRvalue will be an S6.10 fixed point number. - The dynamic can be controlled by an envelope, in which case the range determines the envelope's low point (
L=0) and high point (L=255). Because the same envelope might control many different parameters at once, each dynamic needs to provide its own range that is appropriate for the parameter. - If the dynamic is not controlled by an envelope, then the
HIGHvalue is used. TheLOWvalue is ignored. - Whenever a note is played, the "trigger instrument" command includes a
PITCH_FACTORandMOD_FACTOR. For a piano,PITCH_FACTORmight specify which piano key was pressed, whereasMOD_FACTORspecifies how loud it should be (how hard the piano key was pressed). In general,PITCH_FACTORandMOD_FACTORcan be applied to any dynamic parameter, so it's up to the instrument to decide what they mean. - The parameter's value gets multiplied by
PITCH_FACTORand/orMOD_FACTOR. Therefore, the final parameter value can exceed theLOW…HIGHrange.
Defining an envelope
CLASS IO_ENVELOPE # 20 BYTES
VAR SAMPLES_PER_T: PAIR
# 1 = SUSTAIN; WITHOUT THIS FLAG, SUSTAIN_INDEX IS IGNORED
# 2 = LOOPING
# 4 = STAIRSTEPS INSTEAD OF INTERPOLATION
VAR FLAGS: BYTE
# INDEX OF THE GRAPH POINT TO BE HELD UNTIL A "RELEASE INSTRUMENT" COMMAND
VAR SUSTAIN_INDEX: BYTE # [JAMDAC PLUS]
# EIGHT (L, T) PAIRS
# L: 0..255 --> LOW..HIGH OF IO_DYNAMIC LEVEL
# T: 0..255 --> 0..255*SAMPLES_PER_T TIME STEP SIZE
# IF A POINT HAS T=0, IT CREATES AN OPEN INTERVAL WITH A DISCONTINUITY.
# IF TWO CONSECUTIVE POINTS HAVE T=0, THEN THE SECOND POINT AND REMAINDER
# ARE DISCARDED. IF THE EIGHTH POINT HAS T>0, IT INTERPOLATES TO L=GRAPH[0].
INSET GRAPH: BYTE[SIZE 16]
END CLASS
Jamdac's envelope component causes a value to change over time according to a specified graph made from line segments. The graph's value at a given time is called the envelope level. The time is measured by counting individual signal samples. Jamac's envelopes are specified using up to eight data points in IO_ENVELOPE::GRAPH. Each point determines a level L and a time interval T since the previous point, written as (L, T). The IO_ENVELOPE::GRAPH elements are bytes, so L ranges from 0…255 (LOW…HIGH). T is also a byte, however 255 samples would be an extremely short time interval. Instead, T is multiplied by SAMPLES_PER_T to determine the actual number of samples for the interval. SAMPLES_PER_T can be any number from 0 to 32767. Representing T as multiples of SAMPLES_PER_T enables the IO_ENVELOPE object to fit in just 20 bytes, considerably reducing the MMIO transfer cost.
The envelope duration is the total amount of time for the graph. For a repeating envelope (2 in IO_ENVELOPE::FLAGS), the envelope duration is the length of one loop. For a given envelope E, its duration can be calculated by adding up the individual T values:
T_SUM <-- E.GRAPH[1] + E.GRAPH[3] + E.GRAPH[5] + E.GRAPH[7]
| + E.GRAPH[9] + E.GRAPH[11] + E.GRAPH[13] + E.GRAPH[15]
DURATION_IN_SAMPLES <-- T_SUM * E.SAMPLES_PER_T
Thought question: What should we choose for
SAMPLES_PER_T? If it is too small, thenTmight exceed its limit of 255. IfSAMPLES_PER_Tis too big, then our graph may be inaccurate because the granularity ofTis too big. The Hybrix Designer lets you specify any duration and points, then it calculatesSAMPLES_PER_Tsuch that:
SAMPLES_PER_Tcannot exceed 32767- the longest segment must be representable as
T∗SAMPLES_PER_TwhereTcannot exceed 255T_SUM∗SAMPLES_PER_Tshould be very close to the desired envelope duration- ensuring accuracy of
DURATION_IN_SAMPLESis more important than perfecting any individual segmentHow would you solve that problem?