/[svn]/linuxsampler/trunk/src/engines/common/InstrumentScriptVMFunctions.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/engines/common/InstrumentScriptVMFunctions.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3588 - (hide annotations) (download)
Sun Sep 1 16:06:48 2019 UTC (4 years, 7 months ago) by schoenebeck
File size: 118441 byte(s)
NKSP: Built-in instrument functions fixes & hardening:

* Fixed the following built-in functions having misinterpreted values given
  with unit type (for their 2nd argument) as if they were relative values
  (that is as if they were passed without a unit type): "change_attack()",
  "change_decay()", "change_release()", "change_cutoff_attack()",
  "change_cutoff_decay()", "change_cutoff_release()".

* Fixed the following built-in functions having applied completely wrong
  'final' values: "change_sustain()", "change_cutoff_sustain()" (since the
  respective EGs being their modulation sink assume uint data type with
  value range 0..1000 instead of 0.0..1.0.

* Added individual parse-time checks of function arguments for the following
  built-in functions: "play_note()", "note_off()", "set_event_mark()",
  "delete_event_mark()", "by_marks()", "change_cutoff()", "change_attack()",
  "change_decay()", "change_release()", "change_cutoff_attack()",
  "change_cutoff_decay()", "change_cutoff_release()",
  "change_amp_lfo_freq()", "change_cutoff_lfo_freq()",
  "change_pitch_lfo_freq()", "change_vol_time()", "change_tune_time()" and
  "change_pan_time()".

* Don't abort function call if unit type was used and at the same time
  'final' operator was omitted for the primary value argument of the
  following built-in functions: "change_cutoff()", "change_attack()",
  "change_decay()", "change_release()", "change_cutoff_attack()",
  "change_cutoff_decay()", "change_cutoff_release()",
  "change_amp_lfo_freq()", "change_cutoff_lfo_freq()",
  "change_pitch_lfo_freq()", "change_vol_time()", "change_tune_time()",
  "change_pan_time()", instead imply 'final'ness at runtime and raise an
  appropriate parser warning at parse time.

* Bumped version (2.1.1.svn13).

1 schoenebeck 2596 /*
2 schoenebeck 3557 * Copyright (c) 2014-2019 Christian Schoenebeck
3 schoenebeck 2596 *
4     * http://www.linuxsampler.org
5     *
6     * This file is part of LinuxSampler and released under the same terms.
7     * See README file for details.
8     */
9    
10     #include "InstrumentScriptVMFunctions.h"
11     #include "InstrumentScriptVM.h"
12     #include "../AbstractEngineChannel.h"
13 schoenebeck 3561 #include "../../common/global_private.h"
14 schoenebeck 2596
15     namespace LinuxSampler {
16 schoenebeck 2931
17     // play_note() function
18 schoenebeck 2596
19     InstrumentScriptVMFunction_play_note::InstrumentScriptVMFunction_play_note(InstrumentScriptVM* parent)
20     : m_vm(parent)
21     {
22     }
23    
24 schoenebeck 3587 bool InstrumentScriptVMFunction_play_note::acceptsArgType(vmint iArg, ExprType_t type) const {
25     if (iArg == 2 || iArg == 3)
26     return type == INT_EXPR || type == REAL_EXPR;
27     else
28     return type == INT_EXPR;
29     }
30    
31     bool InstrumentScriptVMFunction_play_note::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
32     if (iArg == 2 || iArg == 3)
33     return type == VM_NO_UNIT || type == VM_SECOND;
34     else
35     return type == VM_NO_UNIT;
36     }
37    
38     bool InstrumentScriptVMFunction_play_note::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
39     if (iArg == 2 || iArg == 3)
40     return type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
41     else
42     return false;
43     }
44    
45 schoenebeck 3588 void InstrumentScriptVMFunction_play_note::checkArgs(VMFnArgs* args,
46     std::function<void(String)> err,
47     std::function<void(String)> wrn)
48     {
49     // super class checks
50     Super::checkArgs(args, err, wrn);
51    
52     // own checks ...
53     if (args->arg(0)->isConstExpr()) {
54     vmint note = args->arg(0)->asNumber()->evalCastInt();
55     if (note < 0 || note > 127) {
56     err("MIDI note number value for argument 1 must be between 0..127");
57     return;
58     }
59     }
60     if (args->argsCount() >= 2 && args->arg(1)->isConstExpr()) {
61     vmint velocity = args->arg(1)->asNumber()->evalCastInt();
62     if (velocity < 0 || velocity > 127) {
63     err("MIDI velocity value for argument 2 must be between 0..127");
64     return;
65     }
66     }
67     if (args->argsCount() >= 3 && args->arg(2)->isConstExpr()) {
68     VMNumberExpr* argSampleOffset = args->arg(2)->asNumber();
69     vmint sampleoffset =
70     (argSampleOffset->unitType()) ?
71     argSampleOffset->evalCastInt(VM_MICRO) :
72     argSampleOffset->evalCastInt();
73     if (sampleoffset < -1) {
74     err("Sample offset of argument 3 may not be less than -1");
75     return;
76     }
77     }
78     if (args->argsCount() >= 4 && args->arg(3)->isConstExpr()) {
79     VMNumberExpr* argDuration = args->arg(3)->asNumber();
80     vmint duration =
81     (argDuration->unitType()) ?
82     argDuration->evalCastInt(VM_MICRO) :
83     argDuration->evalCastInt();
84     if (duration < -2) {
85     err("Argument 4 must be a duration value of at least -2 or higher");
86     return;
87     }
88     }
89     }
90    
91 schoenebeck 2596 VMFnResult* InstrumentScriptVMFunction_play_note::exec(VMFnArgs* args) {
92 schoenebeck 3557 vmint note = args->arg(0)->asInt()->evalInt();
93     vmint velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;
94 schoenebeck 3587 VMNumberExpr* argDuration = (args->argsCount() >= 4) ? args->arg(3)->asNumber() : NULL;
95     vmint duration =
96     (argDuration) ?
97     (argDuration->unitType()) ?
98     argDuration->evalCastInt(VM_MICRO) :
99     argDuration->evalCastInt() : 0; //TODO: -1 might be a better default value instead of 0
100 schoenebeck 2596
101     if (note < 0 || note > 127) {
102     errMsg("play_note(): argument 1 is an invalid note number");
103 schoenebeck 2879 return errorResult(0);
104 schoenebeck 2596 }
105    
106     if (velocity < 0 || velocity > 127) {
107     errMsg("play_note(): argument 2 is an invalid velocity value");
108 schoenebeck 2879 return errorResult(0);
109 schoenebeck 2596 }
110    
111 schoenebeck 3253 if (duration < -2) {
112     errMsg("play_note(): argument 4 must be a duration value of at least -2 or higher");
113 schoenebeck 2879 return errorResult(0);
114 schoenebeck 2596 }
115    
116     AbstractEngineChannel* pEngineChannel =
117     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
118    
119 schoenebeck 2879 Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
120     e.Init(); // clear IDs
121 schoenebeck 2938 e.Type = Event::type_play_note;
122 schoenebeck 2596 e.Param.Note.Key = note;
123     e.Param.Note.Velocity = velocity;
124 schoenebeck 2879 // make this new note dependent to the life time of the original note
125     if (duration == -1) {
126     if (m_vm->currentVMEventHandler()->eventHandlerType() != VM_EVENT_HANDLER_NOTE) {
127     errMsg("play_note(): -1 for argument 4 may only be used for note event handlers");
128     return errorResult(0);
129     }
130     e.Param.Note.ParentNoteID = m_vm->m_event->cause.Param.Note.ID;
131 schoenebeck 3253 // check if that requested parent note is actually still alive
132     NoteBase* pParentNote =
133     pEngineChannel->pEngine->NoteByID( e.Param.Note.ParentNoteID );
134     // if parent note is already gone then this new note is not required anymore
135     if (!pParentNote)
136     return successResult(0);
137 schoenebeck 2879 }
138 schoenebeck 2596
139 schoenebeck 2879 const note_id_t id = pEngineChannel->ScheduleNoteMicroSec(&e, 0);
140 schoenebeck 2596
141 schoenebeck 3251 // if a sample offset is supplied, assign the offset as override
142     // to the previously created Note object
143     if (args->argsCount() >= 3) {
144 schoenebeck 3587 VMNumberExpr* argSampleOffset = args->arg(2)->asNumber();
145     vmint sampleoffset =
146     (argSampleOffset->unitType()) ?
147     argSampleOffset->evalCastInt(VM_MICRO) :
148     argSampleOffset->evalCastInt();
149 schoenebeck 3251 if (sampleoffset >= 0) {
150     NoteBase* pNote = pEngineChannel->pEngine->NoteByID(id);
151     if (pNote) {
152 schoenebeck 3557 pNote->Override.SampleOffset =
153     (decltype(pNote->Override.SampleOffset)) sampleoffset;
154 schoenebeck 3251 }
155     } else if (sampleoffset < -1) {
156     errMsg("play_note(): sample offset of argument 3 may not be less than -1");
157     }
158     }
159    
160 schoenebeck 2938 // if a duration is supplied (and play-note event was scheduled
161     // successfully above), then schedule a subsequent stop-note event
162 schoenebeck 2879 if (id && duration > 0) {
163 schoenebeck 2938 e.Type = Event::type_stop_note;
164     e.Param.Note.ID = id;
165 schoenebeck 2871 e.Param.Note.Velocity = 127;
166     pEngineChannel->ScheduleEventMicroSec(&e, duration);
167     }
168    
169 schoenebeck 2879 // even if id is null, don't return an errorResult() here, because that
170     // would abort the script, and under heavy load it may be considerable
171     // that ScheduleNoteMicroSec() fails above, so simply ignore that
172     return successResult( ScriptID::fromNoteID(id) );
173 schoenebeck 2598 }
174    
175 schoenebeck 2931 // set_controller() function
176    
177 schoenebeck 2600 InstrumentScriptVMFunction_set_controller::InstrumentScriptVMFunction_set_controller(InstrumentScriptVM* parent)
178     : m_vm(parent)
179     {
180     }
181    
182     VMFnResult* InstrumentScriptVMFunction_set_controller::exec(VMFnArgs* args) {
183 schoenebeck 3557 vmint controller = args->arg(0)->asInt()->evalInt();
184     vmint value = args->arg(1)->asInt()->evalInt();
185 schoenebeck 2600
186     AbstractEngineChannel* pEngineChannel =
187     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
188    
189 schoenebeck 2879 Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
190     e.Init(); // clear IDs
191 schoenebeck 2600 if (controller == CTRL_TABLE_IDX_AFTERTOUCH) {
192     e.Type = Event::type_channel_pressure;
193     e.Param.ChannelPressure.Value = value & 127;
194     } else if (controller == CTRL_TABLE_IDX_PITCHBEND) {
195     e.Type = Event::type_pitchbend;
196     e.Param.Pitch.Pitch = value;
197     } else if (controller >= 0 && controller <= 127) {
198     e.Type = Event::type_control_change;
199     e.Param.CC.Controller = controller;
200     e.Param.CC.Value = value;
201     } else {
202     errMsg("set_controller(): argument 1 is an invalid controller");
203     return errorResult();
204     }
205    
206 schoenebeck 2879 const event_id_t id = pEngineChannel->ScheduleEventMicroSec(&e, 0);
207 schoenebeck 2600
208 schoenebeck 2879 // even if id is null, don't return an errorResult() here, because that
209     // would abort the script, and under heavy load it may be considerable
210     // that ScheduleEventMicroSec() fails above, so simply ignore that
211     return successResult( ScriptID::fromEventID(id) );
212 schoenebeck 2931 }
213 schoenebeck 2600
214 schoenebeck 2931 // ignore_event() function
215    
216 schoenebeck 2598 InstrumentScriptVMFunction_ignore_event::InstrumentScriptVMFunction_ignore_event(InstrumentScriptVM* parent)
217     : m_vm(parent)
218     {
219     }
220    
221 schoenebeck 3557 bool InstrumentScriptVMFunction_ignore_event::acceptsArgType(vmint iArg, ExprType_t type) const {
222 schoenebeck 2630 return type == INT_EXPR || type == INT_ARR_EXPR;
223     }
224    
225 schoenebeck 2598 VMFnResult* InstrumentScriptVMFunction_ignore_event::exec(VMFnArgs* args) {
226     AbstractEngineChannel* pEngineChannel =
227 schoenebeck 2630 static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
228 schoenebeck 2598
229 schoenebeck 3212 if (args->argsCount() == 0 || args->arg(0)->exprType() == INT_EXPR) {
230     const ScriptID id = (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : m_vm->m_event->id;
231     if (!id && args->argsCount() >= 1) {
232 schoenebeck 2879 wrnMsg("ignore_event(): event ID argument may not be zero");
233     // not errorResult(), because that would abort the script, not intentional in this case
234 schoenebeck 2630 return successResult();
235     }
236 schoenebeck 2879 pEngineChannel->IgnoreEventByScriptID(id);
237 schoenebeck 2630 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
238     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
239     for (int i = 0; i < ids->arraySize(); ++i) {
240 schoenebeck 2879 const ScriptID id = ids->evalIntElement(i);
241     pEngineChannel->IgnoreEventByScriptID(id);
242 schoenebeck 2630 }
243     }
244 schoenebeck 2598
245 schoenebeck 2596 return successResult();
246     }
247    
248 schoenebeck 2931 // ignore_controller() function
249    
250 schoenebeck 2598 InstrumentScriptVMFunction_ignore_controller::InstrumentScriptVMFunction_ignore_controller(InstrumentScriptVM* parent)
251     : m_vm(parent)
252     {
253     }
254    
255     VMFnResult* InstrumentScriptVMFunction_ignore_controller::exec(VMFnArgs* args) {
256 schoenebeck 2879 const ScriptID id = (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : m_vm->m_event->id;
257     if (!id && args->argsCount() >= 1) {
258     wrnMsg("ignore_controller(): event ID argument may not be zero");
259 schoenebeck 2598 return successResult();
260     }
261    
262     AbstractEngineChannel* pEngineChannel =
263     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
264    
265 schoenebeck 2879 pEngineChannel->IgnoreEventByScriptID(id);
266 schoenebeck 2598
267     return successResult();
268     }
269    
270 schoenebeck 2931 // note_off() function
271    
272 schoenebeck 2629 InstrumentScriptVMFunction_note_off::InstrumentScriptVMFunction_note_off(InstrumentScriptVM* parent)
273     : m_vm(parent)
274     {
275     }
276    
277 schoenebeck 3557 bool InstrumentScriptVMFunction_note_off::acceptsArgType(vmint iArg, ExprType_t type) const {
278 schoenebeck 2630 return type == INT_EXPR || type == INT_ARR_EXPR;
279     }
280    
281 schoenebeck 3588 void InstrumentScriptVMFunction_note_off::checkArgs(VMFnArgs* args,
282     std::function<void(String)> err,
283     std::function<void(String)> wrn)
284     {
285     // super class checks
286     Super::checkArgs(args, err, wrn);
287    
288     // own checks ...
289     if (args->argsCount() >= 2 && args->arg(1)->isConstExpr() && args->arg(1)->exprType() == INT_EXPR) {
290     vmint velocity = args->arg(1)->asInt()->evalInt();
291     if (velocity < 0 || velocity > 127) {
292     err("MIDI velocity value for argument 2 must be between 0..127");
293     return;
294     }
295     }
296     }
297    
298 schoenebeck 2629 VMFnResult* InstrumentScriptVMFunction_note_off::exec(VMFnArgs* args) {
299 schoenebeck 2630 AbstractEngineChannel* pEngineChannel =
300     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
301    
302 schoenebeck 3557 vmint velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;
303 schoenebeck 2630 if (velocity < 0 || velocity > 127) {
304     errMsg("note_off(): argument 2 is an invalid velocity value");
305     return errorResult();
306     }
307 schoenebeck 2629
308 schoenebeck 2630 if (args->arg(0)->exprType() == INT_EXPR) {
309 schoenebeck 2879 const ScriptID id = args->arg(0)->asInt()->evalInt();
310     if (!id) {
311     wrnMsg("note_off(): note ID for argument 1 may not be zero");
312 schoenebeck 2630 return successResult();
313     }
314 schoenebeck 2879 if (!id.isNoteID()) {
315     wrnMsg("note_off(): argument 1 is not a note ID");
316     return successResult();
317     }
318 schoenebeck 2630
319 schoenebeck 2879 NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
320     if (!pNote) return successResult();
321 schoenebeck 2630
322 schoenebeck 2879 Event e = pNote->cause;
323     e.Init(); // clear IDs
324     e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"
325 schoenebeck 2938 e.Type = Event::type_stop_note;
326     e.Param.Note.ID = id.noteID();
327     e.Param.Note.Key = pNote->hostKey;
328 schoenebeck 2630 e.Param.Note.Velocity = velocity;
329    
330 schoenebeck 2879 pEngineChannel->ScheduleEventMicroSec(&e, 0);
331 schoenebeck 2630 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
332     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
333 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
334 schoenebeck 2879 const ScriptID id = ids->evalIntElement(i);
335     if (!id || !id.isNoteID()) continue;
336 schoenebeck 2630
337 schoenebeck 2879 NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
338     if (!pNote) continue;
339 schoenebeck 2630
340 schoenebeck 2879 Event e = pNote->cause;
341     e.Init(); // clear IDs
342     e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"
343 schoenebeck 2938 e.Type = Event::type_stop_note;
344     e.Param.Note.ID = id.noteID();
345     e.Param.Note.Key = pNote->hostKey;
346 schoenebeck 2630 e.Param.Note.Velocity = velocity;
347    
348 schoenebeck 2879 pEngineChannel->ScheduleEventMicroSec(&e, 0);
349 schoenebeck 2630 }
350 schoenebeck 2629 }
351    
352 schoenebeck 2630 return successResult();
353     }
354    
355 schoenebeck 2931 // set_event_mark() function
356    
357 schoenebeck 2630 InstrumentScriptVMFunction_set_event_mark::InstrumentScriptVMFunction_set_event_mark(InstrumentScriptVM* parent)
358     : m_vm(parent)
359     {
360     }
361    
362 schoenebeck 3588 void InstrumentScriptVMFunction_set_event_mark::checkArgs(VMFnArgs* args,
363     std::function<void(String)> err,
364     std::function<void(String)> wrn)
365     {
366     // super class checks
367     Super::checkArgs(args, err, wrn);
368    
369     // own checks ...
370     if (args->argsCount() >= 2 && args->arg(1)->isConstExpr()) {
371     const vmint groupID = args->arg(1)->asInt()->evalInt();
372     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
373     err("Argument 2 value is an invalid group id.");
374     return;
375     }
376     }
377     }
378    
379 schoenebeck 2630 VMFnResult* InstrumentScriptVMFunction_set_event_mark::exec(VMFnArgs* args) {
380 schoenebeck 2879 const ScriptID id = args->arg(0)->asInt()->evalInt();
381 schoenebeck 3557 const vmint groupID = args->arg(1)->asInt()->evalInt();
382 schoenebeck 2630
383     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
384     errMsg("set_event_mark(): argument 2 is an invalid group id");
385 schoenebeck 2629 return errorResult();
386     }
387    
388     AbstractEngineChannel* pEngineChannel =
389     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
390    
391 schoenebeck 2879 // check if the event/note still exists
392     switch (id.type()) {
393     case ScriptID::EVENT: {
394     RTList<Event>::Iterator itEvent = pEngineChannel->pEngine->EventByID( id.eventID() );
395     if (!itEvent) return successResult();
396     break;
397     }
398     case ScriptID::NOTE: {
399     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
400     if (!pNote) return successResult();
401     break;
402     }
403     }
404 schoenebeck 2629
405 schoenebeck 2879 pEngineChannel->pScript->eventGroups[groupID].insert(id);
406 schoenebeck 2629
407 schoenebeck 2630 return successResult();
408     }
409 schoenebeck 2629
410 schoenebeck 2931 // delete_event_mark() function
411    
412 schoenebeck 2630 InstrumentScriptVMFunction_delete_event_mark::InstrumentScriptVMFunction_delete_event_mark(InstrumentScriptVM* parent)
413     : m_vm(parent)
414     {
415     }
416    
417 schoenebeck 3588 void InstrumentScriptVMFunction_delete_event_mark::checkArgs(VMFnArgs* args,
418     std::function<void(String)> err,
419     std::function<void(String)> wrn)
420     {
421     // super class checks
422     Super::checkArgs(args, err, wrn);
423    
424     // own checks ...
425     if (args->argsCount() >= 2 && args->arg(1)->isConstExpr()) {
426     const vmint groupID = args->arg(1)->asInt()->evalInt();
427     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
428     err("Argument 2 value is an invalid group id.");
429     return;
430     }
431     }
432     }
433    
434 schoenebeck 2630 VMFnResult* InstrumentScriptVMFunction_delete_event_mark::exec(VMFnArgs* args) {
435 schoenebeck 2879 const ScriptID id = args->arg(0)->asInt()->evalInt();
436 schoenebeck 3557 const vmint groupID = args->arg(1)->asInt()->evalInt();
437 schoenebeck 2630
438     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
439     errMsg("delete_event_mark(): argument 2 is an invalid group id");
440     return errorResult();
441     }
442    
443     AbstractEngineChannel* pEngineChannel =
444     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
445    
446 schoenebeck 2879 pEngineChannel->pScript->eventGroups[groupID].erase(id);
447 schoenebeck 2630
448 schoenebeck 2629 return successResult();
449     }
450    
451 schoenebeck 2931 // by_marks() function
452    
453 schoenebeck 2630 InstrumentScriptVMFunction_by_marks::InstrumentScriptVMFunction_by_marks(InstrumentScriptVM* parent)
454     : m_vm(parent)
455     {
456     }
457    
458 schoenebeck 3557 vmint InstrumentScriptVMFunction_by_marks::Result::arraySize() const {
459 schoenebeck 2630 return eventGroup->size();
460     }
461    
462 schoenebeck 3557 vmint InstrumentScriptVMFunction_by_marks::Result::evalIntElement(vmuint i) {
463 schoenebeck 2630 return (*eventGroup)[i];
464     }
465    
466     VMFnResult* InstrumentScriptVMFunction_by_marks::errorResult() {
467     m_result.eventGroup = NULL;
468     m_result.flags = StmtFlags_t(STMT_ABORT_SIGNALLED | STMT_ERROR_OCCURRED);
469     return &m_result;
470     }
471    
472     VMFnResult* InstrumentScriptVMFunction_by_marks::successResult(EventGroup* eventGroup) {
473     m_result.eventGroup = eventGroup;
474     m_result.flags = STMT_SUCCESS;
475     return &m_result;
476     }
477    
478 schoenebeck 3588 void InstrumentScriptVMFunction_by_marks::checkArgs(VMFnArgs* args,
479     std::function<void(String)> err,
480     std::function<void(String)> wrn)
481     {
482     // super class checks
483     Super::checkArgs(args, err, wrn);
484    
485     // own checks ...
486     if (args->arg(0)->isConstExpr()) {
487     const vmint groupID = args->arg(0)->asInt()->evalInt();
488     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
489     err("Argument value is an invalid group id.");
490     return;
491     }
492     }
493     }
494    
495 schoenebeck 2630 VMFnResult* InstrumentScriptVMFunction_by_marks::exec(VMFnArgs* args) {
496 schoenebeck 3557 vmint groupID = args->arg(0)->asInt()->evalInt();
497 schoenebeck 2630
498     if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
499     errMsg("by_marks(): argument is an invalid group id");
500     return errorResult();
501     }
502    
503     AbstractEngineChannel* pEngineChannel =
504     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
505    
506     return successResult( &pEngineChannel->pScript->eventGroups[groupID] );
507     }
508    
509 schoenebeck 2931 // change_vol() function
510    
511     InstrumentScriptVMFunction_change_vol::InstrumentScriptVMFunction_change_vol(InstrumentScriptVM* parent)
512     : m_vm(parent)
513     {
514     }
515    
516 schoenebeck 3557 bool InstrumentScriptVMFunction_change_vol::acceptsArgType(vmint iArg, ExprType_t type) const {
517 schoenebeck 2931 if (iArg == 0)
518     return type == INT_EXPR || type == INT_ARR_EXPR;
519 schoenebeck 3587 else if (iArg == 1)
520     return type == INT_EXPR || type == REAL_EXPR;
521 schoenebeck 2931 else
522 schoenebeck 3188 return type == INT_EXPR;
523 schoenebeck 2931 }
524    
525 schoenebeck 3561 bool InstrumentScriptVMFunction_change_vol::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
526     if (iArg == 1)
527     return type == VM_NO_UNIT || type == VM_BEL;
528     else
529     return type == VM_NO_UNIT;
530     }
531    
532 schoenebeck 3564 bool InstrumentScriptVMFunction_change_vol::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
533 schoenebeck 3587 return iArg == 1 && type == VM_BEL; // only allow metric prefix(es) if 'Bel' is used as unit type
534 schoenebeck 3561 }
535    
536     bool InstrumentScriptVMFunction_change_vol::acceptsArgFinal(vmint iArg) const {
537     return iArg == 1;
538     }
539    
540 schoenebeck 2931 VMFnResult* InstrumentScriptVMFunction_change_vol::exec(VMFnArgs* args) {
541 schoenebeck 3587 StdUnit_t unit = args->arg(1)->asNumber()->unitType();
542     vmint volume =
543     (unit) ?
544     args->arg(1)->asNumber()->evalCastInt(VM_MILLI,VM_DECI) :
545     args->arg(1)->asNumber()->evalCastInt(); // volume change in milli dB
546     bool isFinal = args->arg(1)->asNumber()->isFinal();
547 schoenebeck 2931 bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
548 schoenebeck 2962 const float fVolumeLin = RTMath::DecibelToLinRatio(float(volume) / 1000.f);
549 schoenebeck 2931
550     AbstractEngineChannel* pEngineChannel =
551     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
552    
553     if (args->arg(0)->exprType() == INT_EXPR) {
554     const ScriptID id = args->arg(0)->asInt()->evalInt();
555     if (!id) {
556     wrnMsg("change_vol(): note ID for argument 1 may not be zero");
557     return successResult();
558     }
559     if (!id.isNoteID()) {
560     wrnMsg("change_vol(): argument 1 is not a note ID");
561     return successResult();
562     }
563    
564     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
565     if (!pNote) return successResult();
566    
567 schoenebeck 2962 // if change_vol() was called immediately after note was triggered
568 schoenebeck 3218 // then immediately apply the volume to note object, but only if
569     // change_vol_time() has not been called before
570     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
571     pNote->Override.VolumeTime <= DEFAULT_NOTE_VOLUME_TIME_S)
572     {
573 schoenebeck 2962 if (relative)
574 schoenebeck 3561 pNote->Override.Volume.Value *= fVolumeLin;
575 schoenebeck 2931 else
576 schoenebeck 3561 pNote->Override.Volume.Value = fVolumeLin;
577     pNote->Override.Volume.Final = isFinal;
578 schoenebeck 2962 } else { // otherwise schedule the volume change ...
579 schoenebeck 2931 Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
580     e.Init(); // clear IDs
581     e.Type = Event::type_note_synth_param;
582     e.Param.NoteSynthParam.NoteID = id.noteID();
583     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
584     e.Param.NoteSynthParam.Delta = fVolumeLin;
585 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
586     isFinal, relative, unit
587     );
588 schoenebeck 2931 pEngineChannel->ScheduleEventMicroSec(&e, 0);
589     }
590 schoenebeck 2962 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
591     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
592 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
593 schoenebeck 2962 const ScriptID id = ids->evalIntElement(i);
594     if (!id || !id.isNoteID()) continue;
595    
596     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
597     if (!pNote) continue;
598    
599     // if change_vol() was called immediately after note was triggered
600 schoenebeck 3218 // then immediately apply the volume to Note object, but only if
601     // change_vol_time() has not been called before
602     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
603     pNote->Override.VolumeTime <= DEFAULT_NOTE_VOLUME_TIME_S)
604     {
605 schoenebeck 2962 if (relative)
606 schoenebeck 3561 pNote->Override.Volume.Value *= fVolumeLin;
607 schoenebeck 2962 else
608 schoenebeck 3561 pNote->Override.Volume.Value = fVolumeLin;
609     pNote->Override.Volume.Final = isFinal;
610 schoenebeck 2962 } else { // otherwise schedule the volume change ...
611     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
612     e.Init(); // clear IDs
613     e.Type = Event::type_note_synth_param;
614     e.Param.NoteSynthParam.NoteID = id.noteID();
615     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
616     e.Param.NoteSynthParam.Delta = fVolumeLin;
617 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
618     isFinal, relative, unit
619     );
620 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
621     }
622     }
623 schoenebeck 2931 }
624    
625     return successResult();
626     }
627    
628     // change_tune() function
629    
630     InstrumentScriptVMFunction_change_tune::InstrumentScriptVMFunction_change_tune(InstrumentScriptVM* parent)
631     : m_vm(parent)
632     {
633     }
634    
635 schoenebeck 3557 bool InstrumentScriptVMFunction_change_tune::acceptsArgType(vmint iArg, ExprType_t type) const {
636 schoenebeck 2931 if (iArg == 0)
637     return type == INT_EXPR || type == INT_ARR_EXPR;
638 schoenebeck 3587 else if (iArg == 1)
639     return type == INT_EXPR || type == REAL_EXPR;
640 schoenebeck 2931 else
641 schoenebeck 3188 return type == INT_EXPR;
642 schoenebeck 2931 }
643    
644 schoenebeck 3564 bool InstrumentScriptVMFunction_change_tune::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
645 schoenebeck 3561 return iArg == 1;
646     }
647    
648     bool InstrumentScriptVMFunction_change_tune::acceptsArgFinal(vmint iArg) const {
649     return iArg == 1;
650     }
651    
652 schoenebeck 2931 VMFnResult* InstrumentScriptVMFunction_change_tune::exec(VMFnArgs* args) {
653 schoenebeck 3587 vmint tune =
654     (args->arg(1)->asNumber()->hasUnitFactorNow())
655     ? args->arg(1)->asNumber()->evalCastInt(VM_MILLI,VM_CENTI)
656     : args->arg(1)->asNumber()->evalCastInt(); // tuning change in milli cents
657     bool isFinal = args->arg(1)->asNumber()->isFinal();
658     StdUnit_t unit = args->arg(1)->asNumber()->unitType();
659 schoenebeck 2931 bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
660 schoenebeck 2962 const float fFreqRatio = RTMath::CentsToFreqRatioUnlimited(float(tune) / 1000.f);
661 schoenebeck 2931
662     AbstractEngineChannel* pEngineChannel =
663     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
664    
665     if (args->arg(0)->exprType() == INT_EXPR) {
666     const ScriptID id = args->arg(0)->asInt()->evalInt();
667     if (!id) {
668     wrnMsg("change_tune(): note ID for argument 1 may not be zero");
669     return successResult();
670     }
671     if (!id.isNoteID()) {
672     wrnMsg("change_tune(): argument 1 is not a note ID");
673     return successResult();
674     }
675    
676     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
677     if (!pNote) return successResult();
678    
679 schoenebeck 2962 // if change_tune() was called immediately after note was triggered
680 schoenebeck 3218 // then immediately apply the tuning to Note object, but only if
681     // change_tune_time() has not been called before
682     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
683     pNote->Override.PitchTime <= DEFAULT_NOTE_PITCH_TIME_S)
684     {
685 schoenebeck 2962 if (relative)
686 schoenebeck 3561 pNote->Override.Pitch.Value *= fFreqRatio;
687 schoenebeck 2931 else
688 schoenebeck 3561 pNote->Override.Pitch.Value = fFreqRatio;
689     pNote->Override.Pitch.Final = isFinal;
690 schoenebeck 2962 } else { // otherwise schedule tuning change ...
691 schoenebeck 2931 Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
692     e.Init(); // clear IDs
693     e.Type = Event::type_note_synth_param;
694     e.Param.NoteSynthParam.NoteID = id.noteID();
695     e.Param.NoteSynthParam.Type = Event::synth_param_pitch;
696     e.Param.NoteSynthParam.Delta = fFreqRatio;
697 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
698     isFinal, relative, unit
699     );
700 schoenebeck 2931 pEngineChannel->ScheduleEventMicroSec(&e, 0);
701     }
702 schoenebeck 2962 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
703     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
704 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
705 schoenebeck 2962 const ScriptID id = ids->evalIntElement(i);
706     if (!id || !id.isNoteID()) continue;
707    
708     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
709     if (!pNote) continue;
710    
711     // if change_tune() was called immediately after note was triggered
712 schoenebeck 3218 // then immediately apply the tuning to Note object, but only if
713     // change_tune_time() has not been called before
714     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
715     pNote->Override.PitchTime <= DEFAULT_NOTE_PITCH_TIME_S)
716     {
717 schoenebeck 2962 if (relative)
718 schoenebeck 3561 pNote->Override.Pitch.Value *= fFreqRatio;
719 schoenebeck 2962 else
720 schoenebeck 3561 pNote->Override.Pitch.Value = fFreqRatio;
721     pNote->Override.Pitch.Final = isFinal;
722 schoenebeck 2962 } else { // otherwise schedule tuning change ...
723     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
724     e.Init(); // clear IDs
725     e.Type = Event::type_note_synth_param;
726     e.Param.NoteSynthParam.NoteID = id.noteID();
727     e.Param.NoteSynthParam.Type = Event::synth_param_pitch;
728     e.Param.NoteSynthParam.Delta = fFreqRatio;
729 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
730     isFinal, relative, unit
731     );
732 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
733     }
734     }
735 schoenebeck 2931 }
736    
737     return successResult();
738     }
739    
740     // change_pan() function
741    
742     InstrumentScriptVMFunction_change_pan::InstrumentScriptVMFunction_change_pan(InstrumentScriptVM* parent)
743     : m_vm(parent)
744     {
745     }
746    
747 schoenebeck 3557 bool InstrumentScriptVMFunction_change_pan::acceptsArgType(vmint iArg, ExprType_t type) const {
748 schoenebeck 2931 if (iArg == 0)
749     return type == INT_EXPR || type == INT_ARR_EXPR;
750     else
751 schoenebeck 3188 return type == INT_EXPR;
752 schoenebeck 2931 }
753    
754 schoenebeck 3561 bool InstrumentScriptVMFunction_change_pan::acceptsArgFinal(vmint iArg) const {
755     return iArg == 1;
756     }
757    
758 schoenebeck 2931 VMFnResult* InstrumentScriptVMFunction_change_pan::exec(VMFnArgs* args) {
759 schoenebeck 3561 vmint pan = args->arg(1)->asInt()->evalInt();
760     bool isFinal = args->arg(1)->asInt()->isFinal();
761 schoenebeck 2931 bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
762    
763     if (pan > 1000) {
764     wrnMsg("change_pan(): argument 2 may not be larger than 1000");
765     pan = 1000;
766     } else if (pan < -1000) {
767     wrnMsg("change_pan(): argument 2 may not be smaller than -1000");
768     pan = -1000;
769     }
770 schoenebeck 2962 const float fPan = float(pan) / 1000.f;
771 schoenebeck 2931
772     AbstractEngineChannel* pEngineChannel =
773     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
774    
775     if (args->arg(0)->exprType() == INT_EXPR) {
776     const ScriptID id = args->arg(0)->asInt()->evalInt();
777     if (!id) {
778     wrnMsg("change_pan(): note ID for argument 1 may not be zero");
779     return successResult();
780     }
781     if (!id.isNoteID()) {
782     wrnMsg("change_pan(): argument 1 is not a note ID");
783     return successResult();
784     }
785    
786     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
787     if (!pNote) return successResult();
788    
789 schoenebeck 2962 // if change_pan() was called immediately after note was triggered
790     // then immediately apply the panning to Note object
791 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
792 schoenebeck 2962 if (relative) {
793 schoenebeck 3561 pNote->Override.Pan.Value = RTMath::RelativeSummedAvg(pNote->Override.Pan.Value, fPan, ++pNote->Override.Pan.Sources);
794 schoenebeck 2931 } else {
795 schoenebeck 3561 pNote->Override.Pan.Value = fPan;
796     pNote->Override.Pan.Sources = 1; // only relevant on subsequent change_pan() calls on same note with 'relative' being set
797 schoenebeck 2962 }
798 schoenebeck 3561 pNote->Override.Pan.Final = isFinal;
799 schoenebeck 2962 } else { // otherwise schedule panning change ...
800 schoenebeck 2931 Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
801     e.Init(); // clear IDs
802     e.Type = Event::type_note_synth_param;
803     e.Param.NoteSynthParam.NoteID = id.noteID();
804     e.Param.NoteSynthParam.Type = Event::synth_param_pan;
805     e.Param.NoteSynthParam.Delta = fPan;
806 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
807     isFinal, relative, false
808     );
809 schoenebeck 2931 pEngineChannel->ScheduleEventMicroSec(&e, 0);
810     }
811 schoenebeck 2962 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
812     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
813 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
814 schoenebeck 2962 const ScriptID id = ids->evalIntElement(i);
815     if (!id || !id.isNoteID()) continue;
816    
817     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
818     if (!pNote) continue;
819    
820     // if change_pan() was called immediately after note was triggered
821     // then immediately apply the panning to Note object
822 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
823 schoenebeck 2962 if (relative) {
824 schoenebeck 3561 pNote->Override.Pan.Value = RTMath::RelativeSummedAvg(pNote->Override.Pan.Value, fPan, ++pNote->Override.Pan.Sources);
825 schoenebeck 2962 } else {
826 schoenebeck 3561 pNote->Override.Pan.Value = fPan;
827     pNote->Override.Pan.Sources = 1; // only relevant on subsequent change_pan() calls on same note with 'relative' being set
828 schoenebeck 2962 }
829 schoenebeck 3561 pNote->Override.Pan.Final = isFinal;
830 schoenebeck 2962 } else { // otherwise schedule panning change ...
831     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
832     e.Init(); // clear IDs
833     e.Type = Event::type_note_synth_param;
834     e.Param.NoteSynthParam.NoteID = id.noteID();
835     e.Param.NoteSynthParam.Type = Event::synth_param_pan;
836     e.Param.NoteSynthParam.Delta = fPan;
837 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
838     isFinal, relative, false
839     );
840 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
841     }
842     }
843 schoenebeck 2931 }
844    
845     return successResult();
846     }
847    
848 schoenebeck 2935 #define VM_FILTER_PAR_MAX_VALUE 1000000
849 schoenebeck 3561 #define VM_FILTER_PAR_MAX_HZ 30000
850 schoenebeck 2953 #define VM_EG_PAR_MAX_VALUE 1000000
851 schoenebeck 2935
852     // change_cutoff() function
853    
854     InstrumentScriptVMFunction_change_cutoff::InstrumentScriptVMFunction_change_cutoff(InstrumentScriptVM* parent)
855     : m_vm(parent)
856     {
857     }
858    
859 schoenebeck 3557 bool InstrumentScriptVMFunction_change_cutoff::acceptsArgType(vmint iArg, ExprType_t type) const {
860 schoenebeck 2935 if (iArg == 0)
861     return type == INT_EXPR || type == INT_ARR_EXPR;
862 schoenebeck 3587 else if (iArg == 1)
863     return type == INT_EXPR || type == REAL_EXPR;
864 schoenebeck 2935 else
865 schoenebeck 3188 return type == INT_EXPR;
866 schoenebeck 2935 }
867    
868 schoenebeck 3561 bool InstrumentScriptVMFunction_change_cutoff::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
869     if (iArg == 1)
870     return type == VM_NO_UNIT || type == VM_HERTZ;
871     else
872     return type == VM_NO_UNIT;
873     }
874    
875 schoenebeck 3564 bool InstrumentScriptVMFunction_change_cutoff::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
876 schoenebeck 3587 return iArg == 1 && type == VM_HERTZ; // only allow metric prefix(es) if 'Hz' is used as unit type
877 schoenebeck 3561 }
878    
879     bool InstrumentScriptVMFunction_change_cutoff::acceptsArgFinal(vmint iArg) const {
880     return iArg == 1;
881     }
882    
883 schoenebeck 3588 void InstrumentScriptVMFunction_change_cutoff::checkArgs(VMFnArgs* args,
884     std::function<void(String)> err,
885     std::function<void(String)> wrn)
886     {
887     // super class checks
888     Super::checkArgs(args, err, wrn);
889    
890     // own checks ...
891     if (args->argsCount() >= 2) {
892     VMNumberExpr* argCutoff = args->arg(1)->asNumber();
893     if (argCutoff->unitType() && !argCutoff->isFinal()) {
894     wrn("Argument 2 implies 'final' value when using Hz as unit for cutoff frequency.");
895     }
896     }
897     }
898    
899 schoenebeck 2935 VMFnResult* InstrumentScriptVMFunction_change_cutoff::exec(VMFnArgs* args) {
900 schoenebeck 3588 const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
901 schoenebeck 3587 vmint cutoff =
902     (unit) ?
903     args->arg(1)->asNumber()->evalCastInt(VM_NO_PREFIX) :
904     args->arg(1)->asNumber()->evalCastInt();
905 schoenebeck 3588 const bool isFinal =
906     (unit) ?
907     true : // imply 'final' value if unit type is used
908     args->arg(1)->asNumber()->isFinal();
909 schoenebeck 3561 if (!unit && cutoff > VM_FILTER_PAR_MAX_VALUE) {
910     wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_VALUE));
911 schoenebeck 2935 cutoff = VM_FILTER_PAR_MAX_VALUE;
912 schoenebeck 3561 } else if (unit && cutoff > VM_FILTER_PAR_MAX_HZ) {
913     wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_HZ) " Hz");
914     cutoff = VM_FILTER_PAR_MAX_HZ;
915 schoenebeck 2935 } else if (cutoff < 0) {
916     wrnMsg("change_cutoff(): argument 2 may not be negative");
917     cutoff = 0;
918     }
919 schoenebeck 3561 const float fCutoff =
920     (unit) ? cutoff : float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);
921 schoenebeck 2935
922     AbstractEngineChannel* pEngineChannel =
923     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
924    
925     if (args->arg(0)->exprType() == INT_EXPR) {
926     const ScriptID id = args->arg(0)->asInt()->evalInt();
927     if (!id) {
928     wrnMsg("change_cutoff(): note ID for argument 1 may not be zero");
929     return successResult();
930     }
931     if (!id.isNoteID()) {
932     wrnMsg("change_cutoff(): argument 1 is not a note ID");
933     return successResult();
934     }
935    
936     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
937     if (!pNote) return successResult();
938    
939 schoenebeck 2962 // if change_cutoff() was called immediately after note was triggered
940     // then immediately apply cutoff to Note object
941 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
942 schoenebeck 3561 pNote->Override.Cutoff.Value = fCutoff;
943     pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
944 schoenebeck 2962 } else { // otherwise schedule cutoff change ...
945     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
946     e.Init(); // clear IDs
947     e.Type = Event::type_note_synth_param;
948     e.Param.NoteSynthParam.NoteID = id.noteID();
949     e.Param.NoteSynthParam.Type = Event::synth_param_cutoff;
950     e.Param.NoteSynthParam.Delta = fCutoff;
951 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
952     isFinal, false, unit
953     );
954 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
955     }
956 schoenebeck 2935 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
957     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
958 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
959 schoenebeck 2935 const ScriptID id = ids->evalIntElement(i);
960     if (!id || !id.isNoteID()) continue;
961    
962     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
963     if (!pNote) continue;
964    
965 schoenebeck 2962 // if change_cutoff() was called immediately after note was triggered
966     // then immediately apply cutoff to Note object
967 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
968 schoenebeck 3561 pNote->Override.Cutoff.Value = fCutoff;
969     pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
970 schoenebeck 2962 } else { // otherwise schedule cutoff change ...
971     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
972     e.Init(); // clear IDs
973     e.Type = Event::type_note_synth_param;
974     e.Param.NoteSynthParam.NoteID = id.noteID();
975     e.Param.NoteSynthParam.Type = Event::synth_param_cutoff;
976     e.Param.NoteSynthParam.Delta = fCutoff;
977 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
978     isFinal, false, unit
979     );
980 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
981     }
982 schoenebeck 2935 }
983     }
984    
985     return successResult();
986     }
987    
988     // change_reso() function
989    
990     InstrumentScriptVMFunction_change_reso::InstrumentScriptVMFunction_change_reso(InstrumentScriptVM* parent)
991     : m_vm(parent)
992     {
993     }
994    
995 schoenebeck 3557 bool InstrumentScriptVMFunction_change_reso::acceptsArgType(vmint iArg, ExprType_t type) const {
996 schoenebeck 2935 if (iArg == 0)
997     return type == INT_EXPR || type == INT_ARR_EXPR;
998     else
999 schoenebeck 3188 return type == INT_EXPR;
1000 schoenebeck 2935 }
1001    
1002 schoenebeck 3561 bool InstrumentScriptVMFunction_change_reso::acceptsArgFinal(vmint iArg) const {
1003     return iArg == 1;
1004     }
1005    
1006 schoenebeck 2935 VMFnResult* InstrumentScriptVMFunction_change_reso::exec(VMFnArgs* args) {
1007 schoenebeck 3557 vmint resonance = args->arg(1)->asInt()->evalInt();
1008 schoenebeck 3561 bool isFinal = args->arg(1)->asInt()->isFinal();
1009 schoenebeck 2935 if (resonance > VM_FILTER_PAR_MAX_VALUE) {
1010     wrnMsg("change_reso(): argument 2 may not be larger than 1000000");
1011     resonance = VM_FILTER_PAR_MAX_VALUE;
1012     } else if (resonance < 0) {
1013     wrnMsg("change_reso(): argument 2 may not be negative");
1014     resonance = 0;
1015     }
1016 schoenebeck 2962 const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);
1017 schoenebeck 2935
1018     AbstractEngineChannel* pEngineChannel =
1019     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1020    
1021     if (args->arg(0)->exprType() == INT_EXPR) {
1022     const ScriptID id = args->arg(0)->asInt()->evalInt();
1023     if (!id) {
1024     wrnMsg("change_reso(): note ID for argument 1 may not be zero");
1025     return successResult();
1026     }
1027     if (!id.isNoteID()) {
1028     wrnMsg("change_reso(): argument 1 is not a note ID");
1029     return successResult();
1030     }
1031    
1032     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1033     if (!pNote) return successResult();
1034    
1035 schoenebeck 2962 // if change_reso() was called immediately after note was triggered
1036     // then immediately apply resonance to Note object
1037 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1038 schoenebeck 3561 pNote->Override.Resonance.Value = fResonance;
1039     pNote->Override.Resonance.Final = isFinal;
1040 schoenebeck 2962 } else { // otherwise schedule resonance change ...
1041     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1042     e.Init(); // clear IDs
1043     e.Type = Event::type_note_synth_param;
1044     e.Param.NoteSynthParam.NoteID = id.noteID();
1045     e.Param.NoteSynthParam.Type = Event::synth_param_resonance;
1046     e.Param.NoteSynthParam.Delta = fResonance;
1047 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1048     isFinal, false, false
1049     );
1050 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1051     }
1052 schoenebeck 2935 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1053     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1054 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1055 schoenebeck 2935 const ScriptID id = ids->evalIntElement(i);
1056     if (!id || !id.isNoteID()) continue;
1057    
1058     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1059     if (!pNote) continue;
1060    
1061 schoenebeck 2962 // if change_reso() was called immediately after note was triggered
1062     // then immediately apply resonance to Note object
1063 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1064 schoenebeck 3561 pNote->Override.Resonance.Value = fResonance;
1065     pNote->Override.Resonance.Final = isFinal;
1066 schoenebeck 2962 } else { // otherwise schedule resonance change ...
1067     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1068     e.Init(); // clear IDs
1069     e.Type = Event::type_note_synth_param;
1070     e.Param.NoteSynthParam.NoteID = id.noteID();
1071     e.Param.NoteSynthParam.Type = Event::synth_param_resonance;
1072     e.Param.NoteSynthParam.Delta = fResonance;
1073 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1074     isFinal, false, false
1075     );
1076 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1077     }
1078 schoenebeck 2935 }
1079     }
1080    
1081     return successResult();
1082     }
1083 schoenebeck 2953
1084     // change_attack() function
1085 schoenebeck 2935
1086 schoenebeck 2953 InstrumentScriptVMFunction_change_attack::InstrumentScriptVMFunction_change_attack(InstrumentScriptVM* parent)
1087     : m_vm(parent)
1088     {
1089     }
1090    
1091 schoenebeck 3557 bool InstrumentScriptVMFunction_change_attack::acceptsArgType(vmint iArg, ExprType_t type) const {
1092 schoenebeck 2953 if (iArg == 0)
1093     return type == INT_EXPR || type == INT_ARR_EXPR;
1094     else
1095 schoenebeck 3587 return type == INT_EXPR || type == REAL_EXPR;
1096 schoenebeck 2953 }
1097    
1098 schoenebeck 3561 bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1099     if (iArg == 1)
1100     return type == VM_NO_UNIT || type == VM_SECOND;
1101     else
1102     return type == VM_NO_UNIT;
1103     }
1104    
1105 schoenebeck 3564 bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1106 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1107 schoenebeck 3561 }
1108    
1109     bool InstrumentScriptVMFunction_change_attack::acceptsArgFinal(vmint iArg) const {
1110     return iArg == 1;
1111     }
1112    
1113 schoenebeck 3588 void InstrumentScriptVMFunction_change_attack::checkArgs(VMFnArgs* args,
1114     std::function<void(String)> err,
1115     std::function<void(String)> wrn)
1116     {
1117     // super class checks
1118     Super::checkArgs(args, err, wrn);
1119    
1120     // own checks ...
1121     if (args->argsCount() >= 2) {
1122     VMNumberExpr* argTime = args->arg(1)->asNumber();
1123     if (argTime->unitType() && !argTime->isFinal()) {
1124     wrn("Argument 2 implies 'final' value when using seconds as unit for attack time.");
1125     }
1126     }
1127     }
1128    
1129 schoenebeck 2953 VMFnResult* InstrumentScriptVMFunction_change_attack::exec(VMFnArgs* args) {
1130 schoenebeck 3588 const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1131 schoenebeck 3587 vmint attack =
1132     (unit) ?
1133     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1134     args->arg(1)->asNumber()->evalCastInt();
1135 schoenebeck 3588 const bool isFinal =
1136     (unit) ?
1137     true : // imply 'final' value if unit type is used
1138     args->arg(1)->asNumber()->isFinal();
1139 schoenebeck 3303 // note: intentionally not checking against a max. value here!
1140     // (to allow i.e. passing 2000000 for doubling the attack time)
1141     if (attack < 0) {
1142 schoenebeck 2953 wrnMsg("change_attack(): argument 2 may not be negative");
1143     attack = 0;
1144     }
1145 schoenebeck 3588 const float fAttack =
1146     (unit) ? attack : float(attack) / float(VM_EG_PAR_MAX_VALUE);
1147 schoenebeck 2953
1148     AbstractEngineChannel* pEngineChannel =
1149     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1150    
1151     if (args->arg(0)->exprType() == INT_EXPR) {
1152     const ScriptID id = args->arg(0)->asInt()->evalInt();
1153     if (!id) {
1154     wrnMsg("change_attack(): note ID for argument 1 may not be zero");
1155     return successResult();
1156     }
1157     if (!id.isNoteID()) {
1158     wrnMsg("change_attack(): argument 1 is not a note ID");
1159     return successResult();
1160     }
1161    
1162     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1163 schoenebeck 2962 if (!pNote) return successResult();
1164 schoenebeck 2953
1165 schoenebeck 2962 // if change_attack() was called immediately after note was triggered
1166     // then immediately apply attack to Note object
1167 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1168 schoenebeck 3561 pNote->Override.Attack.Value = fAttack;
1169     pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1170 schoenebeck 2962 } else { // otherwise schedule attack change ...
1171     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1172     e.Init(); // clear IDs
1173     e.Type = Event::type_note_synth_param;
1174     e.Param.NoteSynthParam.NoteID = id.noteID();
1175     e.Param.NoteSynthParam.Type = Event::synth_param_attack;
1176     e.Param.NoteSynthParam.Delta = fAttack;
1177 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1178     isFinal, false, unit
1179     );
1180 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1181     }
1182 schoenebeck 2953 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1183     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1184 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1185 schoenebeck 2953 const ScriptID id = ids->evalIntElement(i);
1186     if (!id || !id.isNoteID()) continue;
1187    
1188     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1189     if (!pNote) continue;
1190    
1191 schoenebeck 2962 // if change_attack() was called immediately after note was triggered
1192     // then immediately apply attack to Note object
1193 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1194 schoenebeck 3561 pNote->Override.Attack.Value = fAttack;
1195     pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1196 schoenebeck 2962 } else { // otherwise schedule attack change ...
1197     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1198     e.Init(); // clear IDs
1199     e.Type = Event::type_note_synth_param;
1200     e.Param.NoteSynthParam.NoteID = id.noteID();
1201     e.Param.NoteSynthParam.Type = Event::synth_param_attack;
1202     e.Param.NoteSynthParam.Delta = fAttack;
1203 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1204     isFinal, false, unit
1205     );
1206 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1207     }
1208 schoenebeck 2953 }
1209     }
1210    
1211     return successResult();
1212     }
1213    
1214     // change_decay() function
1215    
1216     InstrumentScriptVMFunction_change_decay::InstrumentScriptVMFunction_change_decay(InstrumentScriptVM* parent)
1217     : m_vm(parent)
1218     {
1219     }
1220    
1221 schoenebeck 3557 bool InstrumentScriptVMFunction_change_decay::acceptsArgType(vmint iArg, ExprType_t type) const {
1222 schoenebeck 2953 if (iArg == 0)
1223     return type == INT_EXPR || type == INT_ARR_EXPR;
1224     else
1225 schoenebeck 3587 return type == INT_EXPR || type == REAL_EXPR;
1226 schoenebeck 2953 }
1227    
1228 schoenebeck 3561 bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1229     if (iArg == 1)
1230     return type == VM_NO_UNIT || type == VM_SECOND;
1231     else
1232     return type == VM_NO_UNIT;
1233     }
1234    
1235 schoenebeck 3564 bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1236 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1237 schoenebeck 3561 }
1238    
1239     bool InstrumentScriptVMFunction_change_decay::acceptsArgFinal(vmint iArg) const {
1240     return iArg == 1;
1241     }
1242    
1243 schoenebeck 3588 void InstrumentScriptVMFunction_change_decay::checkArgs(VMFnArgs* args,
1244     std::function<void(String)> err,
1245     std::function<void(String)> wrn)
1246     {
1247     // super class checks
1248     Super::checkArgs(args, err, wrn);
1249    
1250     // own checks ...
1251     if (args->argsCount() >= 2) {
1252     VMNumberExpr* argTime = args->arg(1)->asNumber();
1253     if (argTime->unitType() && !argTime->isFinal()) {
1254     wrn("Argument 2 implies 'final' value when using seconds as unit for decay time.");
1255     }
1256     }
1257     }
1258    
1259 schoenebeck 2953 VMFnResult* InstrumentScriptVMFunction_change_decay::exec(VMFnArgs* args) {
1260 schoenebeck 3588 const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1261 schoenebeck 3587 vmint decay =
1262     (unit) ?
1263     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1264     args->arg(1)->asNumber()->evalCastInt();
1265 schoenebeck 3588 const bool isFinal =
1266     (unit) ?
1267     true : // imply 'final' value if unit type is used
1268     args->arg(1)->asNumber()->isFinal();
1269 schoenebeck 3303 // note: intentionally not checking against a max. value here!
1270     // (to allow i.e. passing 2000000 for doubling the decay time)
1271     if (decay < 0) {
1272 schoenebeck 2953 wrnMsg("change_decay(): argument 2 may not be negative");
1273     decay = 0;
1274     }
1275 schoenebeck 3588 const float fDecay =
1276     (unit) ? decay : float(decay) / float(VM_EG_PAR_MAX_VALUE);
1277 schoenebeck 2953
1278     AbstractEngineChannel* pEngineChannel =
1279     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1280    
1281     if (args->arg(0)->exprType() == INT_EXPR) {
1282     const ScriptID id = args->arg(0)->asInt()->evalInt();
1283     if (!id) {
1284     wrnMsg("change_decay(): note ID for argument 1 may not be zero");
1285     return successResult();
1286     }
1287     if (!id.isNoteID()) {
1288     wrnMsg("change_decay(): argument 1 is not a note ID");
1289     return successResult();
1290     }
1291    
1292     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1293 schoenebeck 2962 if (!pNote) return successResult();
1294 schoenebeck 2953
1295 schoenebeck 2962 // if change_decay() was called immediately after note was triggered
1296     // then immediately apply decay to Note object
1297 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1298 schoenebeck 3561 pNote->Override.Decay.Value = fDecay;
1299     pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1300 schoenebeck 2962 } else { // otherwise schedule decay change ...
1301     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1302     e.Init(); // clear IDs
1303     e.Type = Event::type_note_synth_param;
1304     e.Param.NoteSynthParam.NoteID = id.noteID();
1305     e.Param.NoteSynthParam.Type = Event::synth_param_decay;
1306     e.Param.NoteSynthParam.Delta = fDecay;
1307 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1308     isFinal, false, unit
1309     );
1310 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1311     }
1312 schoenebeck 2953 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1313     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1314 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1315 schoenebeck 2953 const ScriptID id = ids->evalIntElement(i);
1316     if (!id || !id.isNoteID()) continue;
1317    
1318     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1319     if (!pNote) continue;
1320    
1321 schoenebeck 2962 // if change_decay() was called immediately after note was triggered
1322     // then immediately apply decay to Note object
1323 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1324 schoenebeck 3561 pNote->Override.Decay.Value = fDecay;
1325     pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1326 schoenebeck 2962 } else { // otherwise schedule decay change ...
1327     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1328     e.Init(); // clear IDs
1329     e.Type = Event::type_note_synth_param;
1330     e.Param.NoteSynthParam.NoteID = id.noteID();
1331     e.Param.NoteSynthParam.Type = Event::synth_param_decay;
1332     e.Param.NoteSynthParam.Delta = fDecay;
1333 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1334     isFinal, false, unit
1335     );
1336 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1337     }
1338 schoenebeck 2953 }
1339     }
1340    
1341     return successResult();
1342     }
1343    
1344     // change_release() function
1345    
1346     InstrumentScriptVMFunction_change_release::InstrumentScriptVMFunction_change_release(InstrumentScriptVM* parent)
1347     : m_vm(parent)
1348     {
1349     }
1350    
1351 schoenebeck 3557 bool InstrumentScriptVMFunction_change_release::acceptsArgType(vmint iArg, ExprType_t type) const {
1352 schoenebeck 2953 if (iArg == 0)
1353     return type == INT_EXPR || type == INT_ARR_EXPR;
1354     else
1355 schoenebeck 3587 return type == INT_EXPR || type == REAL_EXPR;
1356 schoenebeck 2953 }
1357    
1358 schoenebeck 3561 bool InstrumentScriptVMFunction_change_release::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1359     if (iArg == 1)
1360     return type == VM_NO_UNIT || type == VM_SECOND;
1361     else
1362     return type == VM_NO_UNIT;
1363     }
1364    
1365 schoenebeck 3564 bool InstrumentScriptVMFunction_change_release::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1366 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1367 schoenebeck 3561 }
1368    
1369     bool InstrumentScriptVMFunction_change_release::acceptsArgFinal(vmint iArg) const {
1370     return iArg == 1;
1371     }
1372    
1373 schoenebeck 3588 void InstrumentScriptVMFunction_change_release::checkArgs(VMFnArgs* args,
1374     std::function<void(String)> err,
1375     std::function<void(String)> wrn)
1376     {
1377     // super class checks
1378     Super::checkArgs(args, err, wrn);
1379    
1380     // own checks ...
1381     if (args->argsCount() >= 2) {
1382     VMNumberExpr* argTime = args->arg(1)->asNumber();
1383     if (argTime->unitType() && !argTime->isFinal()) {
1384     wrn("Argument 2 implies 'final' value when using seconds as unit for release time.");
1385     }
1386     }
1387     }
1388    
1389 schoenebeck 2953 VMFnResult* InstrumentScriptVMFunction_change_release::exec(VMFnArgs* args) {
1390 schoenebeck 3588 const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1391 schoenebeck 3587 vmint release =
1392     (unit) ?
1393     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1394     args->arg(1)->asNumber()->evalCastInt();
1395 schoenebeck 3588 const bool isFinal =
1396     (unit) ?
1397     true : // imply 'final' value if unit type is used
1398     args->arg(1)->asNumber()->isFinal();
1399 schoenebeck 3303 // note: intentionally not checking against a max. value here!
1400     // (to allow i.e. passing 2000000 for doubling the release time)
1401     if (release < 0) {
1402 schoenebeck 2953 wrnMsg("change_release(): argument 2 may not be negative");
1403     release = 0;
1404     }
1405 schoenebeck 3588 const float fRelease =
1406     (unit) ? release : float(release) / float(VM_EG_PAR_MAX_VALUE);
1407 schoenebeck 2953
1408     AbstractEngineChannel* pEngineChannel =
1409     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1410    
1411     if (args->arg(0)->exprType() == INT_EXPR) {
1412     const ScriptID id = args->arg(0)->asInt()->evalInt();
1413     if (!id) {
1414     wrnMsg("change_release(): note ID for argument 1 may not be zero");
1415     return successResult();
1416     }
1417     if (!id.isNoteID()) {
1418     wrnMsg("change_release(): argument 1 is not a note ID");
1419     return successResult();
1420     }
1421    
1422     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1423 schoenebeck 2962 if (!pNote) return successResult();
1424 schoenebeck 2953
1425 schoenebeck 2962 // if change_release() was called immediately after note was triggered
1426     // then immediately apply relase to Note object
1427 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1428 schoenebeck 3561 pNote->Override.Release.Value = fRelease;
1429     pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1430 schoenebeck 2962 } else { // otherwise schedule release change ...
1431     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1432     e.Init(); // clear IDs
1433     e.Type = Event::type_note_synth_param;
1434     e.Param.NoteSynthParam.NoteID = id.noteID();
1435     e.Param.NoteSynthParam.Type = Event::synth_param_release;
1436     e.Param.NoteSynthParam.Delta = fRelease;
1437 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1438     isFinal, false, unit
1439     );
1440 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1441     }
1442 schoenebeck 2953 } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1443     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1444 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1445 schoenebeck 2953 const ScriptID id = ids->evalIntElement(i);
1446     if (!id || !id.isNoteID()) continue;
1447    
1448     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1449     if (!pNote) continue;
1450    
1451 schoenebeck 2962 // if change_release() was called immediately after note was triggered
1452     // then immediately apply relase to Note object
1453 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1454 schoenebeck 3561 pNote->Override.Release.Value = fRelease;
1455     pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1456 schoenebeck 2962 } else { // otherwise schedule release change ...
1457     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1458     e.Init(); // clear IDs
1459     e.Type = Event::type_note_synth_param;
1460     e.Param.NoteSynthParam.NoteID = id.noteID();
1461     e.Param.NoteSynthParam.Type = Event::synth_param_release;
1462     e.Param.NoteSynthParam.Delta = fRelease;
1463 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1464     isFinal, false, unit
1465     );
1466 schoenebeck 2962 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1467     }
1468 schoenebeck 2953 }
1469     }
1470    
1471     return successResult();
1472     }
1473    
1474 schoenebeck 3246 // template for change_*() functions
1475    
1476 schoenebeck 3557 bool VMChangeSynthParamFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1477 schoenebeck 3118 if (iArg == 0)
1478     return type == INT_EXPR || type == INT_ARR_EXPR;
1479     else
1480 schoenebeck 3587 return type == INT_EXPR || (m_acceptReal && type == REAL_EXPR);
1481 schoenebeck 3118 }
1482    
1483 schoenebeck 3561 bool VMChangeSynthParamFunction::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1484     if (iArg == 1)
1485     return type == VM_NO_UNIT || type == m_unit;
1486     else
1487     return type == VM_NO_UNIT;
1488     }
1489    
1490 schoenebeck 3564 bool VMChangeSynthParamFunction::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1491 schoenebeck 3587 return m_acceptUnitPrefix && iArg == 1 && type == m_unit; // only allow metric prefix(es) if approprirate unit type is used (e.g. Hz)
1492 schoenebeck 3561 }
1493    
1494     bool VMChangeSynthParamFunction::acceptsArgFinal(vmint iArg) const {
1495     return (m_acceptFinal) ? (iArg == 1) : false;
1496     }
1497    
1498     inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Param& param, const bool bFinal, const StdUnit_t unit) {
1499     param.Scope = NoteBase::scopeBy_FinalUnit(bFinal, unit);
1500     }
1501    
1502     inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Norm& param, const bool bFinal, const StdUnit_t unit) {
1503     param.Final = bFinal;
1504     }
1505    
1506     inline static void setNoteParamScopeBy_FinalUnit(float& param, const bool bFinal, const StdUnit_t unit) {
1507     /* NOOP */
1508     }
1509    
1510     template<class T>
1511     inline static void setNoteParamValue(T& param, vmfloat value) {
1512     param.Value = value;
1513     }
1514    
1515     inline static void setNoteParamValue(float& param, vmfloat value) {
1516     param = value;
1517     }
1518    
1519 schoenebeck 3588 void VMChangeSynthParamFunction::checkArgs(VMFnArgs* args,
1520     std::function<void(String)> err,
1521     std::function<void(String)> wrn)
1522     {
1523     // super class checks
1524     Super::checkArgs(args, err, wrn);
1525    
1526     // own checks ...
1527     if (m_unit && m_unit != VM_BEL && args->argsCount() >= 2) {
1528     VMNumberExpr* arg = args->arg(1)->asNumber();
1529     if (arg && arg->unitType() && !arg->isFinal()) {
1530     wrn("Argument 2 implies 'final' value when unit type " +
1531     unitTypeStr(arg->unitType()) + " is used.");
1532     }
1533     }
1534     }
1535    
1536 schoenebeck 3193 // Arbitrarily chosen constant value symbolizing "no limit".
1537     #define NO_LIMIT 1315916909
1538    
1539 schoenebeck 3561 template<class T_NoteParamType, T_NoteParamType NoteBase::_Override::*T_noteParam,
1540     vmint T_synthParam,
1541     vmint T_minValueNorm, vmint T_maxValueNorm, bool T_normalizeNorm,
1542     vmint T_minValueUnit, vmint T_maxValueUnit,
1543     MetricPrefix_t T_unitPrefix0, MetricPrefix_t ... T_unitPrefixN>
1544     VMFnResult* VMChangeSynthParamFunction::execTemplate(VMFnArgs* args, const char* functionName)
1545     {
1546 schoenebeck 3587 const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1547 schoenebeck 3588 const bool isFinal =
1548     (m_unit && m_unit != VM_BEL && unit) ?
1549     true : // imply 'final' value if unit type is used (except of 'Bel' which may be relative)
1550     args->arg(1)->asNumber()->isFinal();
1551 schoenebeck 3561 vmint value =
1552 schoenebeck 3587 (m_acceptUnitPrefix && ((m_unit && unit) || (!m_unit && args->arg(1)->asNumber()->hasUnitFactorNow())))
1553     ? args->arg(1)->asNumber()->evalCastInt(T_unitPrefix0, T_unitPrefixN ...)
1554     : args->arg(1)->asNumber()->evalCastInt();
1555 schoenebeck 3561
1556     // check if passed value is in allowed range
1557     if (unit && m_unit) {
1558     if (T_maxValueUnit != NO_LIMIT && value > T_maxValueUnit) {
1559     wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueUnit));
1560     value = T_maxValueUnit;
1561     } else if (T_minValueUnit != NO_LIMIT && value < T_minValueUnit) {
1562     if (T_minValueUnit == 0)
1563     wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1564     else
1565     wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueUnit));
1566     value = T_minValueUnit;
1567     }
1568     } else { // value was passed to this function without a unit ...
1569     if (T_maxValueNorm != NO_LIMIT && value > T_maxValueNorm) {
1570     wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueNorm));
1571     value = T_maxValueNorm;
1572     } else if (T_minValueNorm != NO_LIMIT && value < T_minValueNorm) {
1573     if (T_minValueNorm == 0)
1574     wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1575     else
1576     wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueNorm));
1577     value = T_minValueNorm;
1578     }
1579     }
1580    
1581     // convert passed argument value to engine internal expected value range (i.e. 0.0 .. 1.0)
1582     const float fValue =
1583     (unit && m_unit) ?
1584     (unit == VM_BEL) ?
1585     RTMath::DecibelToLinRatio(float(value) * float(T_unitPrefix0) /*i.e. mdB -> dB*/) :
1586     float(value) * VMUnit::unitFactor(T_unitPrefix0, T_unitPrefixN ...) /*i.e. us -> s*/ :
1587     (T_normalizeNorm) ?
1588     float(value) / ((T_maxValueNorm != NO_LIMIT) ? float(T_maxValueNorm) : 1000000.f/* fallback: value range used for most */) :
1589     float(value) /* as is */;
1590    
1591 schoenebeck 3118 AbstractEngineChannel* pEngineChannel =
1592     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1593    
1594     if (args->arg(0)->exprType() == INT_EXPR) {
1595     const ScriptID id = args->arg(0)->asInt()->evalInt();
1596     if (!id) {
1597     wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1598     return successResult();
1599     }
1600     if (!id.isNoteID()) {
1601     wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1602     return successResult();
1603     }
1604    
1605     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1606     if (!pNote) return successResult();
1607    
1608     // if this change_*() script function was called immediately after
1609     // note was triggered then immediately apply the synth parameter
1610     // change to Note object
1611 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1612 schoenebeck 3561 setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1613     setNoteParamScopeBy_FinalUnit(
1614     (pNote->Override.*T_noteParam),
1615     isFinal, unit
1616     );
1617 schoenebeck 3118 } else { // otherwise schedule this synth parameter change ...
1618     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1619     e.Init(); // clear IDs
1620     e.Type = Event::type_note_synth_param;
1621     e.Param.NoteSynthParam.NoteID = id.noteID();
1622     e.Param.NoteSynthParam.Type = (Event::synth_param_t) T_synthParam;
1623     e.Param.NoteSynthParam.Delta = fValue;
1624 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1625     isFinal, false, unit
1626     );
1627 schoenebeck 3118 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1628     }
1629     } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1630     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1631 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1632 schoenebeck 3118 const ScriptID id = ids->evalIntElement(i);
1633     if (!id || !id.isNoteID()) continue;
1634    
1635     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1636     if (!pNote) continue;
1637    
1638     // if this change_*() script function was called immediately after
1639     // note was triggered then immediately apply the synth parameter
1640     // change to Note object
1641 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1642 schoenebeck 3561 setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1643     setNoteParamScopeBy_FinalUnit(
1644     (pNote->Override.*T_noteParam),
1645     isFinal, unit
1646     );
1647 schoenebeck 3118 } else { // otherwise schedule this synth parameter change ...
1648     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1649     e.Init(); // clear IDs
1650     e.Type = Event::type_note_synth_param;
1651     e.Param.NoteSynthParam.NoteID = id.noteID();
1652     e.Param.NoteSynthParam.Type = (Event::synth_param_t) T_synthParam;
1653     e.Param.NoteSynthParam.Delta = fValue;
1654 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1655     isFinal, false, unit
1656     );
1657 schoenebeck 3118 pEngineChannel->ScheduleEventMicroSec(&e, 0);
1658     }
1659     }
1660     }
1661    
1662     return successResult();
1663     }
1664    
1665 schoenebeck 3316 // change_sustain() function
1666    
1667     VMFnResult* InstrumentScriptVMFunction_change_sustain::exec(VMFnArgs* args) {
1668     return VMChangeSynthParamFunction::execTemplate<
1669 schoenebeck 3561 decltype(NoteBase::_Override::Sustain),
1670 schoenebeck 3360 &NoteBase::_Override::Sustain,
1671     Event::synth_param_sustain,
1672 schoenebeck 3561 /* if value passed without unit */
1673     0, NO_LIMIT, true,
1674     /* if value passed WITH 'Bel' unit */
1675     NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_sustain" );
1676 schoenebeck 3316 }
1677    
1678 schoenebeck 3360 // change_cutoff_attack() function
1679    
1680     VMFnResult* InstrumentScriptVMFunction_change_cutoff_attack::exec(VMFnArgs* args) {
1681     return VMChangeSynthParamFunction::execTemplate<
1682 schoenebeck 3561 decltype(NoteBase::_Override::CutoffAttack),
1683 schoenebeck 3360 &NoteBase::_Override::CutoffAttack,
1684     Event::synth_param_cutoff_attack,
1685 schoenebeck 3561 /* if value passed without unit */
1686     0, NO_LIMIT, true,
1687     /* if value passed with 'seconds' unit */
1688     0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_attack" );
1689 schoenebeck 3360 }
1690    
1691     // change_cutoff_decay() function
1692    
1693     VMFnResult* InstrumentScriptVMFunction_change_cutoff_decay::exec(VMFnArgs* args) {
1694     return VMChangeSynthParamFunction::execTemplate<
1695 schoenebeck 3561 decltype(NoteBase::_Override::CutoffDecay),
1696 schoenebeck 3360 &NoteBase::_Override::CutoffDecay,
1697     Event::synth_param_cutoff_decay,
1698 schoenebeck 3561 /* if value passed without unit */
1699     0, NO_LIMIT, true,
1700     /* if value passed with 'seconds' unit */
1701     0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_decay" );
1702 schoenebeck 3360 }
1703    
1704     // change_cutoff_sustain() function
1705    
1706     VMFnResult* InstrumentScriptVMFunction_change_cutoff_sustain::exec(VMFnArgs* args) {
1707     return VMChangeSynthParamFunction::execTemplate<
1708 schoenebeck 3561 decltype(NoteBase::_Override::CutoffSustain),
1709 schoenebeck 3360 &NoteBase::_Override::CutoffSustain,
1710     Event::synth_param_cutoff_sustain,
1711 schoenebeck 3561 /* if value passed without unit */
1712     0, NO_LIMIT, true,
1713     /* if value passed WITH 'Bel' unit */
1714     NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_cutoff_sustain" );
1715 schoenebeck 3360 }
1716    
1717     // change_cutoff_release() function
1718    
1719     VMFnResult* InstrumentScriptVMFunction_change_cutoff_release::exec(VMFnArgs* args) {
1720     return VMChangeSynthParamFunction::execTemplate<
1721 schoenebeck 3561 decltype(NoteBase::_Override::CutoffRelease),
1722 schoenebeck 3360 &NoteBase::_Override::CutoffRelease,
1723     Event::synth_param_cutoff_release,
1724 schoenebeck 3561 /* if value passed without unit */
1725     0, NO_LIMIT, true,
1726     /* if value passed with 'seconds' unit */
1727     0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_release" );
1728 schoenebeck 3360 }
1729    
1730 schoenebeck 3118 // change_amp_lfo_depth() function
1731    
1732     VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_depth::exec(VMFnArgs* args) {
1733 schoenebeck 3188 return VMChangeSynthParamFunction::execTemplate<
1734 schoenebeck 3561 decltype(NoteBase::_Override::AmpLFODepth),
1735 schoenebeck 3188 &NoteBase::_Override::AmpLFODepth,
1736 schoenebeck 3193 Event::synth_param_amp_lfo_depth,
1737 schoenebeck 3561 /* if value passed without unit */
1738     0, 1000000, true,
1739     /* not used (since this function does not accept unit) */
1740     NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_amp_lfo_depth" );
1741 schoenebeck 3118 }
1742    
1743     // change_amp_lfo_freq() function
1744    
1745     VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_freq::exec(VMFnArgs* args) {
1746 schoenebeck 3188 return VMChangeSynthParamFunction::execTemplate<
1747 schoenebeck 3561 decltype(NoteBase::_Override::AmpLFOFreq),
1748 schoenebeck 3188 &NoteBase::_Override::AmpLFOFreq,
1749 schoenebeck 3193 Event::synth_param_amp_lfo_freq,
1750 schoenebeck 3561 /* if value passed without unit */
1751     0, 1000000, true,
1752     /* if value passed with 'Hz' unit */
1753     0, 30000, VM_NO_PREFIX>( args, "change_amp_lfo_freq" );
1754 schoenebeck 3118 }
1755    
1756 schoenebeck 3360 // change_cutoff_lfo_depth() function
1757    
1758     VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_depth::exec(VMFnArgs* args) {
1759     return VMChangeSynthParamFunction::execTemplate<
1760 schoenebeck 3561 decltype(NoteBase::_Override::CutoffLFODepth),
1761 schoenebeck 3360 &NoteBase::_Override::CutoffLFODepth,
1762     Event::synth_param_cutoff_lfo_depth,
1763 schoenebeck 3561 /* if value passed without unit */
1764     0, 1000000, true,
1765     /* not used (since this function does not accept unit) */
1766     NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_cutoff_lfo_depth" );
1767 schoenebeck 3360 }
1768    
1769     // change_cutoff_lfo_freq() function
1770    
1771     VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_freq::exec(VMFnArgs* args) {
1772     return VMChangeSynthParamFunction::execTemplate<
1773 schoenebeck 3561 decltype(NoteBase::_Override::CutoffLFOFreq),
1774 schoenebeck 3360 &NoteBase::_Override::CutoffLFOFreq,
1775     Event::synth_param_cutoff_lfo_freq,
1776 schoenebeck 3561 /* if value passed without unit */
1777     0, 1000000, true,
1778     /* if value passed with 'Hz' unit */
1779     0, 30000, VM_NO_PREFIX>( args, "change_cutoff_lfo_freq" );
1780 schoenebeck 3360 }
1781    
1782 schoenebeck 3118 // change_pitch_lfo_depth() function
1783    
1784     VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_depth::exec(VMFnArgs* args) {
1785 schoenebeck 3188 return VMChangeSynthParamFunction::execTemplate<
1786 schoenebeck 3561 decltype(NoteBase::_Override::PitchLFODepth),
1787 schoenebeck 3188 &NoteBase::_Override::PitchLFODepth,
1788 schoenebeck 3193 Event::synth_param_pitch_lfo_depth,
1789 schoenebeck 3561 /* if value passed without unit */
1790     0, 1000000, true,
1791     /* not used (since this function does not accept unit) */
1792     NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_pitch_lfo_depth" );
1793 schoenebeck 3118 }
1794    
1795     // change_pitch_lfo_freq() function
1796    
1797     VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_freq::exec(VMFnArgs* args) {
1798 schoenebeck 3188 return VMChangeSynthParamFunction::execTemplate<
1799 schoenebeck 3561 decltype(NoteBase::_Override::PitchLFOFreq),
1800 schoenebeck 3188 &NoteBase::_Override::PitchLFOFreq,
1801 schoenebeck 3193 Event::synth_param_pitch_lfo_freq,
1802 schoenebeck 3561 /* if value passed without unit */
1803     0, 1000000, true,
1804     /* if value passed with 'Hz' unit */
1805     0, 30000, VM_NO_PREFIX>( args, "change_pitch_lfo_freq" );
1806 schoenebeck 3118 }
1807    
1808 schoenebeck 3188 // change_vol_time() function
1809    
1810     VMFnResult* InstrumentScriptVMFunction_change_vol_time::exec(VMFnArgs* args) {
1811     return VMChangeSynthParamFunction::execTemplate<
1812 schoenebeck 3561 decltype(NoteBase::_Override::VolumeTime),
1813 schoenebeck 3188 &NoteBase::_Override::VolumeTime,
1814 schoenebeck 3193 Event::synth_param_volume_time,
1815 schoenebeck 3561 /* if value passed without unit (implying 'us' unit) */
1816     0, NO_LIMIT, true,
1817     /* if value passed with 'seconds' unit */
1818     0, NO_LIMIT, VM_MICRO>( args, "change_vol_time" );
1819 schoenebeck 3188 }
1820    
1821     // change_tune_time() function
1822    
1823     VMFnResult* InstrumentScriptVMFunction_change_tune_time::exec(VMFnArgs* args) {
1824     return VMChangeSynthParamFunction::execTemplate<
1825 schoenebeck 3561 decltype(NoteBase::_Override::PitchTime),
1826 schoenebeck 3188 &NoteBase::_Override::PitchTime,
1827 schoenebeck 3193 Event::synth_param_pitch_time,
1828 schoenebeck 3561 /* if value passed without unit (implying 'us' unit) */
1829     0, NO_LIMIT, true,
1830     /* if value passed with 'seconds' unit */
1831     0, NO_LIMIT, VM_MICRO>( args, "change_tune_time" );
1832 schoenebeck 3188 }
1833    
1834 schoenebeck 3335 // change_pan_time() function
1835    
1836     VMFnResult* InstrumentScriptVMFunction_change_pan_time::exec(VMFnArgs* args) {
1837     return VMChangeSynthParamFunction::execTemplate<
1838 schoenebeck 3561 decltype(NoteBase::_Override::PanTime),
1839     &NoteBase::_Override::PanTime,
1840     Event::synth_param_pan_time,
1841     /* if value passed without unit (implying 'us' unit) */
1842     0, NO_LIMIT, true,
1843     /* if value passed with 'seconds' unit */
1844     0, NO_LIMIT, VM_MICRO>( args, "change_pan_time" );
1845 schoenebeck 3335 }
1846    
1847 schoenebeck 3246 // template for change_*_curve() functions
1848    
1849 schoenebeck 3557 bool VMChangeFadeCurveFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1850 schoenebeck 3246 if (iArg == 0)
1851     return type == INT_EXPR || type == INT_ARR_EXPR;
1852     else
1853     return type == INT_EXPR;
1854     }
1855    
1856 schoenebeck 3557 template<fade_curve_t NoteBase::_Override::*T_noteParam, vmint T_synthParam>
1857 schoenebeck 3246 VMFnResult* VMChangeFadeCurveFunction::execTemplate(VMFnArgs* args, const char* functionName) {
1858 schoenebeck 3557 vmint value = args->arg(1)->asInt()->evalInt();
1859 schoenebeck 3246 switch (value) {
1860     case FADE_CURVE_LINEAR:
1861     case FADE_CURVE_EASE_IN_EASE_OUT:
1862     break;
1863     default:
1864     wrnMsg(String(functionName) + "(): invalid curve type passed as argument 2");
1865     return successResult();
1866     }
1867    
1868     AbstractEngineChannel* pEngineChannel =
1869     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1870    
1871     if (args->arg(0)->exprType() == INT_EXPR) {
1872     const ScriptID id = args->arg(0)->asInt()->evalInt();
1873     if (!id) {
1874     wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1875     return successResult();
1876     }
1877     if (!id.isNoteID()) {
1878     wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1879     return successResult();
1880     }
1881    
1882     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1883     if (!pNote) return successResult();
1884    
1885     // if this change_*_curve() script function was called immediately after
1886     // note was triggered then immediately apply the synth parameter
1887     // change to Note object
1888     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1889     pNote->Override.*T_noteParam = (fade_curve_t) value;
1890     } else { // otherwise schedule this synth parameter change ...
1891     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1892     e.Init(); // clear IDs
1893     e.Type = Event::type_note_synth_param;
1894     e.Param.NoteSynthParam.NoteID = id.noteID();
1895     e.Param.NoteSynthParam.Type = (Event::synth_param_t) T_synthParam;
1896     e.Param.NoteSynthParam.Delta = value;
1897 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1898 schoenebeck 3246
1899     pEngineChannel->ScheduleEventMicroSec(&e, 0);
1900     }
1901     } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1902     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1903 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
1904 schoenebeck 3246 const ScriptID id = ids->evalIntElement(i);
1905     if (!id || !id.isNoteID()) continue;
1906    
1907     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1908     if (!pNote) continue;
1909    
1910     // if this change_*_curve() script function was called immediately after
1911     // note was triggered then immediately apply the synth parameter
1912     // change to Note object
1913     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1914     pNote->Override.*T_noteParam = (fade_curve_t) value;
1915     } else { // otherwise schedule this synth parameter change ...
1916     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1917     e.Init(); // clear IDs
1918     e.Type = Event::type_note_synth_param;
1919     e.Param.NoteSynthParam.NoteID = id.noteID();
1920     e.Param.NoteSynthParam.Type = (Event::synth_param_t) T_synthParam;
1921     e.Param.NoteSynthParam.Delta = value;
1922 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1923 schoenebeck 3246
1924     pEngineChannel->ScheduleEventMicroSec(&e, 0);
1925     }
1926     }
1927     }
1928    
1929     return successResult();
1930     }
1931    
1932     // change_vol_curve() function
1933    
1934     VMFnResult* InstrumentScriptVMFunction_change_vol_curve::exec(VMFnArgs* args) {
1935     return VMChangeFadeCurveFunction::execTemplate<
1936     &NoteBase::_Override::VolumeCurve,
1937     Event::synth_param_volume_curve>( args, "change_vol_curve" );
1938     }
1939    
1940     // change_tune_curve() function
1941    
1942     VMFnResult* InstrumentScriptVMFunction_change_tune_curve::exec(VMFnArgs* args) {
1943     return VMChangeFadeCurveFunction::execTemplate<
1944     &NoteBase::_Override::PitchCurve,
1945     Event::synth_param_pitch_curve>( args, "change_tune_curve" );
1946     }
1947    
1948 schoenebeck 3335 // change_pan_curve() function
1949    
1950     VMFnResult* InstrumentScriptVMFunction_change_pan_curve::exec(VMFnArgs* args) {
1951     return VMChangeFadeCurveFunction::execTemplate<
1952     &NoteBase::_Override::PanCurve,
1953     Event::synth_param_pan_curve>( args, "change_pan_curve" );
1954     }
1955    
1956 schoenebeck 3188 // fade_in() function
1957    
1958     InstrumentScriptVMFunction_fade_in::InstrumentScriptVMFunction_fade_in(InstrumentScriptVM* parent)
1959     : m_vm(parent)
1960     {
1961     }
1962    
1963 schoenebeck 3557 bool InstrumentScriptVMFunction_fade_in::acceptsArgType(vmint iArg, ExprType_t type) const {
1964 schoenebeck 3188 if (iArg == 0)
1965     return type == INT_EXPR || type == INT_ARR_EXPR;
1966     else
1967 schoenebeck 3587 return type == INT_EXPR || type == REAL_EXPR;
1968 schoenebeck 3188 }
1969    
1970 schoenebeck 3561 bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1971     if (iArg == 1)
1972     return type == VM_NO_UNIT || type == VM_SECOND;
1973     else
1974     return type == VM_NO_UNIT;
1975     }
1976    
1977 schoenebeck 3564 bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1978 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1979 schoenebeck 3561 }
1980    
1981 schoenebeck 3188 VMFnResult* InstrumentScriptVMFunction_fade_in::exec(VMFnArgs* args) {
1982 schoenebeck 3587 StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1983     vmint duration =
1984     (unit) ?
1985     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1986     args->arg(1)->asNumber()->evalCastInt();
1987 schoenebeck 3188 if (duration < 0) {
1988     wrnMsg("fade_in(): argument 2 may not be negative");
1989     duration = 0;
1990     }
1991     const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
1992    
1993     AbstractEngineChannel* pEngineChannel =
1994     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1995    
1996     if (args->arg(0)->exprType() == INT_EXPR) {
1997     const ScriptID id = args->arg(0)->asInt()->evalInt();
1998     if (!id) {
1999     wrnMsg("fade_in(): note ID for argument 1 may not be zero");
2000     return successResult();
2001     }
2002     if (!id.isNoteID()) {
2003     wrnMsg("fade_in(): argument 1 is not a note ID");
2004     return successResult();
2005     }
2006    
2007     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2008     if (!pNote) return successResult();
2009    
2010     // if fade_in() was called immediately after note was triggered
2011     // then immediately apply a start volume of zero to Note object,
2012     // as well as the fade in duration
2013 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2014 schoenebeck 3561 pNote->Override.Volume.Value = 0.f;
2015 schoenebeck 3188 pNote->Override.VolumeTime = fDuration;
2016     } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2017     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2018     e.Init(); // clear IDs
2019     e.Type = Event::type_note_synth_param;
2020     e.Param.NoteSynthParam.NoteID = id.noteID();
2021     e.Param.NoteSynthParam.Type = Event::synth_param_volume_time;
2022     e.Param.NoteSynthParam.Delta = fDuration;
2023 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2024 schoenebeck 3188
2025     pEngineChannel->ScheduleEventMicroSec(&e, 0);
2026     }
2027     // and finally schedule a "volume" change, simply one time slice
2028     // ahead, with the final fade in volume (1.0)
2029     {
2030     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2031     e.Init(); // clear IDs
2032     e.Type = Event::type_note_synth_param;
2033     e.Param.NoteSynthParam.NoteID = id.noteID();
2034     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
2035     e.Param.NoteSynthParam.Delta = 1.f;
2036 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2037 schoenebeck 3188
2038     // scheduling with 0 delay would also work here, but +1 is more
2039     // safe regarding potential future implementation changes of the
2040     // scheduler (see API comments of RTAVLTree::insert())
2041     pEngineChannel->ScheduleEventMicroSec(&e, 1);
2042     }
2043     } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2044     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2045 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
2046 schoenebeck 3188 const ScriptID id = ids->evalIntElement(i);
2047     if (!id || !id.isNoteID()) continue;
2048    
2049     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2050     if (!pNote) continue;
2051    
2052     // if fade_in() was called immediately after note was triggered
2053     // then immediately apply a start volume of zero to Note object,
2054     // as well as the fade in duration
2055 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2056 schoenebeck 3561 pNote->Override.Volume.Value = 0.f;
2057 schoenebeck 3188 pNote->Override.VolumeTime = fDuration;
2058     } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2059     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2060     e.Init(); // clear IDs
2061     e.Type = Event::type_note_synth_param;
2062     e.Param.NoteSynthParam.NoteID = id.noteID();
2063     e.Param.NoteSynthParam.Type = Event::synth_param_volume_time;
2064     e.Param.NoteSynthParam.Delta = fDuration;
2065 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2066 schoenebeck 3188
2067     pEngineChannel->ScheduleEventMicroSec(&e, 0);
2068     }
2069     // and finally schedule a "volume" change, simply one time slice
2070     // ahead, with the final fade in volume (1.0)
2071     {
2072     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2073     e.Init(); // clear IDs
2074     e.Type = Event::type_note_synth_param;
2075     e.Param.NoteSynthParam.NoteID = id.noteID();
2076     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
2077     e.Param.NoteSynthParam.Delta = 1.f;
2078 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2079 schoenebeck 3188
2080     // scheduling with 0 delay would also work here, but +1 is more
2081     // safe regarding potential future implementation changes of the
2082     // scheduler (see API comments of RTAVLTree::insert())
2083     pEngineChannel->ScheduleEventMicroSec(&e, 1);
2084     }
2085     }
2086     }
2087    
2088     return successResult();
2089     }
2090    
2091     // fade_out() function
2092    
2093     InstrumentScriptVMFunction_fade_out::InstrumentScriptVMFunction_fade_out(InstrumentScriptVM* parent)
2094     : m_vm(parent)
2095     {
2096     }
2097    
2098 schoenebeck 3557 bool InstrumentScriptVMFunction_fade_out::acceptsArgType(vmint iArg, ExprType_t type) const {
2099 schoenebeck 3188 if (iArg == 0)
2100     return type == INT_EXPR || type == INT_ARR_EXPR;
2101     else
2102 schoenebeck 3587 return type == INT_EXPR || type == REAL_EXPR;
2103 schoenebeck 3188 }
2104    
2105 schoenebeck 3561 bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2106     if (iArg == 1)
2107     return type == VM_NO_UNIT || type == VM_SECOND;
2108     else
2109     return type == VM_NO_UNIT;
2110     }
2111    
2112 schoenebeck 3564 bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2113 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2114 schoenebeck 3561 }
2115    
2116 schoenebeck 3188 VMFnResult* InstrumentScriptVMFunction_fade_out::exec(VMFnArgs* args) {
2117 schoenebeck 3587 StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2118     vmint duration =
2119     (unit) ?
2120     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2121     args->arg(1)->asNumber()->evalCastInt();
2122 schoenebeck 3188 if (duration < 0) {
2123     wrnMsg("fade_out(): argument 2 may not be negative");
2124     duration = 0;
2125     }
2126     const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2127    
2128     bool stop = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : true;
2129    
2130     AbstractEngineChannel* pEngineChannel =
2131     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2132    
2133     if (args->arg(0)->exprType() == INT_EXPR) {
2134     const ScriptID id = args->arg(0)->asInt()->evalInt();
2135     if (!id) {
2136     wrnMsg("fade_out(): note ID for argument 1 may not be zero");
2137     return successResult();
2138     }
2139     if (!id.isNoteID()) {
2140     wrnMsg("fade_out(): argument 1 is not a note ID");
2141     return successResult();
2142     }
2143    
2144     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2145     if (!pNote) return successResult();
2146    
2147     // if fade_out() was called immediately after note was triggered
2148     // then immediately apply fade out duration to Note object
2149 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2150 schoenebeck 3188 pNote->Override.VolumeTime = fDuration;
2151     } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2152     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2153     e.Init(); // clear IDs
2154     e.Type = Event::type_note_synth_param;
2155     e.Param.NoteSynthParam.NoteID = id.noteID();
2156     e.Param.NoteSynthParam.Type = Event::synth_param_volume_time;
2157     e.Param.NoteSynthParam.Delta = fDuration;
2158 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2159 schoenebeck 3188
2160     pEngineChannel->ScheduleEventMicroSec(&e, 0);
2161     }
2162     // now schedule a "volume" change, simply one time slice ahead, with
2163     // the final fade out volume (0.0)
2164     {
2165     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2166     e.Init(); // clear IDs
2167     e.Type = Event::type_note_synth_param;
2168     e.Param.NoteSynthParam.NoteID = id.noteID();
2169     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
2170     e.Param.NoteSynthParam.Delta = 0.f;
2171 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2172 schoenebeck 3188
2173     // scheduling with 0 delay would also work here, but +1 is more
2174     // safe regarding potential future implementation changes of the
2175     // scheduler (see API comments of RTAVLTree::insert())
2176     pEngineChannel->ScheduleEventMicroSec(&e, 1);
2177     }
2178     // and finally if stopping the note was requested after the fade out
2179     // completed, then schedule to kill the voice after the requested
2180     // duration
2181     if (stop) {
2182     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2183     e.Init(); // clear IDs
2184     e.Type = Event::type_kill_note;
2185     e.Param.Note.ID = id.noteID();
2186     e.Param.Note.Key = pNote->hostKey;
2187    
2188     pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2189     }
2190     } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2191     VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2192 schoenebeck 3557 for (vmint i = 0; i < ids->arraySize(); ++i) {
2193 schoenebeck 3188 const ScriptID id = ids->evalIntElement(i);
2194     if (!id || !id.isNoteID()) continue;
2195    
2196     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2197     if (!pNote) continue;
2198    
2199     // if fade_out() was called immediately after note was triggered
2200     // then immediately apply fade out duration to Note object
2201 schoenebeck 3205 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2202 schoenebeck 3188 pNote->Override.VolumeTime = fDuration;
2203     } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2204     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2205     e.Init(); // clear IDs
2206     e.Type = Event::type_note_synth_param;
2207     e.Param.NoteSynthParam.NoteID = id.noteID();
2208     e.Param.NoteSynthParam.Type = Event::synth_param_volume_time;
2209     e.Param.NoteSynthParam.Delta = fDuration;
2210 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2211 schoenebeck 3188
2212     pEngineChannel->ScheduleEventMicroSec(&e, 0);
2213     }
2214     // now schedule a "volume" change, simply one time slice ahead, with
2215     // the final fade out volume (0.0)
2216     {
2217     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2218     e.Init(); // clear IDs
2219     e.Type = Event::type_note_synth_param;
2220     e.Param.NoteSynthParam.NoteID = id.noteID();
2221     e.Param.NoteSynthParam.Type = Event::synth_param_volume;
2222     e.Param.NoteSynthParam.Delta = 0.f;
2223 schoenebeck 3561 e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2224 schoenebeck 3188
2225     // scheduling with 0 delay would also work here, but +1 is more
2226     // safe regarding potential future implementation changes of the
2227     // scheduler (see API comments of RTAVLTree::insert())
2228     pEngineChannel->ScheduleEventMicroSec(&e, 1);
2229     }
2230     // and finally if stopping the note was requested after the fade out
2231     // completed, then schedule to kill the voice after the requested
2232     // duration
2233     if (stop) {
2234     Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2235     e.Init(); // clear IDs
2236     e.Type = Event::type_kill_note;
2237     e.Param.Note.ID = id.noteID();
2238     e.Param.Note.Key = pNote->hostKey;
2239    
2240     pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2241     }
2242     }
2243     }
2244    
2245     return successResult();
2246     }
2247    
2248 schoenebeck 3193 // get_event_par() function
2249    
2250     InstrumentScriptVMFunction_get_event_par::InstrumentScriptVMFunction_get_event_par(InstrumentScriptVM* parent)
2251     : m_vm(parent)
2252     {
2253     }
2254    
2255     VMFnResult* InstrumentScriptVMFunction_get_event_par::exec(VMFnArgs* args) {
2256     AbstractEngineChannel* pEngineChannel =
2257     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2258    
2259     const ScriptID id = args->arg(0)->asInt()->evalInt();
2260     if (!id) {
2261     wrnMsg("get_event_par(): note ID for argument 1 may not be zero");
2262     return successResult(0);
2263     }
2264     if (!id.isNoteID()) {
2265     wrnMsg("get_event_par(): argument 1 is not a note ID");
2266     return successResult(0);
2267     }
2268    
2269     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2270     if (!pNote) {
2271     wrnMsg("get_event_par(): no note alive with that note ID of argument 1");
2272     return successResult(0);
2273     }
2274    
2275 schoenebeck 3557 const vmint parameter = args->arg(1)->asInt()->evalInt();
2276 schoenebeck 3193 switch (parameter) {
2277     case EVENT_PAR_NOTE:
2278     return successResult(pNote->cause.Param.Note.Key);
2279     case EVENT_PAR_VELOCITY:
2280     return successResult(pNote->cause.Param.Note.Velocity);
2281     case EVENT_PAR_VOLUME:
2282     return successResult(
2283 schoenebeck 3561 RTMath::LinRatioToDecibel(pNote->Override.Volume.Value) * 1000.f
2284 schoenebeck 3193 );
2285     case EVENT_PAR_TUNE:
2286     return successResult(
2287 schoenebeck 3561 RTMath::FreqRatioToCents(pNote->Override.Pitch.Value) * 1000.f
2288 schoenebeck 3193 );
2289     case EVENT_PAR_0:
2290     return successResult(pNote->userPar[0]);
2291     case EVENT_PAR_1:
2292     return successResult(pNote->userPar[1]);
2293     case EVENT_PAR_2:
2294     return successResult(pNote->userPar[2]);
2295     case EVENT_PAR_3:
2296     return successResult(pNote->userPar[3]);
2297     }
2298    
2299     wrnMsg("get_event_par(): argument 2 is an invalid event parameter");
2300     return successResult(0);
2301     }
2302    
2303     // set_event_par() function
2304    
2305     InstrumentScriptVMFunction_set_event_par::InstrumentScriptVMFunction_set_event_par(InstrumentScriptVM* parent)
2306     : m_vm(parent)
2307     {
2308     }
2309    
2310     VMFnResult* InstrumentScriptVMFunction_set_event_par::exec(VMFnArgs* args) {
2311     AbstractEngineChannel* pEngineChannel =
2312     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2313    
2314     const ScriptID id = args->arg(0)->asInt()->evalInt();
2315     if (!id) {
2316     wrnMsg("set_event_par(): note ID for argument 1 may not be zero");
2317     return successResult();
2318     }
2319     if (!id.isNoteID()) {
2320     wrnMsg("set_event_par(): argument 1 is not a note ID");
2321     return successResult();
2322     }
2323    
2324     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2325     if (!pNote) return successResult();
2326    
2327 schoenebeck 3557 const vmint parameter = args->arg(1)->asInt()->evalInt();
2328     const vmint value = args->arg(2)->asInt()->evalInt();
2329 schoenebeck 3193
2330     switch (parameter) {
2331     case EVENT_PAR_NOTE:
2332     if (value < 0 || value > 127) {
2333     wrnMsg("set_event_par(): note number of argument 3 is out of range");
2334     return successResult();
2335     }
2336 schoenebeck 3214 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2337 schoenebeck 3193 pNote->cause.Param.Note.Key = value;
2338 schoenebeck 3214 m_vm->m_event->cause.Param.Note.Key = value;
2339     } else {
2340 schoenebeck 3193 wrnMsg("set_event_par(): note number can only be changed when note is new");
2341 schoenebeck 3214 }
2342 schoenebeck 3193 return successResult();
2343     case EVENT_PAR_VELOCITY:
2344     if (value < 0 || value > 127) {
2345     wrnMsg("set_event_par(): velocity of argument 3 is out of range");
2346     return successResult();
2347     }
2348 schoenebeck 3214 if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2349 schoenebeck 3193 pNote->cause.Param.Note.Velocity = value;
2350 schoenebeck 3214 m_vm->m_event->cause.Param.Note.Velocity = value;
2351     } else {
2352 schoenebeck 3193 wrnMsg("set_event_par(): velocity can only be changed when note is new");
2353 schoenebeck 3214 }
2354 schoenebeck 3193 return successResult();
2355     case EVENT_PAR_VOLUME:
2356     wrnMsg("set_event_par(): changing volume by this function is currently not supported, use change_vol() instead");
2357     return successResult();
2358     case EVENT_PAR_TUNE:
2359     wrnMsg("set_event_par(): changing tune by this function is currently not supported, use change_tune() instead");
2360     return successResult();
2361     case EVENT_PAR_0:
2362     pNote->userPar[0] = value;
2363     return successResult();
2364     case EVENT_PAR_1:
2365     pNote->userPar[1] = value;
2366     return successResult();
2367     case EVENT_PAR_2:
2368     pNote->userPar[2] = value;
2369     return successResult();
2370     case EVENT_PAR_3:
2371     pNote->userPar[3] = value;
2372     return successResult();
2373     }
2374    
2375     wrnMsg("set_event_par(): argument 2 is an invalid event parameter");
2376     return successResult();
2377     }
2378    
2379 schoenebeck 3214 // change_note() function
2380    
2381     InstrumentScriptVMFunction_change_note::InstrumentScriptVMFunction_change_note(InstrumentScriptVM* parent)
2382     : m_vm(parent)
2383     {
2384     }
2385    
2386     VMFnResult* InstrumentScriptVMFunction_change_note::exec(VMFnArgs* args) {
2387     AbstractEngineChannel* pEngineChannel =
2388     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2389    
2390     const ScriptID id = args->arg(0)->asInt()->evalInt();
2391     if (!id) {
2392     wrnMsg("change_note(): note ID for argument 1 may not be zero");
2393     return successResult();
2394     }
2395     if (!id.isNoteID()) {
2396     wrnMsg("change_note(): argument 1 is not a note ID");
2397     return successResult();
2398     }
2399    
2400     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2401     if (!pNote) return successResult();
2402    
2403 schoenebeck 3557 const vmint value = args->arg(1)->asInt()->evalInt();
2404 schoenebeck 3214 if (value < 0 || value > 127) {
2405     wrnMsg("change_note(): note number of argument 2 is out of range");
2406     return successResult();
2407     }
2408    
2409     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2410     pNote->cause.Param.Note.Key = value;
2411     m_vm->m_event->cause.Param.Note.Key = value;
2412 schoenebeck 3216 } else {
2413 schoenebeck 3214 wrnMsg("change_note(): note number can only be changed when note is new");
2414     }
2415    
2416     return successResult();
2417     }
2418    
2419     // change_velo() function
2420    
2421     InstrumentScriptVMFunction_change_velo::InstrumentScriptVMFunction_change_velo(InstrumentScriptVM* parent)
2422     : m_vm(parent)
2423     {
2424     }
2425    
2426     VMFnResult* InstrumentScriptVMFunction_change_velo::exec(VMFnArgs* args) {
2427     AbstractEngineChannel* pEngineChannel =
2428     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2429    
2430     const ScriptID id = args->arg(0)->asInt()->evalInt();
2431     if (!id) {
2432     wrnMsg("change_velo(): note ID for argument 1 may not be zero");
2433     return successResult();
2434     }
2435     if (!id.isNoteID()) {
2436     wrnMsg("change_velo(): argument 1 is not a note ID");
2437     return successResult();
2438     }
2439    
2440     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2441     if (!pNote) return successResult();
2442    
2443 schoenebeck 3557 const vmint value = args->arg(1)->asInt()->evalInt();
2444 schoenebeck 3214 if (value < 0 || value > 127) {
2445     wrnMsg("change_velo(): velocity of argument 2 is out of range");
2446     return successResult();
2447     }
2448    
2449     if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2450     pNote->cause.Param.Note.Velocity = value;
2451     m_vm->m_event->cause.Param.Note.Velocity = value;
2452 schoenebeck 3216 } else {
2453 schoenebeck 3214 wrnMsg("change_velo(): velocity can only be changed when note is new");
2454     }
2455    
2456     return successResult();
2457     }
2458    
2459 schoenebeck 3255 // change_play_pos() function
2460    
2461     InstrumentScriptVMFunction_change_play_pos::InstrumentScriptVMFunction_change_play_pos(InstrumentScriptVM* parent)
2462 schoenebeck 3587 : m_vm(parent)
2463 schoenebeck 3255 {
2464     }
2465    
2466 schoenebeck 3587 bool InstrumentScriptVMFunction_change_play_pos::acceptsArgType(vmint iArg, ExprType_t type) const {
2467     if (iArg == 0)
2468     return type == INT_EXPR;
2469     else
2470     return type == INT_EXPR || type == REAL_EXPR;
2471     }
2472    
2473 schoenebeck 3561 bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2474     if (iArg == 1)
2475     return type == VM_NO_UNIT || type == VM_SECOND;
2476     else
2477     return type == VM_NO_UNIT;
2478     }
2479    
2480 schoenebeck 3564 bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2481 schoenebeck 3587 return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2482 schoenebeck 3561 }
2483    
2484 schoenebeck 3255 VMFnResult* InstrumentScriptVMFunction_change_play_pos::exec(VMFnArgs* args) {
2485 schoenebeck 3564 const ScriptID id = args->arg(0)->asInt()->evalInt();
2486 schoenebeck 3255 if (!id) {
2487     wrnMsg("change_play_pos(): note ID for argument 1 may not be zero");
2488     return successResult();
2489     }
2490     if (!id.isNoteID()) {
2491     wrnMsg("change_play_pos(): argument 1 is not a note ID");
2492     return successResult();
2493     }
2494    
2495 schoenebeck 3587 StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2496     const vmint pos =
2497     (unit) ?
2498     args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2499     args->arg(1)->asNumber()->evalCastInt();
2500 schoenebeck 3255 if (pos < 0) {
2501     wrnMsg("change_play_pos(): playback position of argument 2 may not be negative");
2502     return successResult();
2503     }
2504    
2505     AbstractEngineChannel* pEngineChannel =
2506     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2507    
2508     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2509     if (!pNote) return successResult();
2510    
2511 schoenebeck 3557 pNote->Override.SampleOffset =
2512     (decltype(pNote->Override.SampleOffset)) pos;
2513 schoenebeck 3255
2514     return successResult();
2515     }
2516    
2517 schoenebeck 2935 // event_status() function
2518    
2519     InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)
2520     : m_vm(parent)
2521     {
2522     }
2523    
2524     VMFnResult* InstrumentScriptVMFunction_event_status::exec(VMFnArgs* args) {
2525     AbstractEngineChannel* pEngineChannel =
2526     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2527    
2528     const ScriptID id = args->arg(0)->asInt()->evalInt();
2529     if (!id) {
2530     wrnMsg("event_status(): note ID for argument 1 may not be zero");
2531     return successResult(EVENT_STATUS_INACTIVE);
2532     }
2533     if (!id.isNoteID()) {
2534     wrnMsg("event_status(): argument 1 is not a note ID");
2535     return successResult(EVENT_STATUS_INACTIVE);
2536     }
2537    
2538     NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2539     return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);
2540     }
2541    
2542 schoenebeck 3296 // callback_status() function
2543    
2544     InstrumentScriptVMFunction_callback_status::InstrumentScriptVMFunction_callback_status(InstrumentScriptVM* parent)
2545     : m_vm(parent)
2546     {
2547     }
2548    
2549     VMFnResult* InstrumentScriptVMFunction_callback_status::exec(VMFnArgs* args) {
2550     const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2551     if (!id) {
2552     wrnMsg("callback_status(): callback ID for argument 1 may not be zero");
2553     return successResult();
2554     }
2555    
2556     AbstractEngineChannel* pEngineChannel =
2557     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2558    
2559     RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2560     if (!itCallback)
2561     return successResult(CALLBACK_STATUS_TERMINATED);
2562    
2563     return successResult(
2564     (m_vm->m_event->execCtx == itCallback->execCtx) ?
2565     CALLBACK_STATUS_RUNNING : CALLBACK_STATUS_QUEUE
2566     );
2567     }
2568    
2569 schoenebeck 2948 // wait() function (overrides core wait() implementation)
2570    
2571     InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)
2572     : CoreVMFunction_wait(parent)
2573     {
2574     }
2575    
2576     VMFnResult* InstrumentScriptVMFunction_wait::exec(VMFnArgs* args) {
2577     InstrumentScriptVM* m_vm = (InstrumentScriptVM*) vm;
2578    
2579     // this might be set by passing 1 with the 2nd argument of built-in stop_wait() function
2580     if (m_vm->m_event->ignoreAllWaitCalls) return successResult();
2581    
2582     return CoreVMFunction_wait::exec(args);
2583     }
2584    
2585     // stop_wait() function
2586    
2587     InstrumentScriptVMFunction_stop_wait::InstrumentScriptVMFunction_stop_wait(InstrumentScriptVM* parent)
2588     : m_vm(parent)
2589     {
2590     }
2591    
2592     VMFnResult* InstrumentScriptVMFunction_stop_wait::exec(VMFnArgs* args) {
2593     AbstractEngineChannel* pEngineChannel =
2594     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2595    
2596     const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2597     if (!id) {
2598     wrnMsg("stop_wait(): callback ID for argument 1 may not be zero");
2599     return successResult();
2600     }
2601    
2602     RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2603     if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2604    
2605     const bool disableWaitForever =
2606     (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;
2607    
2608     pEngineChannel->ScheduleResumeOfScriptCallback(
2609 schoenebeck 3205 itCallback, m_vm->m_event->scheduleTime, disableWaitForever
2610 schoenebeck 2948 );
2611    
2612     return successResult();
2613     }
2614    
2615 schoenebeck 3277 // abort() function
2616    
2617     InstrumentScriptVMFunction_abort::InstrumentScriptVMFunction_abort(InstrumentScriptVM* parent)
2618 schoenebeck 3293 : m_vm(parent)
2619 schoenebeck 3277 {
2620     }
2621    
2622     VMFnResult* InstrumentScriptVMFunction_abort::exec(VMFnArgs* args) {
2623     const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2624     if (!id) {
2625     wrnMsg("abort(): callback ID for argument 1 may not be zero");
2626     return successResult();
2627     }
2628    
2629     AbstractEngineChannel* pEngineChannel =
2630     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2631    
2632     RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2633     if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2634    
2635     itCallback->execCtx->signalAbort();
2636    
2637     return successResult();
2638     }
2639    
2640 schoenebeck 3293 // fork() function
2641    
2642     InstrumentScriptVMFunction_fork::InstrumentScriptVMFunction_fork(InstrumentScriptVM* parent)
2643     : m_vm(parent)
2644     {
2645     }
2646    
2647     VMFnResult* InstrumentScriptVMFunction_fork::exec(VMFnArgs* args) {
2648     // check if this is actually the parent going to fork, or rather one of
2649     // the children which is already forked
2650     if (m_vm->m_event->forkIndex != 0) { // this is the entry point for a child ...
2651     int forkResult = m_vm->m_event->forkIndex;
2652     // reset so that this child may i.e. also call fork() later on
2653     m_vm->m_event->forkIndex = 0;
2654     return successResult(forkResult);
2655     }
2656    
2657     // if we are here, then this is the parent, so we must fork this parent
2658    
2659 schoenebeck 3557 const vmint n =
2660 schoenebeck 3293 (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : 1;
2661     const bool bAutoAbort =
2662     (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : true;
2663    
2664     if (m_vm->m_event->countChildHandlers() + n > MAX_FORK_PER_SCRIPT_HANDLER) {
2665     wrnMsg("fork(): requested amount would exceed allowed limit per event handler");
2666     return successResult(-1);
2667     }
2668    
2669     AbstractEngineChannel* pEngineChannel =
2670     static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2671    
2672     if (!pEngineChannel->hasFreeScriptCallbacks(n)) {
2673     wrnMsg("fork(): global limit of event handlers exceeded");
2674     return successResult(-1);
2675     }
2676    
2677     for (int iChild = 0; iChild < n; ++iChild) {
2678     RTList<ScriptEvent>::Iterator itChild =
2679     pEngineChannel->forkScriptCallback(m_vm->m_event, bAutoAbort);
2680     if (!itChild) { // should never happen, otherwise its a bug ...
2681     errMsg("fork(): internal error while allocating child");
2682     return errorResult(-1); // terminate script
2683     }
2684     // since both parent, as well all child script execution instances
2685 schoenebeck 3557 // all land in this exec() method, the following is (more or less)
2686 schoenebeck 3293 // the only feature that lets us distinguish the parent and
2687 schoenebeck 3557 // respective children from each other in this exec() method
2688 schoenebeck 3293 itChild->forkIndex = iChild + 1;
2689     }
2690    
2691     return successResult(0);
2692     }
2693    
2694 schoenebeck 2596 } // namespace LinuxSampler

  ViewVC Help
Powered by ViewVC