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

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

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

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

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

  ViewVC Help
Powered by ViewVC