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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.2606  
changed lines
  Added in v.3743

  ViewVC Help
Powered by ViewVC