-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCllParameterHandlers.sc
More file actions
277 lines (256 loc) · 7.26 KB
/
CllParameterHandlers.sc
File metadata and controls
277 lines (256 loc) · 7.26 KB
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
Chucklib-livecode: A framework for live-coding improvisation of electronic music
Copyright (C) 2018 Henry James Harkins
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
// these assume to be run within a chucklib-livecode BP
// the BP supplies ~valueForParm
// parameter handlers
CllParmHandlerFactory {
// the logic to decode the sub-cases is still a bit ugly
// but at least 1/ it's now concentrated in this one location
// and 2/ refactored as nested ifs
*new { |parm, bpKey, map|
var bp;
if(BP.exists(bpKey)) {
bp = BP(bpKey);
} {
Error("Can't create parm handler because BP(%) doesn't exist"
.format(bpKey.asCompileString)).throw;
};
if(map.isNil) {
map = bp.parmMap[parm];
};
if(map.isNil and: { #[delta, dur].includes(parm).not }) {
Error("BP(%): Can't create handler for nonexistent parm '%'"
.format(bpKey.asCompileString, parm)).throw;
};
if(map.tryPerform(\at, \alias).isNil) {
// non-aliased cases (2)
if(bp.parmIsDefault(parm)) {
^CllDefaultNoAliasParm(parm, bpKey, map)
} {
^CllParm(parm, bpKey, map)
};
} {
// aliased cases
if(bp.parmIsDefault(parm)) {
// default parm with alias is always this type
^CllDefaultArrayAliasParm(parm, bpKey, map)
} {
if(map[\alias].size == 0) {
// non-default, alias is not an array
^CllSimpleAliasParm(parm, bpKey, map)
} {
// non-default, alias is an array
^CllNonDefaultArrayAliasParm(parm, bpKey, map)
}
};
}
}
}
CllParm {
var <parm, bpKey, map;
var <storeParm, <patternParm;
var isDefault;
var valueLookup, isRest;
var prevValueID;
// bpKey should have been validated in the factory
// I won't recheck it redundantly here
*new { |parm, bpKey, map|
^super.newCopyArgs(parm, bpKey, map).init
}
init {
isDefault = BP(bpKey).parmIsDefault(parm);
storeParm = map[\alias] ?? { parm };
if(isDefault) {
patternParm = [parm, \delta, \dur];
} {
patternParm = parm;
};
if(BP(bpKey).parmIsPitch(parm)) {
valueLookup = \pitchLookup;
isRest = \pitchIsRest;
prevValueID = map.tryPerform(\at, \default) ?? { SequenceNote(0, 1, 0.9) };
} {
valueLookup = \dictLookup;
isRest = \otherIsRest;
if(map[\default].notNil) {
prevValueID = map[\default];
} {
prevValueID = Rest(0)
};
};
}
wrapPattern { |pattern|
^pattern.collect { |valueID, inEvent|
this.processValue(valueID, inEvent)
}
}
// higher level hook, for subclasses handling arrays
// we need both common behavior and class-specific overrides
// I don't really *want* to split it further down but I don't see a better way
processValue { |valueID, inEvent|
var saveID = valueID;
var next = this.prProcessValue(valueID, inEvent);
prevValueID = saveID;
^next
}
prProcessValue { |valueID, inEvent|
^this.processOneValue(valueID, inEvent)
}
// valueID is the cll representation (character or SequenceNote)
processOneValue { |valueID, inEvent|
^this.valueForParm(valueID, inEvent) ?? { Rest(valueID) }
}
// these should be consistent:
// the subcases for value conversion don't depend on aliasing
// but rather on pitched vs nonpitched
// I'd rather not explode 5 classes into 10 though
valueForParm { |event, inEvent|
^this.perform(valueLookup, event, inEvent)
}
pitchLookup { |event, inEvent|
var convert = map.tryPerform(\at, \convertFunc);
^if(event.isKindOf(SequenceNote)) {
// only defaultParm should influence articulation
if(isDefault) {
if(event.length <= 0.4) {
// this must be a function because ~dur is not populated yet!
inEvent[\sustain] = { min(0.15, ~dur * event.length) };
} {
inEvent[\legato] = event.length
};
// press args into service for accents, may change the spec later
if(event.args == \accent) {
inEvent[\accent] = true;
};
};
// not .asFloat: We already know this is a SeqNote, and asFloat breaks Rests here.
convert.(event, inEvent, map) ?? { event.freq }
} {
if(event == \rest or: { event.class == Char }) {
if(isDefault) {
inEvent[\legato] = 0.9;
event = Rest(map[\rest] ?? { 0 });
} {
event = prevValueID;
}
};
convert.(event, inEvent, map) ?? { event }
};
}
dictLookup { |event, inEvent|
var convert = result = if(map.notNil) {
if(map[\convertFunc].notNil) {
convert = { |ev| map[\convertFunc].value(ev, inEvent, map) }
} {
convert = { |ev| map[ev] };
};
};
var result = convert.(event);
^(if(result == \rest or: { event == \rest }) {
Rest(result ?? { 0 })
} {
// character token was not specified in the parmMap
// always rest if default; non-default parms substitute prev value
if(isDefault) {
result
} {
result ?? { convert.(prevValueID) }
}
});
}
valueIsRest { |event, inEvent|
^this.perform(isRest, event, inEvent)
}
notRest { ^false }
pitchIsRest { |event|
^if(event.respondsTo(\freq)) {
event.freq.isRest
} {
// if we get here -- not a SequenceNote -- then it should be a Char
// if not a Char, assume rest
event.tryPerform(\isAlpha) ?? { true }
}
}
otherIsRest { |event, inEvent|
var result;
^if(map.notNil) {
result = if(map[\convertFunc].notNil) {
map[\convertFunc].(event, inEvent);
} {
map[event]
};
result.isRest or: { result.isNil }
} {
event.isRest
}
}
}
CllDefaultNoAliasParm : CllParm {
prProcessValue { |valueID, inEvent|
var out;
out = valueID.copy;
out[0] = this.processOneValue(out[0], inEvent);
^out
}
}
CllSimpleAliasParm : CllParm {
prProcessValue { |valueID, inEvent|
var result = this.processOneValue(valueID, inEvent);
inEvent.put(storeParm, result);
^result
}
}
CllDefaultArrayAliasParm : CllParm {
prProcessValue { |valueID, inEvent|
var result = this.valueForParm(valueID[0], inEvent), sp0;
if(result.isNil) {
result = [Rest(valueID[0])];
} {
result = result.asArray;
};
if(storeParm.size >= result.size) {
storeParm.do { |key, i|
inEvent.put(key, result.wrapAt(i));
};
} {
"Alias for % allows % values, but too many (%) were provided"
.format(storeParm.asCompileString, storeParm, result.size)
.warn;
};
// valueID[1] should be the dur value -- necessary to be second return value
// if 'result' is one item, unbubble reverses the 'asArray' earlier
^[result.unbubble, valueID[1], valueID[2]]
}
}
CllNonDefaultArrayAliasParm : CllParm {
prProcessValue { |valueID, inEvent|
var result = this.valueForParm(valueID, inEvent);
if(result.isNil) {
result = [Rest(valueID)];
} {
result = result.asArray;
};
if(storeParm.size >= result.size) {
storeParm.do { |key, i|
inEvent.put(key, result.wrapAt(i));
};
} {
"Alias for % allows % values, but too many (%) were provided"
.format(storeParm.asCompileString, storeParm.size, result.size)
.warn;
};
^result
}
}