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

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

  ViewVC Help
Powered by ViewVC