SeqChordNote : SequenceNote
Encapsulates all the notes of a chord into a single object. This way, manipulations on an array of note objects will not break a chord.
See SequenceNote for the base class, and SequenceItem for a grace note + chord object.
A chord includes several notes, most of which must be very short to preserve the illusion of a simultaneous attack. SeqChordNote and its subclass, SequenceItem, use a new variable, shortDur, for the duration of the main note before playing the first chord note. As before, dur represents the delta between the first note of this chord and the first note of the next sequence note object.
In the diagram below, N1 represents the primary note of the SeqChordNote object. That is, the freq, length and args instance variables of the SeqChordNote contain data for N1. Pitch and duration of information for N2 and N3 are contained in the chordNotes array.
/** N1-3: one chord **/ /** Next distinct event **/
*N1* N2 N3 N4
^ A ^
^ B ^
^ C ^
^ D ^
A: shortDur
B: dur of chordNotes[0]
C: dur of chordNotes[1]
D: dur of SeqChordNote (total)
*new(freq, dur, length, args, chordNotes)
chordNotes is an array of SequenceNote objects.
// middle C, 0.05 beats between notes
n = SeqChordNote(60, 0.05, 3, nil, [SequenceNote(64, 0.05, 2.95), SequenceNote(67, 2.9, 2.9)]);
When added to the SeqChordNote object, the freq values of the chord notes are converted to be relative to the main chord note. This simplifies transposition later. The correct values will be produced when streaming out the notes (below).
n.chordNotes.do(_.postln);
[ 4, 0.05, 2.95, nil ] // 64-60 = 4
[ 7, 2.9, 2.9, nil ] // 67-60 = 7
To play the chord back, use patterns and streams.
p = n.asStream;
while { p.next.postln.notNil };
[ 60, 0.05, 3, nil ]
[ 64, 0.05, 2.95, nil ]
[ 67, 2.9, 2.9, nil ]
nil
This works also if the chord is in the middle of an array of SequenceNotes and SeqChordNotes.
Transposition works also:
p = (n + 4).asStream;
while { p.next.postln.notNil };
[ 64, 0.05, 3, nil ]
[ 68, 0.05, 2.95, nil ]
[ 71, 2.9, 2.9, nil ]
nil
You may .add() a single note to a SeqChordNote or concatenate (++) an array of notes at any time. The original SeqChordNote is returned with new content.
dur_(newDur)
Modify the notes' timings to conform with the new value. The dur instance variable holds the sum of shortDur plus the durations of all the chord notes; to preserve the temporal relationships, shortDur and the chord note durations are scaled by the ratio between the new duration and the old.
p = n.asStream;
while { p.next.postln.notNil };
[ 60, 0.05, 3, nil ]
[ 64, 0.05, 2.95, nil ]
[ 67, 2.9, 2.9, nil ]
n.dur
3
n.dur = 6; // we would expect all durations to be * 2 (6/3 = 2)
p = n.asStream;
while { p.next.postln.notNil };
[ 60, 0.1, 3, nil ] // 0.05 * 2 = 0.1, etc.; 0.1 + 0.1 + 5.8 = 6
[ 64, 0.1, 2.95, nil ]
[ 67, 5.8, 2.9, nil ]
add(note)
Adds the note argument to the chord note list -- returns the same SeqChordNote object.
++ notes
Adds the note argument array to the same SeqChordNote object, into the chord note list.
addGraceNotes
Create a SequenceItem with the supplied grace notes (preserving the chord notes as well).
asPlayableNote
All chord notes will be compacted into one SequenceNote with arrays for the parameters, e.g.,
n = SeqChordNote(60, 0.05, 3, nil, [SequenceNote(64, 0.05, 2.95), SequenceNote(67, 2.9, 2.9)]);
n.asPlayableNote;
[ [ 60, 64, 67 ], 3, [ 3, 2.95, 2.9 ], [ nil, nil, nil ] ]
// which is the string representation of
SequenceNote([ 60, 64, 67 ], 3, [ 3, 2.95, 2.9 ], [ nil, nil, nil ])
asSequenceNote
The main note object will be returned, omitting the chord notes. Note that the total duration is used (0.05 + 0.05 + 2.9 = 3.0).
n.asSequenceNote;
[ 60, 3, 3, nil ] // or, SequenceNote(60, 3, 3, nil)
asNoteArray
A flattened representation, not useful for streaming.
n.asNoteArray;
[ a SeqChordNote, a SequenceNote, a SequenceNote ]
n.asNoteArray.do(_.postln);
[ 60, 3, 3, nil ]
[ 64, 0.05, 2.95, nil ]
[ 67, 2.9, 2.9, nil ]
asArray
^[freq, dur, length, args] -- ignores the chord notes.
n.asArray;
[ 60, 3, 3, nil ]
asPattern
asStream
For streaming the notes out in order, preserving the original time relationships.
MIDIRecBuf and parsing
MIDIRecBuf is an easy way to store a list of sequence notes. It has a method, parse, to identify groups of notes that should be chords or grace notes and it constructs the required SeqChordNotes and SequenceItems.
This MIDIRecBuf contains a simple I-IV-V-I in C major. After parsing, the buffer has only 4 objects in the array. But, when you stream out the notes in the buffer, everything comes back as it should.
See [MIDIRecBuf] for details on the parsing algorithm.
b = MIDIRecBuf(\cmaj, [
#[60, 64, 67, 60, 65, 69, 62, 67, 71, 64, 67, 72],
#[0.01, 0.01, 0.98, 0.01, 0.01, 0.98, 0.01, 0.01, 0.98, 0.01, 0.01, 0.98],
1
].asNotes);
b.dumpSeq;
MIDIRecBuf("cmaj")
0 : [ 60, 0.01, 1, nil ]
1 : [ 64, 0.01, 1, nil ]
2 : [ 67, 0.98, 1, nil ]
3 : [ 60, 0.01, 1, nil ]
4 : [ 65, 0.01, 1, nil ]
5 : [ 69, 0.98, 1, nil ]
6 : [ 62, 0.01, 1, nil ]
7 : [ 67, 0.01, 1, nil ]
8 : [ 71, 0.98, 1, nil ]
9 : [ 64, 0.01, 1, nil ]
10 : [ 67, 0.01, 1, nil ]
11 : [ 72, 0.98, 1, nil ]
c = b.parse;
c.dumpSeq;
MIDIRecBuf("cmajb")
0 : [ 60, 1, 1, nil ]
1 : [ 60, 1, 1, nil ]
2 : [ 62, 1, 1, nil ]
3 : [ 64, 1, 1, nil ]
p = Pseq(c.notes, 1).asStream;
while { p.next.postln.notNil };
[ 60, 0.01, 1, nil ]
[ 64, 0.01, 1, nil ]
[ 67, 0.98, 1, nil ]
[ 60, 0.01, 1, nil ]
[ 65, 0.01, 1, nil ]
[ 69, 0.98, 1, nil ]
[ 62, 0.01, 1, nil ]
[ 67, 0.01, 1, nil ]
[ 71, 0.98, 1, nil ]
[ 64, 0.01, 1, nil ]
[ 67, 0.01, 1, nil ]
[ 72, 0.98, 1, nil ]
nil