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

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

  ViewVC Help
Powered by ViewVC