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

Legend:
Removed from v.2879  
changed lines
  Added in v.3561

  ViewVC Help
Powered by ViewVC