This is FM.m in view mode; [Download] [Up]
/* ------------------------------------------------------------------------ *
* FM is a frequency modulation SynthPatch with arbitrary waveforms for *
* carrier and modulator and an interpolating oscillator for the carrier. *
* It supports a wide variety of parameters, including many MIDI parameters.*
* *
* This example is almost identical to the Fm1vi supplied with the 2.0 *
* Music Kit SynthPatch Library. The only difference is that Fm1vi supports *
* multiple "flavors" for optimization. For example, Fm1vi allows you to *
* specify a patch implementing only periodic or only random vibrato. *
* *
* See the FM literature for details of FM synthesis. *
* (Note that the implementation here is "frequency modulation" rather than *
* "phase modulation" and that the deviation scaling does not follow the *
* frequency envelope -- it is exactly as defined in the literature only *
* when the frequency envelope is at 1 and the vibrato is neither above nor *
* below the pitch.) *
* ------------------------------------------------------------------------ */
/* Written by Mike McNabb and David Jaffe. */
#import <midi/midi_types.h>
#import <musickit/unitgenerators/unitgenerators.h>
#import "FM.h"
@implementation FM:SynthPatch
/* The patch is described below. The UnitGenerators are shown in parenthesis:
*
* The vibrato is created by adding (Add2) a periodic component with a random
* component. The periodic component is created by an oscillator (Oscg).
* The random component is created by white noise (Snoise) that is
* low-pass filtered (Onepole).
*
* The vibrato signal is multiplied (Mul1add2) by the output of the frequency
* envelope generator (Asymp) and the frequency envelope generator is
* additionally added. Thus, the vibrato depth is perceptually the same,
* even as the frequency is continuously changing. Let us call the resulting
* signal the "frequency signal".
*
* The modulator (Oscgaf) takes its frequency from the frequency signal and
* its FM index from the index envelope generator (Asymp). The output of
* the modulator is added (Scl1add2) to the frequency signal to produce the
* frequency input to the carrier.
*
* The carrier (Oscgafi) takes its amplitude envelope from the amplitude
* envelope generator (Asymp) and sends its output signal to the stereo
* output sample stream (Out2sum).
*
* The communication between UnitGenerators is accomplished by patchpoints.
* Since the ordering of the UnitGenerators is constrained to be as specified
* below, two patchpoints suffice for all UnitGenerator communication.
* That is, the two patchpoints are reused as temporary storage for each stage
* in the sample computation.
*/
/* The following integers are used to hold offsets into the List of
* UnitGenerator and patchpoint instances. (The List is stored in the FM
* instance variable 'synthElements', inherited from SynthPatch.)
*/
static int ampEnvUG, freqEnvUG, indEnvUG, modulatorUG, carrierUG, outputUG,
svibUG, nvibUG, nvibFilterUG, vibAdderUG, fmAdderUG, freqMulUG, xPP, yPP;
+patchTemplateFor:currentNote
/* Returns and (if necessary) creates the PatchTemplate that specifies the
* UnitGenerators and patchpoints to be used. Note that this method
* does not actually allocate the UnitGenerators and patchpoints; it only
* returns the specification. */
{
/* This SynthPatch has only one template, but could have variations,
* returned according to the note parameter values.
*/
static PatchTemplate *template = nil;
if (template)
return template;
template = [[PatchTemplate alloc] init];
/* These UnitGenerators will be instantiated in the order specified. */
svibUG = [template addUnitGenerator:[OscgUGyy class]];
nvibUG = [template addUnitGenerator:[SnoiseUGx class]];
nvibFilterUG = [template addUnitGenerator:[OnepoleUGxx class]];
vibAdderUG = [template addUnitGenerator:[Add2UGyxy class]];
freqEnvUG = [template addUnitGenerator:[AsympUGx class]];
freqMulUG = [template addUnitGenerator:[Mul1add2UGyxyx class]];
indEnvUG = [template addUnitGenerator:[AsympUGx class]];
modulatorUG = [template addUnitGenerator:[OscgafUGxxyy class]];
fmAdderUG = [template addUnitGenerator:[Scl1add2UGyxy class]];
ampEnvUG = [template addUnitGenerator:[AsympUGx class]];
carrierUG = [template addUnitGenerator:[OscgafiUGxxyy class]];
outputUG = [template addUnitGenerator:[Out2sumUGx class]];
/* Patchpoint specifications */
xPP = [template addPatchpoint:MK_xPatch];
yPP = [template addPatchpoint:MK_yPatch];
return template;
}
#define UGS NX_ADDRESS(synthElements) // Quick access to UG instances
/* Definitions for the UnitGenerator and patchpoint instances: */
#define SVIB_UG UGS[svibUG]
#define NVIB_UG UGS[nvibUG]
#define NVIB_FILTER_UG UGS[nvibFilterUG]
#define VIB_ADDER_UG UGS[vibAdderUG]
#define FREQ_ENV_UG UGS[freqEnvUG]
#define FREQ_MUL_UG UGS[freqMulUG]
#define IND_ENV_UG UGS[indEnvUG]
#define MODULATOR_UG UGS[modulatorUG]
#define FM_ADDER_UG UGS[fmAdderUG]
#define AMP_ENV_UG UGS[ampEnvUG]
#define CARRIER_UG UGS[carrierUG]
#define OUTPUT_UG UGS[outputUG]
#define X_PP UGS[xPP]
#define Y_PP UGS[yPP]
#define OUTPUT_PP X_PP // We use the x patchpoint for the output signal
-init
/* Sent by this class on object creation and reset. */
{
/* This could, alternatively, be specified in the patchTemplateFor:
* method, as described in the NeXT Technical Documentation. We include
* it here for clarity.
*/
/* Connect UnitGenerators here. */
[SVIB_UG setOutput:Y_PP];
[NVIB_UG setOutput:X_PP];
[NVIB_FILTER_UG setInput:X_PP];
[NVIB_FILTER_UG setOutput:X_PP];
[VIB_ADDER_UG setInput1:X_PP];
[VIB_ADDER_UG setInput2:Y_PP];
[VIB_ADDER_UG setOutput:Y_PP];
[FREQ_ENV_UG setOutput:X_PP];
[FREQ_MUL_UG setInput1:X_PP];
[FREQ_MUL_UG setInput2:Y_PP];
[FREQ_MUL_UG setInput3:X_PP];
[FREQ_MUL_UG setOutput:Y_PP];
[IND_ENV_UG setOutput:X_PP];
[MODULATOR_UG setAmpInput:X_PP];
[MODULATOR_UG setIncInput:Y_PP];
[MODULATOR_UG setOutput:X_PP];
[FM_ADDER_UG setInput1:X_PP];
[FM_ADDER_UG setInput2:Y_PP];
[FM_ADDER_UG setOutput:Y_PP];
[AMP_ENV_UG setOutput:X_PP];
[CARRIER_UG setAmpInput:X_PP];
[CARRIER_UG setIncInput:Y_PP];
[CARRIER_UG setOutput:X_PP];
[self _setDefaults];
return self;
}
-noteOnSelf:aNote
/* Sent whenever a noteOn is received. First updates the parameters,
* then connects the carrier to the output.
*/
{
[self _updateParameters:aNote]; // Interpret parameters
[OUTPUT_UG setInput:OUTPUT_PP]; // Connect the ouput last.
[synthElements makeObjectsPerform:@selector(run)]; // Make them all run
return self;
}
-noteUpdateSelf:aNote
/* Sent whenever a noteUpdate is received by the SynthInstrument. */
{
[self _updateParameters:aNote];
}
-(double)noteOffSelf:aNote
/* Sent whenever a noteOff is received by the SynthInstrument. Returns
* the amplitude envelope completion time, needed to schedule the noteEnd. */
{
[self _updateParameters:aNote];
[FREQ_ENV_UG finish];
[IND_ENV_UG finish];
/* The value returned by noteOffSelf: is the time for the release portion
* to complete. We return the value returned by AMP_ENV_UG's finish method,
* i.e. the time in seconds it will take the envelope to
* complete. We only really care about the amplitude envelope's finishing
* (because once it is finished, there is no more sound) so we use its
* time. This assumes that the amplitude envelope ends at 0.0. */
return [AMP_ENV_UG finish];
}
-noteEndSelf
/* Sent when patch goes from finalDecay to idle. */
{
[OUTPUT_UG idle];
/* Since we only used the AMP_ENV_UG's finish time above, the other
* envelopes may or may not be finished, so we explicitly abort them. */
[FREQ_ENV_UG abortEnvelope];
[IND_ENV_UG abortEnvelope];
[self _setDefaults];
return self;
}
-preemptFor:aNote
/* Sent whenever a running note is being preempted by a new note. */
{
/* Cause envelope to go quickly to last value. This is to prevent a
* click between notes when preempting. (This assumes the amplitude
* envelope ends at 0). */
[AMP_ENV_UG preemptEnvelope];
[self _setDefaults]; /* Reset parameters to defaults. */
return self;
}
#import <objc/HashTable.h>
-controllerValues:controllers
/* Sent when a new phrase starts. controllers is a HashTable containing
* key/value pairs as controller-number/controller-value. Our implementation
* here ignores all but MIDI_MAINVOLUME and MIDI_MODWHEEL. See
* <objc/HashTable.h>, <midi/midi_types.h>, and <musickit/SynthPatch.h>. */
{
# define CONTROLPRESENT(_key) [controllers isKey:(const void *)_key]
# define GETVALUE(_key) (int)[controllers valueForKey:(const void *)_key]
if (CONTROLPRESENT(MIDI_MAINVOLUME))
volume = GETVALUE(MIDI_MAINVOLUME);
if (CONTROLPRESENT(MIDI_MODWHEEL))
modWheel = GETVALUE(MIDI_MODWHEEL);
return self;
}
/* ------------------------------------------------------------------ *
* The following two methods are private and are used internally only. *
* They are not intended to be invoked from outside the SynthPatch. *
* ------------------------------------------------------------------- */
-_setDefaults
/* Set the instance variables to reasonable default values. We do this
* after each phrase and upon initialization. This insures that a freshly
* allocated SynthPatch will be in a known state. See <musickit/params.h>
*/
{
waveform = m1Waveform = nil; // WaveTables
wavelen = 0; // Wave table length
phase = m1Phase = 0.0; // Waveform initial phases
ampEnv = freqEnv = m1IndEnv = nil;// Envelopes
freq0 = 0.0; // Frequency values
freq1 = MK_DEFAULTFREQ; // 440 Hz.
amp0 = 0.0; // Amplitude values
amp1 = MK_DEFAULTAMP; // 0.1
m1Ind0 = 0.0; // FM index values
m1Ind1 = MK_DEFAULTINDEX; // 1.0
ampAtt = freqAtt = m1IndAtt = MK_NODVAL; // Attack times (not set)
ampRel = freqRel = m1IndRel = MK_NODVAL; // Release times (not set)
bright = 1.0; // A multiplier on index
bearing = MK_DEFAULTBEARING; // 0.0 degrees
cRatio = MK_DEFAULTCRATIO; // Carrier frequency scaler.
m1Ratio = MK_DEFAULTMRATIO; // Modulator frequency scaler.
portamento = MK_NODVAL; // Rearticulation skew duration (not set)
svibAmp0 = svibAmp1 = 0.0; // Periodic vibrato amp
svibFreq0 = svibFreq1 = 0.0; // Periodic vibrato freq
rvibAmp = 0.0; // Random vibrato amplitude
/* MIDI parameters */
velocity = MK_DEFAULTVELOCITY;
volume = modWheel = afterTouch = MIDI_MAXDATA;
afterTouchSensitivity = velocitySensitivity = 0.5;
pitchbend = MIDI_ZEROBEND;
pitchbendSensitivity = 3.0;
}
static double midiVal(int midiControllerValue)
/* Convert from int between 0 and 127 to double between 0 and 1 */
{
return ((double)midiControllerValue)/((double)MIDI_MAXDATA);
}
-_updateParameters:aNote
/* Updates the SynthPatch according to the information in the note and
* the note's relationship to a possible ongoing phrase.
*/
{
BOOL newPhrase, setWaveform, setM1Waveform, setM1Ratio, setOutput,
setRandomVib, setCRatio, setAfterTouch, setVibWaveform, setVibFreq,
setVibAmp, setPhase, setAmpEnv, setFreqEnv, setM1IndEnv;
void *state; // For parameter iteration below
int par;
MKPhraseStatus phraseStatus = [self phraseStatus];
/* Initialize booleans based on phrase status -------------------------- */
switch (phraseStatus) {
case MK_phraseOn: /* New phrase. */
case MK_phraseOnPreempt: /* New phrase but using preempted patch. */
newPhrase = setWaveform = setM1Waveform = setM1Ratio =
setOutput = setRandomVib = setCRatio = setAfterTouch =
setVibWaveform = setVibFreq = setVibAmp = setPhase =
setAmpEnv = setFreqEnv = setM1IndEnv = YES;
// Set everything for new phrase
break;
case MK_phraseRearticulate: /* NoteOn rearticulation within phrase. */
newPhrase = setWaveform = setM1Waveform = setM1Ratio =
setOutput = setRandomVib = setCRatio = setAfterTouch =
setVibWaveform = setVibFreq = setVibAmp = setPhase = NO;
setAmpEnv = setFreqEnv = setM1IndEnv = YES; // Just restart envelopes
break;
case MK_phraseUpdate: /* NoteUpdate to running phrase. */
case MK_phraseOff: /* NoteOff to running phrase. */
case MK_phraseOffUpdate: /* NoteUpdate to finishing phrase. */
default:
newPhrase = setWaveform = setM1Waveform = setM1Ratio =
setOutput = setRandomVib = setCRatio = setAfterTouch =
setVibWaveform = setVibFreq = setVibAmp = setPhase =
setAmpEnv = setFreqEnv = setM1IndEnv = NO;
// Only set what's in Note
break;
}
/* Since this SynthPatch supports so many parameters, it would be
* inefficient to check each one with Note's isParPresent: method, as
* we did in Simplicity and Envy. Instead, we iterate over the parameters
* in aNote. */
state = MKInitParameterIteration(aNote);
while (par = MKNextParameter(aNote, state))
switch (par) { /* Parameters in (roughly) alphabetical order. */
case MK_afterTouch:
afterTouch = MKGetNoteParAsInt(aNote,MK_afterTouch);
setAfterTouch = YES;
break;
case MK_afterTouchSensitivity:
afterTouchSensitivity =
MKGetNoteParAsDouble(aNote,MK_afterTouchSensitivity);
setAfterTouch = YES;
break;
case MK_ampEnv:
ampEnv = MKGetNoteParAsEnvelope(aNote,MK_ampEnv);
setAmpEnv = YES;
break;
case MK_ampAtt:
ampAtt = MKGetNoteParAsDouble(aNote,MK_ampAtt);
setAmpEnv = YES;
break;
case MK_ampRel:
ampRel = MKGetNoteParAsDouble(aNote,MK_ampRel);
setAmpEnv = YES;
break;
case MK_amp0:
amp0 = MKGetNoteParAsDouble(aNote,MK_amp0);
setAmpEnv = YES;
break;
case MK_amp1: // MK_amp is synonym
amp1 = MKGetNoteParAsDouble(aNote,MK_amp1);
setAmpEnv = YES;
break;
case MK_bearing:
bearing = MKGetNoteParAsDouble(aNote,MK_bearing);
setOutput = YES;
break;
case MK_bright:
bright = MKGetNoteParAsDouble(aNote,MK_bright);
setM1IndEnv = YES;
break;
case MK_controlChange: {
int controller = MKGetNoteParAsInt(aNote,MK_controlChange);
if (controller == MIDI_MAINVOLUME) {
volume = MKGetNoteParAsInt(aNote,MK_controlVal);
setOutput = YES;
}
else if (controller == MIDI_MODWHEEL) {
modWheel = MKGetNoteParAsInt(aNote,MK_controlVal);
setVibFreq = setVibAmp = YES;
}
break;
}
case MK_cRatio:
cRatio = MKGetNoteParAsDouble(aNote,MK_cRatio);
setCRatio = YES;
break;
case MK_freqEnv:
freqEnv = MKGetNoteParAsEnvelope(aNote,MK_freqEnv);
setFreqEnv = YES;
break;
case MK_freqAtt:
freqAtt = MKGetNoteParAsDouble(aNote,MK_freqAtt);
setFreqEnv = YES;
break;
case MK_freqRel:
freqRel = MKGetNoteParAsDouble(aNote,MK_freqRel);
setFreqEnv = YES;
break;
case MK_freq:
case MK_keyNum:
freq1 = [aNote freq]; // A special method (see <musickit/Note.h>)
setFreqEnv = YES;
break;
case MK_freq0:
freq0 = MKGetNoteParAsDouble(aNote,MK_freq0);
setFreqEnv = YES;
break;
case MK_m1IndEnv:
m1IndEnv = MKGetNoteParAsEnvelope(aNote,MK_m1IndEnv);
setM1IndEnv = YES;
break;
case MK_m1IndAtt:
m1IndAtt = MKGetNoteParAsDouble(aNote,MK_m1IndAtt);
setM1IndEnv = YES;
break;
case MK_m1IndRel:
m1IndRel = MKGetNoteParAsDouble(aNote,MK_m1IndRel);
setM1IndEnv = YES;
break;
case MK_m1Ind0:
m1Ind0 = MKGetNoteParAsDouble(aNote,MK_m1Ind0);
setM1IndEnv = YES;
break;
case MK_m1Ind1:
m1Ind1 = MKGetNoteParAsDouble(aNote,MK_m1Ind1);
setM1IndEnv = YES;
break;
case MK_m1Phase:
m1Phase = MKGetNoteParAsDouble(aNote,MK_m1Phase);
/* To avoid clicks, we don't allow phase to be set except at the
start of a phrase. Therefore, we don't set setPhase. */
break;
case MK_m1Ratio:
m1Ratio = MKGetNoteParAsDouble(aNote,MK_m1Ratio);
setM1Ratio = YES;
break;
case MK_m1Waveform:
m1Waveform = MKGetNoteParAsWaveTable(aNote,MK_m1Waveform);
setM1Waveform = YES;
break;
case MK_phase:
phase = MKGetNoteParAsDouble(aNote,MK_phase);
/* To avoid clicks, we don't allow phase to be set except at the
start of a phrase. Therefore, we don't set setPhase. */
break;
case MK_pitchBend:
pitchbend = MKGetNoteParAsInt(aNote,MK_pitchBend);
setFreqEnv = YES;
break;
case MK_pitchBendSensitivity:
pitchbendSensitivity =
MKGetNoteParAsDouble(aNote,MK_pitchBendSensitivity);
setFreqEnv = YES;
break;
case MK_portamento:
portamento = MKGetNoteParAsDouble(aNote,MK_portamento);
setM1IndEnv = setAmpEnv = YES;
break;
case MK_rvibAmp:
rvibAmp = MKGetNoteParAsDouble(aNote,MK_rvibAmp);
setRandomVib = YES;
break;
case MK_svibFreq0:
svibFreq0 = MKGetNoteParAsDouble(aNote,MK_svibFreq0);
setVibFreq = YES;
break;
case MK_svibFreq1:
svibFreq1 = MKGetNoteParAsDouble(aNote,MK_svibFreq1);
setVibFreq = YES;
break;
case MK_svibAmp0:
svibAmp0 = MKGetNoteParAsDouble(aNote,MK_svibAmp0);
setVibAmp = YES;
break;
case MK_svibAmp1:
svibAmp1 = MKGetNoteParAsDouble(aNote,MK_svibAmp1);
setVibAmp = YES;
break;
case MK_vibWaveform:
vibWaveform = MKGetNoteParAsWaveTable(aNote,MK_vibWaveform);
setVibWaveform = YES;
break;
case MK_velocity:
velocity = MKGetNoteParAsDouble(aNote,MK_velocity);
setAmpEnv = YES;
break;
case MK_velocitySensitivity:
velocitySensitivity =
MKGetNoteParAsDouble(aNote,MK_velocitySensitivity);
setAmpEnv = YES;
break;
case MK_waveform:
waveform = MKGetNoteParAsWaveTable(aNote,MK_waveform);
setWaveform = YES;
break;
case MK_waveLen:
wavelen = MKGetNoteParAsInt(aNote,MK_waveLen);
setWaveform = setM1Waveform = setM1Ratio = YES;
break;
default: /* Skip unrecognized parameters */
break;
} /* End of parameter loop. */
/* -------------------------------- Waveforms --------------------- */
if (setWaveform)
[CARRIER_UG setTable:waveform length:wavelen defaultToSineROM:newPhrase];
if (setM1Waveform)
[MODULATOR_UG setTable:m1Waveform length:wavelen
defaultToSineROM:newPhrase];
/* ------------------------------- Frequency scaling --------------- */
if (setCRatio)
[CARRIER_UG setIncRatio:cRatio];
if (setM1Ratio)
/* Since table lengths may be set automatically (if wavelen is 0),
we must account here for possible difference in table lengths
between carrier and modulator. */
[MODULATOR_UG setIncRatio:m1Ratio * [MODULATOR_UG tableLength] /
[CARRIER_UG tableLength]];
/* ------------------------------- Phases -------------------------- */
if (setPhase) {
[CARRIER_UG setPhase:phase];
[MODULATOR_UG setPhase:m1Phase];
}
/* ------------------------------ Envelopes ------------------------ */
if (setAmpEnv)
MKUpdateAsymp(AMP_ENV_UG,ampEnv,amp0,
amp1 *
MKMidiToAmpWithSensitivity(velocity,velocitySensitivity),
ampAtt,ampRel,portamento,phraseStatus);
if (setFreqEnv) {
double fr0, fr1;
fr0 = MKAdjustFreqWithPitchBend(freq0,pitchbend,pitchbendSensitivity);
fr1 = MKAdjustFreqWithPitchBend(freq1,pitchbend,pitchbendSensitivity);
MKUpdateAsymp(FREQ_ENV_UG,freqEnv,
[CARRIER_UG incAtFreq:fr0], // Convert to osc increment
[CARRIER_UG incAtFreq:fr1],
freqAtt,freqRel,portamento,phraseStatus);
}
if (setM1IndEnv) {
double FMDeviation = [CARRIER_UG incAtFreq:(m1Ratio * freq1)] * bright;
/* See literature on FM synthesis for details about the scaling by
FMDeviation */
MKUpdateAsymp(IND_ENV_UG, m1IndEnv,
m1Ind0 * FMDeviation, m1Ind1 * FMDeviation,
m1IndAtt,m1IndRel,portamento,phraseStatus);
}
/* ----------------------------- Vibrato ---------------------------- */
if (setVibWaveform)
[SVIB_UG setTable:vibWaveform length:128 defaultToSineROM:YES];
if (setVibFreq)
[SVIB_UG setFreq:svibFreq0 + (svibFreq1-svibFreq0) *
midiVal(modWheel)];
if (setVibAmp)
[SVIB_UG setAmp:svibAmp0 + (svibAmp1-svibAmp0) * midiVal(modWheel)];
if (setRandomVib) {
[NVIB_FILTER_UG setB0:0.004 * rvibAmp]; /* Filter gain (rvibAmp) */
[NVIB_FILTER_UG clear]; /* Clear filter state */
if (newPhrase) {
[NVIB_FILTER_UG setA1:-0.9999]; /* Filter feedback coefficient */
[NVIB_UG anySeed]; /* Make each instance have different vib */
}
}
/* ------------------- Bearing, volume and after touch -------------- */
if (setOutput)
[OUTPUT_UG setBearing:bearing scale:MKMidiToAmpAttenuation(volume)];
if (setAfterTouch)
[FM_ADDER_UG setScale:(1-afterTouchSensitivity) +
afterTouchSensitivity * midiVal(afterTouch)];
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.