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

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

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

revision 2879 by schoenebeck, Tue Apr 19 14:07:53 2016 UTC revision 3742 by schoenebeck, Fri Feb 14 15:15:13 2020 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (c) 2014-2016 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: -1 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");
# Line 34  namespace LinuxSampler { Line 108  namespace LinuxSampler {
108              return errorResult(0);              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");
             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");  
113              return errorResult(0);              return errorResult(0);
114          }          }
115    
# Line 51  namespace LinuxSampler { Line 118  namespace LinuxSampler {
118    
119          Event e = m_vm->m_event->cause; // copy to get fragment time for "now"          Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
120          e.Init(); // clear IDs          e.Init(); // clear IDs
121          e.Type = Event::type_note_on;          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          // make this new note dependent to the life time of the original note          // make this new note dependent to the life time of the original note
# Line 61  namespace LinuxSampler { Line 128  namespace LinuxSampler {
128                  return errorResult(0);                  return errorResult(0);
129              }              }
130              e.Param.Note.ParentNoteID = m_vm->m_event->cause.Param.Note.ID;              e.Param.Note.ParentNoteID = m_vm->m_event->cause.Param.Note.ID;
131                // check if that requested parent note is actually still alive
132                NoteBase* pParentNote =
133                    pEngineChannel->pEngine->NoteByID( e.Param.Note.ParentNoteID );
134                // if parent note is already gone then this new note is not required anymore
135                if (!pParentNote)
136                    return successResult(0);
137          }          }
138    
139          const note_id_t id = pEngineChannel->ScheduleNoteMicroSec(&e, 0);          const note_id_t id = pEngineChannel->ScheduleNoteMicroSec(&e, 0);
140    
141          // if a duration is supplied (and note-on event was scheduled          // if a sample offset is supplied, assign the offset as override
142          // successfully above), then schedule a subsequent note-off event          // 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) {          if (id && duration > 0) {
163              e.Type = Event::type_note_off;              e.Type = Event::type_stop_note;
164                e.Param.Note.ID = id;
165              e.Param.Note.Velocity = 127;              e.Param.Note.Velocity = 127;
166              pEngineChannel->ScheduleEventMicroSec(&e, duration);              pEngineChannel->ScheduleEventMicroSec(&e, duration);
167          }          }
# Line 79  namespace LinuxSampler { Line 172  namespace LinuxSampler {
172          return successResult( ScriptID::fromNoteID(id) );          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);
# Line 114  namespace LinuxSampler { Line 209  namespace LinuxSampler {
209          // would abort the script, and under heavy load it may be considerable          // would abort the script, and under heavy load it may be considerable
210          // that ScheduleEventMicroSec() fails above, so simply ignore that          // that ScheduleEventMicroSec() fails above, so simply ignore that
211          return successResult( ScriptID::fromEventID(id) );          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        // 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(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_ignore_event::acceptsArgType(vmint iArg, ExprType_t type) const {
296          return type == INT_EXPR || type == INT_ARR_EXPR;          return type == INT_EXPR || type == INT_ARR_EXPR;
297      }      }
298    
# Line 129  namespace LinuxSampler { Line 300  namespace LinuxSampler {
300          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
301                  static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);                  static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
302    
303          if (args->arg(0)->exprType() == INT_EXPR) {          if (args->argsCount() == 0 || args->arg(0)->exprType() == INT_EXPR) {
304              const ScriptID id = args->arg(0)->asInt()->evalInt();              const ScriptID id = (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : m_vm->m_event->id;
305              if (!id) {              if (!id && args->argsCount() >= 1) {
306                  wrnMsg("ignore_event(): event ID argument may not be zero");                  wrnMsg("ignore_event(): event ID argument may not be zero");
307                  // not errorResult(), because that would abort the script, not intentional in this case                  // not errorResult(), because that would abort the script, not intentional in this case
308                  return successResult();                  return successResult();
# Line 148  namespace LinuxSampler { Line 319  namespace LinuxSampler {
319          return successResult();          return successResult();
320      }      }
321    
322        // ignore_controller() function
323    
324      InstrumentScriptVMFunction_ignore_controller::InstrumentScriptVMFunction_ignore_controller(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_ignore_controller::InstrumentScriptVMFunction_ignore_controller(InstrumentScriptVM* parent)
325          : m_vm(parent)          : m_vm(parent)
326      {      {
# Line 168  namespace LinuxSampler { Line 341  namespace LinuxSampler {
341          return successResult();          return successResult();
342      }      }
343    
344        // note_off() function
345    
346      InstrumentScriptVMFunction_note_off::InstrumentScriptVMFunction_note_off(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_note_off::InstrumentScriptVMFunction_note_off(InstrumentScriptVM* parent)
347          : m_vm(parent)          : m_vm(parent)
348      {      {
349      }      }
350    
351      bool InstrumentScriptVMFunction_note_off::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_note_off::acceptsArgType(vmint iArg, ExprType_t type) const {
352          return type == INT_EXPR || type == INT_ARR_EXPR;          return type == INT_EXPR || type == INT_ARR_EXPR;
353      }      }
354    
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) {      VMFnResult* InstrumentScriptVMFunction_note_off::exec(VMFnArgs* args) {
373          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
374              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
375    
376          int velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;          vmint velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;
377          if (velocity < 0 || velocity > 127) {          if (velocity < 0 || velocity > 127) {
378              errMsg("note_off(): argument 2 is an invalid velocity value");              errMsg("note_off(): argument 2 is an invalid velocity value");
379              return errorResult();              return errorResult();
# Line 204  namespace LinuxSampler { Line 396  namespace LinuxSampler {
396              Event e = pNote->cause;              Event e = pNote->cause;
397              e.Init(); // clear IDs              e.Init(); // clear IDs
398              e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"              e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"
399              e.Type = Event::type_note_off;              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;              e.Param.Note.Velocity = velocity;
403    
404              pEngineChannel->ScheduleEventMicroSec(&e, 0);              pEngineChannel->ScheduleEventMicroSec(&e, 0);
405          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
406              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
407              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
408                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
409                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
410    
# Line 220  namespace LinuxSampler { Line 414  namespace LinuxSampler {
414                  Event e = pNote->cause;                  Event e = pNote->cause;
415                  e.Init(); // clear IDs                  e.Init(); // clear IDs
416                  e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"                  e.CopyTimeFrom(m_vm->m_event->cause); // set fragment time for "now"
417                  e.Type = Event::type_note_off;                  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;                  e.Param.Note.Velocity = velocity;
421    
422                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
# Line 230  namespace LinuxSampler { Line 426  namespace LinuxSampler {
426          return successResult();          return successResult();
427      }      }
428    
429        // set_event_mark() function
430    
431      InstrumentScriptVMFunction_set_event_mark::InstrumentScriptVMFunction_set_event_mark(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_set_event_mark::InstrumentScriptVMFunction_set_event_mark(InstrumentScriptVM* parent)
432          : m_vm(parent)          : 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) {      VMFnResult* InstrumentScriptVMFunction_set_event_mark::exec(VMFnArgs* args) {
454          const ScriptID id = args->arg(0)->asInt()->evalInt();          const ScriptID id = args->arg(0)->asInt()->evalInt();
455          const int groupID = args->arg(1)->asInt()->evalInt();          const vmint groupID = args->arg(1)->asInt()->evalInt();
456    
457          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
458              errMsg("set_event_mark(): argument 2 is an invalid group id");              errMsg("set_event_mark(): argument 2 is an invalid group id");
# Line 266  namespace LinuxSampler { Line 481  namespace LinuxSampler {
481          return successResult();          return successResult();
482      }      }
483    
484        // delete_event_mark() function
485    
486      InstrumentScriptVMFunction_delete_event_mark::InstrumentScriptVMFunction_delete_event_mark(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_delete_event_mark::InstrumentScriptVMFunction_delete_event_mark(InstrumentScriptVM* parent)
487          : m_vm(parent)          : 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) {      VMFnResult* InstrumentScriptVMFunction_delete_event_mark::exec(VMFnArgs* args) {
509          const ScriptID id = args->arg(0)->asInt()->evalInt();          const ScriptID id = args->arg(0)->asInt()->evalInt();
510          const int groupID = args->arg(1)->asInt()->evalInt();          const vmint groupID = args->arg(1)->asInt()->evalInt();
511    
512          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
513              errMsg("delete_event_mark(): argument 2 is an invalid group id");              errMsg("delete_event_mark(): argument 2 is an invalid group id");
# Line 288  namespace LinuxSampler { Line 522  namespace LinuxSampler {
522          return successResult();          return successResult();
523      }      }
524    
525        // by_marks() function
526    
527      InstrumentScriptVMFunction_by_marks::InstrumentScriptVMFunction_by_marks(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_by_marks::InstrumentScriptVMFunction_by_marks(InstrumentScriptVM* parent)
528          : m_vm(parent)          : m_vm(parent)
529      {      {
530      }      }
531    
532      int InstrumentScriptVMFunction_by_marks::Result::arraySize() const {      vmint InstrumentScriptVMFunction_by_marks::Result::arraySize() const {
533          return eventGroup->size();          return eventGroup->size();
534      }      }
535    
536      int InstrumentScriptVMFunction_by_marks::Result::evalIntElement(uint i) {      vmint InstrumentScriptVMFunction_by_marks::Result::evalIntElement(vmuint i) {
537          return (*eventGroup)[i];          return (*eventGroup)[i];
538      }      }
539    
# Line 313  namespace LinuxSampler { Line 549  namespace LinuxSampler {
549          return &m_result;          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) {      VMFnResult* InstrumentScriptVMFunction_by_marks::exec(VMFnArgs* args) {
570          int groupID = args->arg(0)->asInt()->evalInt();          vmint groupID = args->arg(0)->asInt()->evalInt();
571    
572          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
573              errMsg("by_marks(): argument is an invalid group id");              errMsg("by_marks(): argument is an invalid group id");
# Line 327  namespace LinuxSampler { Line 580  namespace LinuxSampler {
580          return successResult( &pEngineChannel->pScript->eventGroups[groupID] );          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) ?
1219                    float(attack) / 1000000.f /* us -> s */ :
1220                    float(attack) / float(VM_EG_PAR_MAX_VALUE);
1221    
1222            AbstractEngineChannel* pEngineChannel =
1223                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1224    
1225            if (args->arg(0)->exprType() == INT_EXPR) {
1226                const ScriptID id = args->arg(0)->asInt()->evalInt();
1227                if (!id) {
1228                    wrnMsg("change_attack(): note ID for argument 1 may not be zero");
1229                    return successResult();
1230                }
1231                if (!id.isNoteID()) {
1232                    wrnMsg("change_attack(): argument 1 is not a note ID");
1233                    return successResult();
1234                }
1235    
1236                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1237                if (!pNote) return successResult();
1238    
1239                // if change_attack() was called immediately after note was triggered
1240                // then immediately apply attack to Note object
1241                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1242                    pNote->Override.Attack.Value = fAttack;
1243                    pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1244                } else { // otherwise schedule attack change ...
1245                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1246                    e.Init(); // clear IDs
1247                    e.Type = Event::type_note_synth_param;
1248                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1249                    e.Param.NoteSynthParam.Type     = Event::synth_param_attack;
1250                    e.Param.NoteSynthParam.Delta    = fAttack;
1251                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1252                        isFinal, false, unit
1253                    );
1254                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1255                }
1256            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1257                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1258                for (vmint i = 0; i < ids->arraySize(); ++i) {
1259                    const ScriptID id = ids->evalIntElement(i);
1260                    if (!id || !id.isNoteID()) continue;
1261    
1262                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1263                    if (!pNote) continue;
1264    
1265                    // if change_attack() was called immediately after note was triggered
1266                    // then immediately apply attack to Note object
1267                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1268                        pNote->Override.Attack.Value = fAttack;
1269                        pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1270                    } else { // otherwise schedule attack change ...
1271                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1272                        e.Init(); // clear IDs
1273                        e.Type = Event::type_note_synth_param;
1274                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1275                        e.Param.NoteSynthParam.Type     = Event::synth_param_attack;
1276                        e.Param.NoteSynthParam.Delta    = fAttack;
1277                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1278                            isFinal, false, unit
1279                        );
1280                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1281                    }
1282                }
1283            }
1284    
1285            return successResult();
1286        }
1287    
1288        // change_decay() function
1289        
1290        InstrumentScriptVMFunction_change_decay::InstrumentScriptVMFunction_change_decay(InstrumentScriptVM* parent)
1291            : m_vm(parent)
1292        {
1293        }
1294    
1295        bool InstrumentScriptVMFunction_change_decay::acceptsArgType(vmint iArg, ExprType_t type) const {
1296            if (iArg == 0)
1297                return type == INT_EXPR || type == INT_ARR_EXPR;
1298            else
1299                return type == INT_EXPR || type == REAL_EXPR;
1300        }
1301    
1302        bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1303            if (iArg == 1)
1304                return type == VM_NO_UNIT || type == VM_SECOND;
1305            else
1306                return type == VM_NO_UNIT;
1307        }
1308    
1309        bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1310            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1311        }
1312    
1313        bool InstrumentScriptVMFunction_change_decay::acceptsArgFinal(vmint iArg) const {
1314            return iArg == 1;
1315        }
1316    
1317        void InstrumentScriptVMFunction_change_decay::checkArgs(VMFnArgs* args,
1318                                                                std::function<void(String)> err,
1319                                                                std::function<void(String)> wrn)
1320        {
1321            // super class checks
1322            Super::checkArgs(args, err, wrn);
1323    
1324            // own checks ...
1325            if (args->argsCount() >= 2) {
1326                VMNumberExpr* argTime = args->arg(1)->asNumber();
1327                if (argTime->unitType() && !argTime->isFinal()) {
1328                    wrn("Argument 2 implies 'final' value when using seconds as unit for decay time.");
1329                }
1330            }
1331        }
1332    
1333        VMFnResult* InstrumentScriptVMFunction_change_decay::exec(VMFnArgs* args) {
1334            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1335            vmint decay =
1336                (unit) ?
1337                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1338                    args->arg(1)->asNumber()->evalCastInt();
1339            const bool isFinal =
1340                (unit) ?
1341                    true : // imply 'final' value if unit type is used
1342                    args->arg(1)->asNumber()->isFinal();
1343            // note: intentionally not checking against a max. value here!
1344            // (to allow i.e. passing 2000000 for doubling the decay time)
1345            if (decay < 0) {
1346                wrnMsg("change_decay(): argument 2 may not be negative");
1347                decay = 0;
1348            }
1349            const float fDecay =
1350                (unit) ?
1351                    float(decay) / 1000000.f /* us -> s */ :
1352                    float(decay) / float(VM_EG_PAR_MAX_VALUE);
1353    
1354            AbstractEngineChannel* pEngineChannel =
1355                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1356    
1357            if (args->arg(0)->exprType() == INT_EXPR) {
1358                const ScriptID id = args->arg(0)->asInt()->evalInt();
1359                if (!id) {
1360                    wrnMsg("change_decay(): note ID for argument 1 may not be zero");
1361                    return successResult();
1362                }
1363                if (!id.isNoteID()) {
1364                    wrnMsg("change_decay(): argument 1 is not a note ID");
1365                    return successResult();
1366                }
1367    
1368                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1369                if (!pNote) return successResult();
1370    
1371                // if change_decay() was called immediately after note was triggered
1372                // then immediately apply decay to Note object
1373                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1374                    pNote->Override.Decay.Value = fDecay;
1375                    pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1376                } else { // otherwise schedule decay change ...
1377                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1378                    e.Init(); // clear IDs
1379                    e.Type = Event::type_note_synth_param;
1380                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1381                    e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1382                    e.Param.NoteSynthParam.Delta    = fDecay;
1383                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1384                        isFinal, false, unit
1385                    );
1386                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1387                }
1388            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1389                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1390                for (vmint i = 0; i < ids->arraySize(); ++i) {
1391                    const ScriptID id = ids->evalIntElement(i);
1392                    if (!id || !id.isNoteID()) continue;
1393    
1394                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1395                    if (!pNote) continue;
1396    
1397                    // if change_decay() was called immediately after note was triggered
1398                    // then immediately apply decay to Note object
1399                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1400                        pNote->Override.Decay.Value = fDecay;
1401                        pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1402                    } else { // otherwise schedule decay change ...
1403                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1404                        e.Init(); // clear IDs
1405                        e.Type = Event::type_note_synth_param;
1406                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1407                        e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1408                        e.Param.NoteSynthParam.Delta    = fDecay;
1409                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1410                            isFinal, false, unit
1411                        );
1412                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1413                    }
1414                }
1415            }
1416    
1417            return successResult();
1418        }
1419    
1420        // change_release() function
1421        
1422        InstrumentScriptVMFunction_change_release::InstrumentScriptVMFunction_change_release(InstrumentScriptVM* parent)
1423            : m_vm(parent)
1424        {
1425        }
1426    
1427        bool InstrumentScriptVMFunction_change_release::acceptsArgType(vmint iArg, ExprType_t type) const {
1428            if (iArg == 0)
1429                return type == INT_EXPR || type == INT_ARR_EXPR;
1430            else
1431                return type == INT_EXPR || type == REAL_EXPR;
1432        }
1433    
1434        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1435            if (iArg == 1)
1436                return type == VM_NO_UNIT || type == VM_SECOND;
1437            else
1438                return type == VM_NO_UNIT;
1439        }
1440    
1441        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1442            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1443        }
1444    
1445        bool InstrumentScriptVMFunction_change_release::acceptsArgFinal(vmint iArg) const {
1446            return iArg == 1;
1447        }
1448    
1449        void InstrumentScriptVMFunction_change_release::checkArgs(VMFnArgs* args,
1450                                                                  std::function<void(String)> err,
1451                                                                  std::function<void(String)> wrn)
1452        {
1453            // super class checks
1454            Super::checkArgs(args, err, wrn);
1455    
1456            // own checks ...
1457            if (args->argsCount() >= 2) {
1458                VMNumberExpr* argTime = args->arg(1)->asNumber();
1459                if (argTime->unitType() && !argTime->isFinal()) {
1460                    wrn("Argument 2 implies 'final' value when using seconds as unit for release time.");
1461                }
1462            }
1463        }
1464    
1465        VMFnResult* InstrumentScriptVMFunction_change_release::exec(VMFnArgs* args) {
1466            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1467            vmint release =
1468                (unit) ?
1469                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1470                    args->arg(1)->asNumber()->evalCastInt();
1471            const bool isFinal =
1472                (unit) ?
1473                    true : // imply 'final' value if unit type is used
1474                    args->arg(1)->asNumber()->isFinal();
1475            // note: intentionally not checking against a max. value here!
1476            // (to allow i.e. passing 2000000 for doubling the release time)
1477            if (release < 0) {
1478                wrnMsg("change_release(): argument 2 may not be negative");
1479                release = 0;
1480            }
1481            const float fRelease =
1482                (unit) ?
1483                    float(release) / 1000000.f /* us -> s */ :
1484                    float(release) / float(VM_EG_PAR_MAX_VALUE);
1485    
1486            AbstractEngineChannel* pEngineChannel =
1487                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1488    
1489            if (args->arg(0)->exprType() == INT_EXPR) {
1490                const ScriptID id = args->arg(0)->asInt()->evalInt();
1491                if (!id) {
1492                    wrnMsg("change_release(): note ID for argument 1 may not be zero");
1493                    return successResult();
1494                }
1495                if (!id.isNoteID()) {
1496                    wrnMsg("change_release(): argument 1 is not a note ID");
1497                    return successResult();
1498                }
1499    
1500                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1501                if (!pNote) return successResult();
1502    
1503                // if change_release() was called immediately after note was triggered
1504                // then immediately apply relase to Note object
1505                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1506                    pNote->Override.Release.Value = fRelease;
1507                    pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1508                } else { // otherwise schedule release change ...
1509                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1510                    e.Init(); // clear IDs
1511                    e.Type = Event::type_note_synth_param;
1512                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1513                    e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1514                    e.Param.NoteSynthParam.Delta    = fRelease;
1515                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1516                        isFinal, false, unit
1517                    );
1518                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1519                }
1520            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1521                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1522                for (vmint i = 0; i < ids->arraySize(); ++i) {
1523                    const ScriptID id = ids->evalIntElement(i);
1524                    if (!id || !id.isNoteID()) continue;
1525    
1526                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1527                    if (!pNote) continue;
1528    
1529                    // if change_release() was called immediately after note was triggered
1530                    // then immediately apply relase to Note object
1531                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1532                        pNote->Override.Release.Value = fRelease;
1533                        pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1534                    } else { // otherwise schedule release change ...
1535                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1536                        e.Init(); // clear IDs
1537                        e.Type = Event::type_note_synth_param;
1538                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1539                        e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1540                        e.Param.NoteSynthParam.Delta    = fRelease;
1541                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1542                            isFinal, false, unit
1543                        );
1544                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1545                    }
1546                }
1547            }
1548    
1549            return successResult();
1550        }
1551    
1552        // template for change_*() functions
1553    
1554        bool VMChangeSynthParamFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1555            if (iArg == 0)
1556                return type == INT_EXPR || type == INT_ARR_EXPR;
1557            else
1558                return type == INT_EXPR || (m_acceptReal && type == REAL_EXPR);
1559        }
1560    
1561        bool VMChangeSynthParamFunction::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1562            if (iArg == 1)
1563                return type == VM_NO_UNIT || type == m_unit;
1564            else
1565                return type == VM_NO_UNIT;
1566        }
1567    
1568        bool VMChangeSynthParamFunction::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1569            return m_acceptUnitPrefix && iArg == 1 && type == m_unit; // only allow metric prefix(es) if approprirate unit type is used (e.g. Hz)
1570        }
1571    
1572        bool VMChangeSynthParamFunction::acceptsArgFinal(vmint iArg) const {
1573            return (m_acceptFinal) ? (iArg == 1) : false;
1574        }
1575    
1576        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Param& param, const bool bFinal, const StdUnit_t unit) {
1577            param.Scope = NoteBase::scopeBy_FinalUnit(bFinal, unit);
1578        }
1579    
1580        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Norm& param, const bool bFinal, const StdUnit_t unit) {
1581            param.Final = bFinal;
1582        }
1583    
1584        inline static void setNoteParamScopeBy_FinalUnit(float& param, const bool bFinal, const StdUnit_t unit) {
1585            /* NOOP */
1586        }
1587    
1588        template<class T>
1589        inline static void setNoteParamValue(T& param, vmfloat value) {
1590            param.Value = value;
1591        }
1592    
1593        inline static void setNoteParamValue(float& param, vmfloat value) {
1594            param = value;
1595        }
1596    
1597        void VMChangeSynthParamFunction::checkArgs(VMFnArgs* args,
1598                                                   std::function<void(String)> err,
1599                                                   std::function<void(String)> wrn)
1600        {
1601            // super class checks
1602            Super::checkArgs(args, err, wrn);
1603    
1604            // own checks ...
1605            if (m_unit && m_unit != VM_BEL && args->argsCount() >= 2) {
1606                VMNumberExpr* arg = args->arg(1)->asNumber();
1607                if (arg && arg->unitType() && !arg->isFinal()) {
1608                    wrn("Argument 2 implies 'final' value when unit type " +
1609                        unitTypeStr(arg->unitType()) + " is used.");
1610                }
1611            }
1612        }
1613    
1614        // Arbitrarily chosen constant value symbolizing "no limit".
1615        #define NO_LIMIT 1315916909
1616    
1617        template<class T_NoteParamType, T_NoteParamType NoteBase::_Override::*T_noteParam,
1618                 vmint T_synthParam,
1619                 vmint T_minValueNorm, vmint T_maxValueNorm, bool T_normalizeNorm,
1620                 vmint T_minValueUnit, vmint T_maxValueUnit,
1621                 MetricPrefix_t T_unitPrefix0, MetricPrefix_t ... T_unitPrefixN>
1622        VMFnResult* VMChangeSynthParamFunction::execTemplate(VMFnArgs* args, const char* functionName)
1623        {
1624            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1625            const bool isFinal =
1626                (m_unit && m_unit != VM_BEL && unit) ?
1627                    true : // imply 'final' value if unit type is used (except of 'Bel' which may be relative)
1628                    args->arg(1)->asNumber()->isFinal();
1629            vmint value =
1630                (m_acceptUnitPrefix && ((m_unit && unit) || (!m_unit && args->arg(1)->asNumber()->hasUnitFactorNow())))
1631                    ? args->arg(1)->asNumber()->evalCastInt(T_unitPrefix0, T_unitPrefixN ...)
1632                    : args->arg(1)->asNumber()->evalCastInt();
1633    
1634            // check if passed value is in allowed range
1635            if (unit && m_unit) {
1636                if (T_maxValueUnit != NO_LIMIT && value > T_maxValueUnit) {
1637                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueUnit));
1638                    value = T_maxValueUnit;
1639                } else if (T_minValueUnit != NO_LIMIT && value < T_minValueUnit) {
1640                    if (T_minValueUnit == 0)
1641                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1642                    else
1643                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueUnit));
1644                    value = T_minValueUnit;
1645                }
1646            } else { // value was passed to this function without a unit ...
1647                if (T_maxValueNorm != NO_LIMIT && value > T_maxValueNorm) {
1648                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueNorm));
1649                    value = T_maxValueNorm;
1650                } else if (T_minValueNorm != NO_LIMIT && value < T_minValueNorm) {
1651                    if (T_minValueNorm == 0)
1652                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1653                    else
1654                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueNorm));
1655                    value = T_minValueNorm;
1656                }
1657            }
1658    
1659            // convert passed argument value to engine internal expected value range (i.e. 0.0 .. 1.0)
1660            const float fValue =
1661                (unit && m_unit) ?
1662                    (unit == VM_BEL) ?
1663                        RTMath::DecibelToLinRatio(float(value) * float(T_unitPrefix0) /*i.e. mdB -> dB*/) :
1664                        float(value) * VMUnit::unitFactor(T_unitPrefix0, T_unitPrefixN ...) /*i.e. us -> s*/ :
1665                    (T_normalizeNorm) ?
1666                        float(value) / ((T_maxValueNorm != NO_LIMIT) ? float(T_maxValueNorm) : 1000000.f/* fallback: value range used for most */) :
1667                        float(value) /* as is */;
1668    
1669            AbstractEngineChannel* pEngineChannel =
1670                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1671    
1672            if (args->arg(0)->exprType() == INT_EXPR) {
1673                const ScriptID id = args->arg(0)->asInt()->evalInt();
1674                if (!id) {
1675                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1676                    return successResult();
1677                }
1678                if (!id.isNoteID()) {
1679                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1680                    return successResult();
1681                }
1682    
1683                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1684                if (!pNote) return successResult();
1685    
1686                // if this change_*() script function was called immediately after
1687                // note was triggered then immediately apply the synth parameter
1688                // change to Note object
1689                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1690                    setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1691                    setNoteParamScopeBy_FinalUnit(
1692                        (pNote->Override.*T_noteParam),
1693                        isFinal, unit
1694                    );
1695                } else { // otherwise schedule this synth parameter change ...
1696                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1697                    e.Init(); // clear IDs
1698                    e.Type = Event::type_note_synth_param;
1699                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1700                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1701                    e.Param.NoteSynthParam.Delta    = fValue;
1702                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1703                        isFinal, false, unit
1704                    );
1705                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1706                }
1707            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1708                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1709                for (vmint i = 0; i < ids->arraySize(); ++i) {
1710                    const ScriptID id = ids->evalIntElement(i);
1711                    if (!id || !id.isNoteID()) continue;
1712    
1713                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1714                    if (!pNote) continue;
1715    
1716                    // if this change_*() script function was called immediately after
1717                    // note was triggered then immediately apply the synth parameter
1718                    // change to Note object
1719                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1720                        setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1721                        setNoteParamScopeBy_FinalUnit(
1722                            (pNote->Override.*T_noteParam),
1723                            isFinal, unit
1724                        );
1725                    } else { // otherwise schedule this synth parameter change ...
1726                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1727                        e.Init(); // clear IDs
1728                        e.Type = Event::type_note_synth_param;
1729                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1730                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1731                        e.Param.NoteSynthParam.Delta    = fValue;
1732                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1733                            isFinal, false, unit
1734                        );
1735                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1736                    }
1737                }
1738            }
1739    
1740            return successResult();
1741        }
1742    
1743        // change_sustain() function
1744    
1745        VMFnResult* InstrumentScriptVMFunction_change_sustain::exec(VMFnArgs* args) {
1746            return VMChangeSynthParamFunction::execTemplate<
1747                        decltype(NoteBase::_Override::Sustain),
1748                        &NoteBase::_Override::Sustain,
1749                        Event::synth_param_sustain,
1750                        /* if value passed without unit */
1751                        0, NO_LIMIT, true,
1752                        /* if value passed WITH 'Bel' unit */
1753                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_sustain" );
1754        }
1755    
1756        // change_cutoff_attack() function
1757    
1758        VMFnResult* InstrumentScriptVMFunction_change_cutoff_attack::exec(VMFnArgs* args) {
1759            return VMChangeSynthParamFunction::execTemplate<
1760                        decltype(NoteBase::_Override::CutoffAttack),
1761                        &NoteBase::_Override::CutoffAttack,
1762                        Event::synth_param_cutoff_attack,
1763                        /* if value passed without unit */
1764                        0, NO_LIMIT, true,
1765                        /* if value passed with 'seconds' unit */
1766                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_attack" );
1767        }
1768    
1769        // change_cutoff_decay() function
1770    
1771        VMFnResult* InstrumentScriptVMFunction_change_cutoff_decay::exec(VMFnArgs* args) {
1772            return VMChangeSynthParamFunction::execTemplate<
1773                        decltype(NoteBase::_Override::CutoffDecay),
1774                        &NoteBase::_Override::CutoffDecay,
1775                        Event::synth_param_cutoff_decay,
1776                        /* if value passed without unit */
1777                        0, NO_LIMIT, true,
1778                        /* if value passed with 'seconds' unit */
1779                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_decay" );
1780        }
1781    
1782        // change_cutoff_sustain() function
1783    
1784        VMFnResult* InstrumentScriptVMFunction_change_cutoff_sustain::exec(VMFnArgs* args) {
1785            return VMChangeSynthParamFunction::execTemplate<
1786                        decltype(NoteBase::_Override::CutoffSustain),
1787                        &NoteBase::_Override::CutoffSustain,
1788                        Event::synth_param_cutoff_sustain,
1789                        /* if value passed without unit */
1790                        0, NO_LIMIT, true,
1791                        /* if value passed WITH 'Bel' unit */
1792                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_cutoff_sustain" );
1793        }
1794    
1795        // change_cutoff_release() function
1796    
1797        VMFnResult* InstrumentScriptVMFunction_change_cutoff_release::exec(VMFnArgs* args) {
1798            return VMChangeSynthParamFunction::execTemplate<
1799                        decltype(NoteBase::_Override::CutoffRelease),
1800                        &NoteBase::_Override::CutoffRelease,
1801                        Event::synth_param_cutoff_release,
1802                        /* if value passed without unit */
1803                        0, NO_LIMIT, true,
1804                        /* if value passed with 'seconds' unit */
1805                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_release" );
1806        }
1807    
1808        // change_amp_lfo_depth() function
1809    
1810        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_depth::exec(VMFnArgs* args) {
1811            return VMChangeSynthParamFunction::execTemplate<
1812                        decltype(NoteBase::_Override::AmpLFODepth),
1813                        &NoteBase::_Override::AmpLFODepth,
1814                        Event::synth_param_amp_lfo_depth,
1815                        /* if value passed without unit */
1816                        0, NO_LIMIT, true,
1817                        /* not used (since this function does not accept unit) */
1818                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_amp_lfo_depth" );
1819        }
1820    
1821        // change_amp_lfo_freq() function
1822    
1823        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_freq::exec(VMFnArgs* args) {
1824            return VMChangeSynthParamFunction::execTemplate<
1825                        decltype(NoteBase::_Override::AmpLFOFreq),
1826                        &NoteBase::_Override::AmpLFOFreq,
1827                        Event::synth_param_amp_lfo_freq,
1828                        /* if value passed without unit */
1829                        0, NO_LIMIT, true,
1830                        /* if value passed with 'Hz' unit */
1831                        0, 30000, VM_NO_PREFIX>( args, "change_amp_lfo_freq" );
1832        }
1833    
1834        // change_cutoff_lfo_depth() function
1835    
1836        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_depth::exec(VMFnArgs* args) {
1837            return VMChangeSynthParamFunction::execTemplate<
1838                        decltype(NoteBase::_Override::CutoffLFODepth),
1839                        &NoteBase::_Override::CutoffLFODepth,
1840                        Event::synth_param_cutoff_lfo_depth,
1841                        /* if value passed without unit */
1842                        0, NO_LIMIT, true,
1843                        /* not used (since this function does not accept unit) */
1844                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_cutoff_lfo_depth" );
1845        }
1846    
1847        // change_cutoff_lfo_freq() function
1848    
1849        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_freq::exec(VMFnArgs* args) {
1850            return VMChangeSynthParamFunction::execTemplate<
1851                        decltype(NoteBase::_Override::CutoffLFOFreq),
1852                        &NoteBase::_Override::CutoffLFOFreq,
1853                        Event::synth_param_cutoff_lfo_freq,
1854                        /* if value passed without unit */
1855                        0, NO_LIMIT, true,
1856                        /* if value passed with 'Hz' unit */
1857                        0, 30000, VM_NO_PREFIX>( args, "change_cutoff_lfo_freq" );
1858        }
1859    
1860        // change_pitch_lfo_depth() function
1861    
1862        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_depth::exec(VMFnArgs* args) {
1863            return VMChangeSynthParamFunction::execTemplate<
1864                        decltype(NoteBase::_Override::PitchLFODepth),
1865                        &NoteBase::_Override::PitchLFODepth,
1866                        Event::synth_param_pitch_lfo_depth,
1867                        /* if value passed without unit */
1868                        0, NO_LIMIT, true,
1869                        /* not used (since this function does not accept unit) */
1870                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_pitch_lfo_depth" );
1871        }
1872    
1873        // change_pitch_lfo_freq() function
1874    
1875        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_freq::exec(VMFnArgs* args) {
1876            return VMChangeSynthParamFunction::execTemplate<
1877                        decltype(NoteBase::_Override::PitchLFOFreq),
1878                        &NoteBase::_Override::PitchLFOFreq,
1879                        Event::synth_param_pitch_lfo_freq,
1880                        /* if value passed without unit */
1881                        0, NO_LIMIT, true,
1882                        /* if value passed with 'Hz' unit */
1883                        0, 30000, VM_NO_PREFIX>( args, "change_pitch_lfo_freq" );
1884        }
1885    
1886        // change_vol_time() function
1887    
1888        VMFnResult* InstrumentScriptVMFunction_change_vol_time::exec(VMFnArgs* args) {
1889            return VMChangeSynthParamFunction::execTemplate<
1890                        decltype(NoteBase::_Override::VolumeTime),
1891                        &NoteBase::_Override::VolumeTime,
1892                        Event::synth_param_volume_time,
1893                        /* if value passed without unit (implying 'us' unit) */
1894                        0, NO_LIMIT, true,
1895                        /* if value passed with 'seconds' unit */
1896                        0, NO_LIMIT, VM_MICRO>( args, "change_vol_time" );
1897        }
1898    
1899        // change_tune_time() function
1900    
1901        VMFnResult* InstrumentScriptVMFunction_change_tune_time::exec(VMFnArgs* args) {
1902            return VMChangeSynthParamFunction::execTemplate<
1903                        decltype(NoteBase::_Override::PitchTime),
1904                        &NoteBase::_Override::PitchTime,
1905                        Event::synth_param_pitch_time,
1906                        /* if value passed without unit (implying 'us' unit) */
1907                        0, NO_LIMIT, true,
1908                        /* if value passed with 'seconds' unit */
1909                        0, NO_LIMIT, VM_MICRO>( args, "change_tune_time" );
1910        }
1911    
1912        // change_pan_time() function
1913    
1914        VMFnResult* InstrumentScriptVMFunction_change_pan_time::exec(VMFnArgs* args) {
1915            return VMChangeSynthParamFunction::execTemplate<
1916                        decltype(NoteBase::_Override::PanTime),
1917                        &NoteBase::_Override::PanTime,
1918                        Event::synth_param_pan_time,
1919                        /* if value passed without unit (implying 'us' unit) */
1920                        0, NO_LIMIT, true,
1921                        /* if value passed with 'seconds' unit */
1922                        0, NO_LIMIT, VM_MICRO>( args, "change_pan_time" );
1923        }
1924    
1925        // template for change_*_curve() functions
1926    
1927        bool VMChangeFadeCurveFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1928            if (iArg == 0)
1929                return type == INT_EXPR || type == INT_ARR_EXPR;
1930            else
1931                return type == INT_EXPR;
1932        }
1933    
1934        template<fade_curve_t NoteBase::_Override::*T_noteParam, vmint T_synthParam>
1935        VMFnResult* VMChangeFadeCurveFunction::execTemplate(VMFnArgs* args, const char* functionName) {
1936            vmint value = args->arg(1)->asInt()->evalInt();
1937            switch (value) {
1938                case FADE_CURVE_LINEAR:
1939                case FADE_CURVE_EASE_IN_EASE_OUT:
1940                    break;
1941                default:
1942                    wrnMsg(String(functionName) + "(): invalid curve type passed as argument 2");
1943                    return successResult();
1944            }
1945    
1946            AbstractEngineChannel* pEngineChannel =
1947                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1948    
1949            if (args->arg(0)->exprType() == INT_EXPR) {
1950                const ScriptID id = args->arg(0)->asInt()->evalInt();
1951                if (!id) {
1952                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1953                    return successResult();
1954                }
1955                if (!id.isNoteID()) {
1956                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1957                    return successResult();
1958                }
1959    
1960                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1961                if (!pNote) return successResult();
1962    
1963                // if this change_*_curve() script function was called immediately after
1964                // note was triggered then immediately apply the synth parameter
1965                // change to Note object
1966                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1967                    pNote->Override.*T_noteParam = (fade_curve_t) value;
1968                } else { // otherwise schedule this synth parameter change ...
1969                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1970                    e.Init(); // clear IDs
1971                    e.Type = Event::type_note_synth_param;
1972                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1973                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1974                    e.Param.NoteSynthParam.Delta    = value;
1975                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1976    
1977                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1978                }
1979            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1980                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1981                for (vmint i = 0; i < ids->arraySize(); ++i) {
1982                    const ScriptID id = ids->evalIntElement(i);
1983                    if (!id || !id.isNoteID()) continue;
1984    
1985                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1986                    if (!pNote) continue;
1987    
1988                    // if this change_*_curve() script function was called immediately after
1989                    // note was triggered then immediately apply the synth parameter
1990                    // change to Note object
1991                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1992                        pNote->Override.*T_noteParam = (fade_curve_t) value;
1993                    } else { // otherwise schedule this synth parameter change ...
1994                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1995                        e.Init(); // clear IDs
1996                        e.Type = Event::type_note_synth_param;
1997                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1998                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1999                        e.Param.NoteSynthParam.Delta    = value;
2000                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2001    
2002                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2003                    }
2004                }
2005            }
2006    
2007            return successResult();
2008        }
2009    
2010        // change_vol_curve() function
2011    
2012        VMFnResult* InstrumentScriptVMFunction_change_vol_curve::exec(VMFnArgs* args) {
2013            return VMChangeFadeCurveFunction::execTemplate<
2014                        &NoteBase::_Override::VolumeCurve,
2015                        Event::synth_param_volume_curve>( args, "change_vol_curve" );
2016        }
2017    
2018        // change_tune_curve() function
2019    
2020        VMFnResult* InstrumentScriptVMFunction_change_tune_curve::exec(VMFnArgs* args) {
2021            return VMChangeFadeCurveFunction::execTemplate<
2022                        &NoteBase::_Override::PitchCurve,
2023                        Event::synth_param_pitch_curve>( args, "change_tune_curve" );
2024        }
2025    
2026        // change_pan_curve() function
2027    
2028        VMFnResult* InstrumentScriptVMFunction_change_pan_curve::exec(VMFnArgs* args) {
2029            return VMChangeFadeCurveFunction::execTemplate<
2030            &NoteBase::_Override::PanCurve,
2031            Event::synth_param_pan_curve>( args, "change_pan_curve" );
2032        }
2033    
2034        // fade_in() function
2035    
2036        InstrumentScriptVMFunction_fade_in::InstrumentScriptVMFunction_fade_in(InstrumentScriptVM* parent)
2037            : m_vm(parent)
2038        {
2039        }
2040    
2041        bool InstrumentScriptVMFunction_fade_in::acceptsArgType(vmint iArg, ExprType_t type) const {
2042            if (iArg == 0)
2043                return type == INT_EXPR || type == INT_ARR_EXPR;
2044            else
2045                return type == INT_EXPR || type == REAL_EXPR;
2046        }
2047    
2048        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2049            if (iArg == 1)
2050                return type == VM_NO_UNIT || type == VM_SECOND;
2051            else
2052                return type == VM_NO_UNIT;
2053        }
2054    
2055        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2056            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2057        }
2058    
2059        VMFnResult* InstrumentScriptVMFunction_fade_in::exec(VMFnArgs* args) {
2060            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2061            vmint duration =
2062                (unit) ?
2063                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2064                    args->arg(1)->asNumber()->evalCastInt();
2065            if (duration < 0) {
2066                wrnMsg("fade_in(): argument 2 may not be negative");
2067                duration = 0;
2068            }
2069            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2070    
2071            AbstractEngineChannel* pEngineChannel =
2072                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2073    
2074            if (args->arg(0)->exprType() == INT_EXPR) {
2075                const ScriptID id = args->arg(0)->asInt()->evalInt();
2076                if (!id) {
2077                    wrnMsg("fade_in(): note ID for argument 1 may not be zero");
2078                    return successResult();
2079                }
2080                if (!id.isNoteID()) {
2081                    wrnMsg("fade_in(): argument 1 is not a note ID");
2082                    return successResult();
2083                }
2084    
2085                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2086                if (!pNote) return successResult();
2087    
2088                // if fade_in() was called immediately after note was triggered
2089                // then immediately apply a start volume of zero to Note object,
2090                // as well as the fade in duration
2091                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2092                    pNote->Override.Volume.Value = 0.f;
2093                    pNote->Override.VolumeTime = fDuration;
2094                } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2095                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2096                    e.Init(); // clear IDs
2097                    e.Type = Event::type_note_synth_param;
2098                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2099                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2100                    e.Param.NoteSynthParam.Delta    = fDuration;
2101                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2102    
2103                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
2104                }
2105                // and finally schedule a "volume" change, simply one time slice
2106                // ahead, with the final fade in volume (1.0)
2107                {
2108                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2109                    e.Init(); // clear IDs
2110                    e.Type = Event::type_note_synth_param;
2111                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2112                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2113                    e.Param.NoteSynthParam.Delta    = 1.f;
2114                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2115    
2116                    // scheduling with 0 delay would also work here, but +1 is more
2117                    // safe regarding potential future implementation changes of the
2118                    // scheduler (see API comments of RTAVLTree::insert())
2119                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2120                }
2121            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2122                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2123                for (vmint i = 0; i < ids->arraySize(); ++i) {
2124                    const ScriptID id = ids->evalIntElement(i);
2125                    if (!id || !id.isNoteID()) continue;
2126    
2127                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2128                    if (!pNote) continue;
2129    
2130                    // if fade_in() was called immediately after note was triggered
2131                    // then immediately apply a start volume of zero to Note object,
2132                    // as well as the fade in duration
2133                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2134                        pNote->Override.Volume.Value = 0.f;
2135                        pNote->Override.VolumeTime = fDuration;
2136                    } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2137                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2138                        e.Init(); // clear IDs
2139                        e.Type = Event::type_note_synth_param;
2140                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2141                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2142                        e.Param.NoteSynthParam.Delta    = fDuration;
2143                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2144    
2145                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2146                    }
2147                    // and finally schedule a "volume" change, simply one time slice
2148                    // ahead, with the final fade in volume (1.0)
2149                    {
2150                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2151                        e.Init(); // clear IDs
2152                        e.Type = Event::type_note_synth_param;
2153                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2154                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2155                        e.Param.NoteSynthParam.Delta    = 1.f;
2156                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2157    
2158                        // scheduling with 0 delay would also work here, but +1 is more
2159                        // safe regarding potential future implementation changes of the
2160                        // scheduler (see API comments of RTAVLTree::insert())
2161                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2162                    }
2163                }
2164            }
2165    
2166            return successResult();
2167        }
2168    
2169        // fade_out() function
2170    
2171        InstrumentScriptVMFunction_fade_out::InstrumentScriptVMFunction_fade_out(InstrumentScriptVM* parent)
2172            : m_vm(parent)
2173        {
2174        }
2175    
2176        bool InstrumentScriptVMFunction_fade_out::acceptsArgType(vmint iArg, ExprType_t type) const {
2177            if (iArg == 0)
2178                return type == INT_EXPR || type == INT_ARR_EXPR;
2179            else
2180                return type == INT_EXPR || type == REAL_EXPR;
2181        }
2182    
2183        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2184            if (iArg == 1)
2185                return type == VM_NO_UNIT || type == VM_SECOND;
2186            else
2187                return type == VM_NO_UNIT;
2188        }
2189    
2190        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2191            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2192        }
2193    
2194        VMFnResult* InstrumentScriptVMFunction_fade_out::exec(VMFnArgs* args) {
2195            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2196            vmint duration =
2197                (unit) ?
2198                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2199                    args->arg(1)->asNumber()->evalCastInt();
2200            if (duration < 0) {
2201                wrnMsg("fade_out(): argument 2 may not be negative");
2202                duration = 0;
2203            }
2204            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2205    
2206            bool stop = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : true;
2207    
2208            AbstractEngineChannel* pEngineChannel =
2209                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2210    
2211            if (args->arg(0)->exprType() == INT_EXPR) {
2212                const ScriptID id = args->arg(0)->asInt()->evalInt();
2213                if (!id) {
2214                    wrnMsg("fade_out(): note ID for argument 1 may not be zero");
2215                    return successResult();
2216                }
2217                if (!id.isNoteID()) {
2218                    wrnMsg("fade_out(): argument 1 is not a note ID");
2219                    return successResult();
2220                }
2221    
2222                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2223                if (!pNote) return successResult();
2224    
2225                // if fade_out() was called immediately after note was triggered
2226                // then immediately apply fade out duration to Note object
2227                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2228                    pNote->Override.VolumeTime = fDuration;
2229                } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2230                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2231                    e.Init(); // clear IDs
2232                    e.Type = Event::type_note_synth_param;
2233                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2234                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2235                    e.Param.NoteSynthParam.Delta    = fDuration;
2236                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2237    
2238                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
2239                }
2240                // now schedule a "volume" change, simply one time slice ahead, with
2241                // the final fade out volume (0.0)
2242                {
2243                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2244                    e.Init(); // clear IDs
2245                    e.Type = Event::type_note_synth_param;
2246                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2247                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2248                    e.Param.NoteSynthParam.Delta    = 0.f;
2249                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2250    
2251                    // scheduling with 0 delay would also work here, but +1 is more
2252                    // safe regarding potential future implementation changes of the
2253                    // scheduler (see API comments of RTAVLTree::insert())
2254                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2255                }
2256                // and finally if stopping the note was requested after the fade out
2257                // completed, then schedule to kill the voice after the requested
2258                // duration
2259                if (stop) {
2260                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2261                    e.Init(); // clear IDs
2262                    e.Type = Event::type_kill_note;
2263                    e.Param.Note.ID = id.noteID();
2264                    e.Param.Note.Key = pNote->hostKey;
2265    
2266                    pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2267                }
2268            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2269                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2270                for (vmint i = 0; i < ids->arraySize(); ++i) {
2271                    const ScriptID id = ids->evalIntElement(i);
2272                    if (!id || !id.isNoteID()) continue;
2273    
2274                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2275                    if (!pNote) continue;
2276    
2277                    // if fade_out() was called immediately after note was triggered
2278                    // then immediately apply fade out duration to Note object
2279                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2280                        pNote->Override.VolumeTime = fDuration;
2281                    } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2282                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2283                        e.Init(); // clear IDs
2284                        e.Type = Event::type_note_synth_param;
2285                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2286                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2287                        e.Param.NoteSynthParam.Delta    = fDuration;
2288                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2289    
2290                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2291                    }
2292                    // now schedule a "volume" change, simply one time slice ahead, with
2293                    // the final fade out volume (0.0)
2294                    {
2295                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2296                        e.Init(); // clear IDs
2297                        e.Type = Event::type_note_synth_param;
2298                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2299                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2300                        e.Param.NoteSynthParam.Delta    = 0.f;
2301                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2302    
2303                        // scheduling with 0 delay would also work here, but +1 is more
2304                        // safe regarding potential future implementation changes of the
2305                        // scheduler (see API comments of RTAVLTree::insert())
2306                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2307                    }
2308                    // and finally if stopping the note was requested after the fade out
2309                    // completed, then schedule to kill the voice after the requested
2310                    // duration
2311                    if (stop) {
2312                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2313                        e.Init(); // clear IDs
2314                        e.Type = Event::type_kill_note;
2315                        e.Param.Note.ID = id.noteID();
2316                        e.Param.Note.Key = pNote->hostKey;
2317                        
2318                        pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2319                    }
2320                }
2321            }
2322    
2323            return successResult();
2324        }
2325    
2326        // get_event_par() function
2327    
2328        InstrumentScriptVMFunction_get_event_par::InstrumentScriptVMFunction_get_event_par(InstrumentScriptVM* parent)
2329            : m_vm(parent)
2330        {
2331        }
2332    
2333        VMFnResult* InstrumentScriptVMFunction_get_event_par::exec(VMFnArgs* args) {
2334            AbstractEngineChannel* pEngineChannel =
2335                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2336    
2337            const ScriptID id = args->arg(0)->asInt()->evalInt();
2338            if (!id) {
2339                wrnMsg("get_event_par(): note ID for argument 1 may not be zero");
2340                return successResult(0);
2341            }
2342            if (!id.isNoteID()) {
2343                wrnMsg("get_event_par(): argument 1 is not a note ID");
2344                return successResult(0);
2345            }
2346    
2347            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2348            if (!pNote) {
2349                wrnMsg("get_event_par(): no note alive with that note ID of argument 1");
2350                return successResult(0);
2351            }
2352    
2353            const vmint parameter = args->arg(1)->asInt()->evalInt();
2354            switch (parameter) {
2355                case EVENT_PAR_NOTE:
2356                    return successResult(pNote->cause.Param.Note.Key);
2357                case EVENT_PAR_VELOCITY:
2358                    return successResult(pNote->cause.Param.Note.Velocity);
2359                case EVENT_PAR_VOLUME:
2360                    return successResult(
2361                        RTMath::LinRatioToDecibel(pNote->Override.Volume.Value) * 1000.f
2362                    );
2363                case EVENT_PAR_TUNE:
2364                    return successResult(
2365                         RTMath::FreqRatioToCents(pNote->Override.Pitch.Value) * 1000.f
2366                    );
2367                case EVENT_PAR_0:
2368                    return successResult(pNote->userPar[0]);
2369                case EVENT_PAR_1:
2370                    return successResult(pNote->userPar[1]);
2371                case EVENT_PAR_2:
2372                    return successResult(pNote->userPar[2]);
2373                case EVENT_PAR_3:
2374                    return successResult(pNote->userPar[3]);
2375            }
2376    
2377            wrnMsg("get_event_par(): argument 2 is an invalid event parameter");
2378            return successResult(0);
2379        }
2380    
2381        // set_event_par() function
2382    
2383        InstrumentScriptVMFunction_set_event_par::InstrumentScriptVMFunction_set_event_par(InstrumentScriptVM* parent)
2384            : m_vm(parent)
2385        {
2386        }
2387    
2388        VMFnResult* InstrumentScriptVMFunction_set_event_par::exec(VMFnArgs* args) {
2389            AbstractEngineChannel* pEngineChannel =
2390                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2391    
2392            const ScriptID id = args->arg(0)->asInt()->evalInt();
2393            if (!id) {
2394                wrnMsg("set_event_par(): note ID for argument 1 may not be zero");
2395                return successResult();
2396            }
2397            if (!id.isNoteID()) {
2398                wrnMsg("set_event_par(): argument 1 is not a note ID");
2399                return successResult();
2400            }
2401    
2402            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2403            if (!pNote) return successResult();
2404    
2405            const vmint parameter = args->arg(1)->asInt()->evalInt();
2406            const vmint value     = args->arg(2)->asInt()->evalInt();
2407    
2408            switch (parameter) {
2409                case EVENT_PAR_NOTE:
2410                    if (value < 0 || value > 127) {
2411                        wrnMsg("set_event_par(): note number of argument 3 is out of range");
2412                        return successResult();
2413                    }
2414                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2415                        pNote->cause.Param.Note.Key = value;
2416                        m_vm->m_event->cause.Param.Note.Key = value;
2417                    } else {
2418                        wrnMsg("set_event_par(): note number can only be changed when note is new");
2419                    }
2420                    return successResult();
2421                case EVENT_PAR_VELOCITY:
2422                    if (value < 0 || value > 127) {
2423                        wrnMsg("set_event_par(): velocity of argument 3 is out of range");
2424                        return successResult();
2425                    }
2426                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2427                        pNote->cause.Param.Note.Velocity = value;
2428                        m_vm->m_event->cause.Param.Note.Velocity = value;
2429                    } else {
2430                        wrnMsg("set_event_par(): velocity can only be changed when note is new");
2431                    }
2432                    return successResult();
2433                case EVENT_PAR_VOLUME:
2434                    wrnMsg("set_event_par(): changing volume by this function is currently not supported, use change_vol() instead");
2435                    return successResult();
2436                case EVENT_PAR_TUNE:
2437                    wrnMsg("set_event_par(): changing tune by this function is currently not supported, use change_tune() instead");
2438                    return successResult();
2439                case EVENT_PAR_0:
2440                    pNote->userPar[0] = value;
2441                    return successResult();
2442                case EVENT_PAR_1:
2443                    pNote->userPar[1] = value;
2444                    return successResult();
2445                case EVENT_PAR_2:
2446                    pNote->userPar[2] = value;
2447                    return successResult();
2448                case EVENT_PAR_3:
2449                    pNote->userPar[3] = value;
2450                    return successResult();
2451            }
2452    
2453            wrnMsg("set_event_par(): argument 2 is an invalid event parameter");
2454            return successResult();
2455        }
2456    
2457        // change_note() function
2458    
2459        InstrumentScriptVMFunction_change_note::InstrumentScriptVMFunction_change_note(InstrumentScriptVM* parent)
2460        : m_vm(parent)
2461        {
2462        }
2463    
2464        VMFnResult* InstrumentScriptVMFunction_change_note::exec(VMFnArgs* args) {
2465            AbstractEngineChannel* pEngineChannel =
2466                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2467    
2468            const ScriptID id = args->arg(0)->asInt()->evalInt();
2469            if (!id) {
2470                wrnMsg("change_note(): note ID for argument 1 may not be zero");
2471                return successResult();
2472            }
2473            if (!id.isNoteID()) {
2474                wrnMsg("change_note(): argument 1 is not a note ID");
2475                return successResult();
2476            }
2477    
2478            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2479            if (!pNote) return successResult();
2480    
2481            const vmint value = args->arg(1)->asInt()->evalInt();
2482            if (value < 0 || value > 127) {
2483                wrnMsg("change_note(): note number of argument 2 is out of range");
2484                return successResult();
2485            }
2486    
2487            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2488                pNote->cause.Param.Note.Key = value;
2489                m_vm->m_event->cause.Param.Note.Key = value;
2490            } else {
2491                wrnMsg("change_note(): note number can only be changed when note is new");
2492            }
2493    
2494            return successResult();
2495        }
2496    
2497        // change_velo() function
2498    
2499        InstrumentScriptVMFunction_change_velo::InstrumentScriptVMFunction_change_velo(InstrumentScriptVM* parent)
2500        : m_vm(parent)
2501        {
2502        }
2503    
2504        VMFnResult* InstrumentScriptVMFunction_change_velo::exec(VMFnArgs* args) {
2505            AbstractEngineChannel* pEngineChannel =
2506                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2507    
2508            const ScriptID id = args->arg(0)->asInt()->evalInt();
2509            if (!id) {
2510                wrnMsg("change_velo(): note ID for argument 1 may not be zero");
2511                return successResult();
2512            }
2513            if (!id.isNoteID()) {
2514                wrnMsg("change_velo(): argument 1 is not a note ID");
2515                return successResult();
2516            }
2517    
2518            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2519            if (!pNote) return successResult();
2520    
2521            const vmint value = args->arg(1)->asInt()->evalInt();
2522            if (value < 0 || value > 127) {
2523                wrnMsg("change_velo(): velocity of argument 2 is out of range");
2524                return successResult();
2525            }
2526    
2527            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2528                pNote->cause.Param.Note.Velocity = value;
2529                m_vm->m_event->cause.Param.Note.Velocity = value;
2530            } else {
2531                wrnMsg("change_velo(): velocity can only be changed when note is new");
2532            }
2533    
2534            return successResult();
2535        }
2536    
2537        // change_play_pos() function
2538    
2539        InstrumentScriptVMFunction_change_play_pos::InstrumentScriptVMFunction_change_play_pos(InstrumentScriptVM* parent)
2540            : m_vm(parent)
2541        {
2542        }
2543    
2544        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgType(vmint iArg, ExprType_t type) const {
2545            if (iArg == 0)
2546                return type == INT_EXPR;
2547            else
2548                return type == INT_EXPR || type == REAL_EXPR;
2549        }
2550    
2551        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2552            if (iArg == 1)
2553                return type == VM_NO_UNIT || type == VM_SECOND;
2554            else
2555                return type == VM_NO_UNIT;
2556        }
2557    
2558        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2559            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2560        }
2561    
2562        VMFnResult* InstrumentScriptVMFunction_change_play_pos::exec(VMFnArgs* args) {
2563            const ScriptID id = args->arg(0)->asInt()->evalInt();
2564            if (!id) {
2565                wrnMsg("change_play_pos(): note ID for argument 1 may not be zero");
2566                return successResult();
2567            }
2568            if (!id.isNoteID()) {
2569                wrnMsg("change_play_pos(): argument 1 is not a note ID");
2570                return successResult();
2571            }
2572    
2573            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2574            const vmint pos =
2575                (unit) ?
2576                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2577                    args->arg(1)->asNumber()->evalCastInt();
2578            if (pos < 0) {
2579                wrnMsg("change_play_pos(): playback position of argument 2 may not be negative");
2580                return successResult();
2581            }
2582    
2583            AbstractEngineChannel* pEngineChannel =
2584                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2585    
2586            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2587            if (!pNote) return successResult();
2588    
2589            pNote->Override.SampleOffset =
2590                (decltype(pNote->Override.SampleOffset)) pos;
2591    
2592            return successResult();
2593        }
2594    
2595        // event_status() function
2596    
2597        InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)
2598            : m_vm(parent)
2599        {
2600        }
2601    
2602        VMFnResult* InstrumentScriptVMFunction_event_status::exec(VMFnArgs* args) {
2603            AbstractEngineChannel* pEngineChannel =
2604                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2605    
2606            const ScriptID id = args->arg(0)->asInt()->evalInt();
2607            if (!id) {
2608                wrnMsg("event_status(): note ID for argument 1 may not be zero");
2609                return successResult(EVENT_STATUS_INACTIVE);
2610            }
2611            if (!id.isNoteID()) {
2612                wrnMsg("event_status(): argument 1 is not a note ID");
2613                return successResult(EVENT_STATUS_INACTIVE);
2614            }
2615    
2616            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2617            return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);
2618        }
2619    
2620        // callback_status() function
2621    
2622        InstrumentScriptVMFunction_callback_status::InstrumentScriptVMFunction_callback_status(InstrumentScriptVM* parent)
2623            : m_vm(parent)
2624        {
2625        }
2626    
2627        VMFnResult* InstrumentScriptVMFunction_callback_status::exec(VMFnArgs* args) {
2628            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2629            if (!id) {
2630                wrnMsg("callback_status(): callback ID for argument 1 may not be zero");
2631                return successResult();
2632            }
2633    
2634            AbstractEngineChannel* pEngineChannel =
2635                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2636    
2637            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2638            if (!itCallback)
2639                return successResult(CALLBACK_STATUS_TERMINATED);
2640    
2641            return successResult(
2642                (m_vm->m_event->execCtx == itCallback->execCtx) ?
2643                    CALLBACK_STATUS_RUNNING : CALLBACK_STATUS_QUEUE
2644            );
2645        }
2646    
2647        // wait() function (overrides core wait() implementation)
2648    
2649        InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)
2650            : CoreVMFunction_wait(parent)
2651        {    
2652        }
2653    
2654        VMFnResult* InstrumentScriptVMFunction_wait::exec(VMFnArgs* args) {
2655            InstrumentScriptVM* m_vm = (InstrumentScriptVM*) vm;
2656    
2657            // this might be set by passing 1 with the 2nd argument of built-in stop_wait() function
2658            if (m_vm->m_event->ignoreAllWaitCalls) return successResult();
2659    
2660            return CoreVMFunction_wait::exec(args);
2661        }
2662    
2663        // stop_wait() function
2664    
2665        InstrumentScriptVMFunction_stop_wait::InstrumentScriptVMFunction_stop_wait(InstrumentScriptVM* parent)
2666            : m_vm(parent)
2667        {    
2668        }
2669    
2670        VMFnResult* InstrumentScriptVMFunction_stop_wait::exec(VMFnArgs* args) {
2671            AbstractEngineChannel* pEngineChannel =
2672                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2673    
2674            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2675            if (!id) {
2676                wrnMsg("stop_wait(): callback ID for argument 1 may not be zero");
2677                return successResult();
2678            }
2679    
2680            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2681            if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2682    
2683            const bool disableWaitForever =
2684                (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;
2685    
2686            pEngineChannel->ScheduleResumeOfScriptCallback(
2687                itCallback, m_vm->m_event->scheduleTime, disableWaitForever
2688            );
2689    
2690            return successResult();
2691        }
2692    
2693        // abort() function
2694    
2695        InstrumentScriptVMFunction_abort::InstrumentScriptVMFunction_abort(InstrumentScriptVM* parent)
2696            : m_vm(parent)
2697        {
2698        }
2699    
2700        VMFnResult* InstrumentScriptVMFunction_abort::exec(VMFnArgs* args) {
2701            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2702            if (!id) {
2703                wrnMsg("abort(): callback ID for argument 1 may not be zero");
2704                return successResult();
2705            }
2706    
2707            AbstractEngineChannel* pEngineChannel =
2708                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2709    
2710            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2711            if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2712    
2713            itCallback->execCtx->signalAbort();
2714    
2715            return successResult();
2716        }
2717    
2718        // fork() function
2719    
2720        InstrumentScriptVMFunction_fork::InstrumentScriptVMFunction_fork(InstrumentScriptVM* parent)
2721            : m_vm(parent)
2722        {
2723        }
2724    
2725        VMFnResult* InstrumentScriptVMFunction_fork::exec(VMFnArgs* args) {
2726            // check if this is actually the parent going to fork, or rather one of
2727            // the children which is already forked
2728            if (m_vm->m_event->forkIndex != 0) { // this is the entry point for a child ...
2729                int forkResult = m_vm->m_event->forkIndex;
2730                // reset so that this child may i.e. also call fork() later on
2731                m_vm->m_event->forkIndex = 0;
2732                return successResult(forkResult);
2733            }
2734    
2735            // if we are here, then this is the parent, so we must fork this parent
2736    
2737            const vmint n =
2738                (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : 1;
2739            const bool bAutoAbort =
2740                (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : true;
2741    
2742            if (m_vm->m_event->countChildHandlers() + n > MAX_FORK_PER_SCRIPT_HANDLER) {
2743                wrnMsg("fork(): requested amount would exceed allowed limit per event handler");
2744                return successResult(-1);
2745            }
2746    
2747            AbstractEngineChannel* pEngineChannel =
2748                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2749    
2750            if (!pEngineChannel->hasFreeScriptCallbacks(n)) {
2751                wrnMsg("fork(): global limit of event handlers exceeded");
2752                return successResult(-1);
2753            }
2754    
2755            for (int iChild = 0; iChild < n; ++iChild) {
2756                RTList<ScriptEvent>::Iterator itChild =
2757                    pEngineChannel->forkScriptCallback(m_vm->m_event, bAutoAbort);
2758                if (!itChild) { // should never happen, otherwise its a bug ...
2759                    errMsg("fork(): internal error while allocating child");
2760                    return errorResult(-1); // terminate script
2761                }
2762                // since both parent, as well all child script execution instances
2763                // all land in this exec() method, the following is (more or less)
2764                // the only feature that lets us distinguish the parent and
2765                // respective children from each other in this exec() method
2766                itChild->forkIndex = iChild + 1;
2767            }
2768    
2769            return successResult(0);
2770        }
2771    
2772  } // namespace LinuxSampler  } // namespace LinuxSampler

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

  ViewVC Help
Powered by ViewVC