-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSequence.sc
221 lines (219 loc) · 5.93 KB
/
Sequence.sc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// IDEA: maybe use EventListPlayer from the Mx quark to play the list of events?
// TODO: make shift method to shift notes left or right, wrapping them to current length
Sequence { // represents a sequence of notes.
var <>list, <protoEvent, >dur, <>yType=\midinote; // dur is the duration in beats
*new {
| ... notes |
^super.new.init(*notes);
}
init {
| ... notes |
this.protoEvent_(nil);
list = [];
notes.do({
| note |
this.add(note);
});
}
storeOn {
| stream |
stream << "Sequence(*" << this.rawList.cs << ")"
<< if(this.protoEvent.isNil, "", ".protoEvent_(" ++ this.protoEvent.cs ++ ")")
<< if(dur.isNil, "", ".dur_(" ++ this.dur.cs ++ ")")
<< if(this.yType == \midinote, "", ".yType_(" ++ this.yType.cs ++ ")");
}
actualDur {
var res = list.reject(_.isNil).collect({
| evt |
evt[\beat]+(evt[\sustain]?1)
});
^res.maxItem;
}
collect {
| function |
^this.list.collect(function);
}
dur {
^if(dur.isNil, { // if dur is nil, find the last playing note in the sequence and use that as the end point.
var len = this.actualDur.ceil;
if(len == 0, {
1;
}, {
len;
});
}, dur);
// FIX: might want to apply a quant to the dur, i.e. to round it to the nearest beat?
}
dur2 { // dur, counting only when notes begin.
var res = list.reject(_.isNil).collect({
| evt |
evt[\beat];
});
^if(res.size == 0, 4, { res.maxItem.ceil; })
}
add { // use the 'beat' key in an event to specify the beat that the note starts, and 'sustain' for how long it lasts.
| note |
if(note.isKindOf(Event), {
if(note[\beat].notNil, {
note[\beat] = note[\beat].max(0);
});
note.removeAt(\id);
note.removeAt(\server);
note.removeAt(\isPlaying);
note.removeAt(\msgFunc);
list = list.add(note);
^(list.size-1);
}, {
"notes added to Sequence must be Events!".error;
});
}
adds {
| ... notes |
notes.do {
| note |
this.add(note);
};
}
remove {
| note |
list = list.reject({
| item |
(item[\beat] == note[\beat]) and: { item[yType] == note[yType] };
});
}
removeAt {
| beat |
var events = this.eventsAt(beat);
events.do({
| event |
this.remove(event);
});
}
removeIn {
| start end |
var events = this.eventsIn(start, end);
events.do({
| event |
this.remove(event);
});
}
size {
^list.select(_.notNil).size;
}
eventsAt { // all events that start on a certain beat.
| beat |
^this.asList.select({
| event |
event.notNil && { event[\beat] == beat };
});
}
eventsIn { // all the events that begin within a range.
| start end |
^this.rawList.select({
| event |
event.notNil and: { (event[\beat] >= start) and: {event[\beat] < end} };
});
}
protoEvent_ { // FIX: a protoEvent should be provided to the stream, not the pattern itself?
| event |
protoEvent = event?(instrument:\default);
}
rawList {
^list.reject(_.isNil);
}
asList { // NEW version
var resAry = [];
var ary = this.rawList.sortBy(\beat).reject({|e|e[\beat].isNil});
ary.do({
| event index |
var res = event.deepCopy;
var last = resAry.last;
var next = ary[index+1];
var between = res[\beat] - if(last.isNil, 0, {(last[\dur]+last[\beat])});
res[\dur] = res[\sustain];
if(index == 0 and: {between > 0}, {
resAry = resAry ++ [(type:\rest, dur:between, beat:0)];
});
// FIX: don't use \delta
if(next.notNil, {
res[\delta] = (next[\beat] - event[\beat]);
});
resAry = resAry ++ [res];
});
if(resAry.size == 0, {
resAry = [(type:\rest, dur:1)];
}, {
if(dur.notNil, {
if(this.actualDur < dur, { // if the specified dur is longer than the actual dur, pad the list with a rest at the end to sync.
var last = resAry.last;
var d_or_s = last[\sustain];
resAry = resAry ++ [(type:\rest, dur:(dur-this.actualDur), beat:(d_or_s+last[\beat]))];
});
if(this.actualDur > dur, { // FIX
"dur smaller than actual dur is not yet supported.".error;
});
});
if(this.actualDur < this.actualDur.ceil, {
var last = resAry.last;
resAry = resAry ++ [(type:\rest, dur:(this.actualDur.ceil-this.actualDur), beat:(this.actualDur))];
});
});
^resAry;
}
asPattern {
var list = this.asList;
if(list.size == 0, {
list = [(type:\rest, dur:1)];
});
^Pchain(protoEvent, Pseq(list));
}
play {
| clock protoEvent quant |
this.asPattern.play(clock, protoEvent, quant);
}
fork {
| clock quant protoEvent |
^this.play(clock, protoEvent, quant);
}
startRecording { // just re-initializes the Sequence. calling this is obviously not required if you just created the Sequence.
this.init;
}
record { // record a note.
| event clock=(TempoClock.default) |
var cbeat, lat = Server.default.latency.timebeats(clock);
if(this.protoEvent[\tempoClockStartBeat].isNil, {
this.protoEvent_(this.protoEvent++(tempoClockStartBeat:clock.beats-lat));
});
cbeat = (clock.beats-this.protoEvent[\tempoClockStartBeat]-lat);
this.add(event++(beat:cbeat));
}
recordFree { // record a note being freed.
| event clock=(TempoClock.default) |
var cbeat = (clock.beats-this.protoEvent[\tempoClockStartBeat])-Server.default.latency.timebeats(clock);
var filteredlist = this.list.select({
| e |
e[\sustain].isNil and: { var cmp = e.deepCopy;cmp.removeAt(\beat);cmp.trueCompare(event); };
});
filteredlist[0][\sustain] = (cbeat-filteredlist[0][\beat]);
}
stopRecording { // end recording. basically just removes the 'tempoClockStartBeat' key from the protoEvent. not required, but keeps things cleaner.
this.protoEvent.removeAt(\tempoClockStartBeat);
}
recordGui {
^this.recorder.makeWindow;
}
recorder {
^UserView().background_(Color.red);
}
reverse { // reverse the Sequence, keeping the same duration.
}
shift { // shift note onsets left (negative) or right (positive), wrapping them to the length of the Sequence.
| beats |
}
}
+ Pattern {
asSequence {
| dur force=false | // 'force' is to force creation of a Sequence even if its estimateDur method returns inf.
// FIX
}
}