ModalSpec
Maps note indices onto scale degrees, with chromatic awareness. Note indices are assumed to be integers. Chromatics are encoded using the fractional part of the note index.
If there is a half step (only one note index difference) between adjacent scale degrees, the fractional part of the scale degree is meaningless.
If a whole step (a two note index difference), the chromatic step between them is encoded as a 0.5 offset, e.g., if c is scale degree 0 and d is scale degree 1, c# is scale degree 0.5.
If the distance is a minor third (as in harmonic minor), chromatic steps are encoded as 1/3 intervals, e.g., if Ab is scale degree 5 and B is scale degree 6, A is 5.333333 and Bb is 5.6666667.
If your music has a diatonic reference in any way, the ability to convert between diatonic and chromatic representations is essential. The default event framework handles this using the degree parameter, in conjunction with scale and steps per octave. I find it helpful, however, when processing note data to be able to refer to a modal definition as an object, and do the conversion in both directions (not just scale degree to MIDI note as in the default event). For instance, a mode may be stored along with MIDI data in the properties dictionary of a MIDIRecBuf so that the notes are inherently associated with the mode.
*new(scale, stepsPerOctave, root, tuning, cpsFunc)
scale: The note indices, starting with 0, for each scale degree. These should all be integers.
stepsPerOctave: The number of note indices included in one octave, e.g., 12 for any 12-note temperament.
root: The note index corresponding to degree 0; "tonic" in tonal scales. For D major, you would give a root of 2.
tuning: A tuning adjustment that will be added to note indices when mapping modal values onto note numbers. If you need to match pitch of a sample that is slightly sharp, you can specify a small positive value. 1.0 will transpose up by a note index.
cpsFunc: Attach a custom note index --> Hz function. Can be a function or a tuning object (see [EqualTemperament] and its subclasses). The default is 12ET using midicps.
The defaults produce a C major scale: ModalSpec(#[0, 2, 4, 5, 7, 9, 11], 12, 0, 0)
map(key)
Maps a note index value into the mode specified by this object. The "key" may be a number, array, SequenceNote, or MIDIRecBuf object.
This method works by delegating to the mapMode method for the key argument; therefore, you can save a dispatch by calling mapMode directly.
// use default C major modal spec
m = ModalSpec.new;
m.map(60);
60.mapMode(m);
[60, 64, 67].mapMode(m);
// [ 35, 37, 39 ] -- 37 - 35 = 2, indicating a third - this is a triad
unmap(degree)
Reverses the mapping: takes a modal degree as input and outputs the corresponding note index (ready to be converted to Hz for play). The same object types are allowed as the argument.
[35, 37, 39].unmapMode(m); // --> [ 60, 647, 67 ]
cps(degree)
Converts a modally mapped value to Hz, using the cpsFunc belonging to this ModalSpec.
m.cps(35);
m.cps(35) == 60.midicps; // true
includes(key)
Responds with true if the specified note index is a member of the scale -- that is, if it is a diatonic note rather than a chromatic note.
// use default C major modal spec
m = ModalSpec.new;
m.includes(60); // includes middle C?
true
m.includes(61); // includes C#?
false
Example:
Note -- this example is not terribly impressive, and in fact can already be accomplished using ~degree. ModalSpec supports many more uses, though.
// planing over a major scale
// this is a very hacky way to do it
~mode = ModalSpec.new; // default = C major
x = 0; ~chord = { x <! (x = x + rrand(1, 3)) } ! 4;
SynthDescLib.global.read;
p = Pbind(
\mode, ~mode,
\bottom, Pvbrown(30, 39, Pwrand(#[1, 2, 3], #[0.4, 0.35, 0.25], inf)
* Prand(#[-1, 1], inf), inf),
\notes, Pkey(\bottom) + ~chord,
\freq, Pfunc({ |ev| ev[\mode].cps(ev[\notes]) }),
\dur, Pwhite(1, 4, inf) * 0.5,
\instrument, \default
).play;
p.stop;
// better way if you do this kind of thing often:
// create a new protoevent, replacing the ~freq function with one referencing cps
(
e = Event.default.copy;
e.parent.superFreq = e[\freq];
e.parent.freq = {
~mode.notNil.if({
~mode.cps(~midinote.value + ~ctranspose) * ~harmonic
}, {
~superFreq.value
});
};
)
p = Pbind(
\mode, ~mode,
\bottom, Pvbrown(30, 39, Pwrand(#[1, 2, 3], #[0.4, 0.35, 0.25], inf)
* Prand(#[-1, 1], inf), inf),
\midinote, Pkey(\bottom) + ~chord,
\dur, Pwhite(1, 4, inf) * 0.5,
\instrument, \default
).play;
p.stop;