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