/[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 2948 by schoenebeck, Fri Jul 15 15:29:04 2016 UTC revision 3691 by schoenebeck, Fri Jan 3 12:35:20 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            
# Line 20  namespace LinuxSampler { Line 21  namespace LinuxSampler {
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 36  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 63  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 sample offset is supplied, assign the offset as override
142            // to the previously created Note object
143            if (args->argsCount() >= 3) {
144                VMNumberExpr* argSampleOffset = args->arg(2)->asNumber();
145                vmint sampleoffset =
146                    (argSampleOffset->unitType()) ?
147                        argSampleOffset->evalCastInt(VM_MICRO) :
148                        argSampleOffset->evalCastInt();
149                if (sampleoffset >= 0) {
150                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID(id);
151                    if (pNote) {
152                        pNote->Override.SampleOffset =
153                            (decltype(pNote->Override.SampleOffset)) sampleoffset;
154                    }
155                } else if (sampleoffset < -1) {
156                    errMsg("play_note(): sample offset of argument 3 may not be less than -1");
157                }
158            }
159    
160          // if a duration is supplied (and play-note event was scheduled          // if a duration is supplied (and play-note event was scheduled
161          // successfully above), then schedule a subsequent stop-note event          // successfully above), then schedule a subsequent stop-note event
162          if (id && duration > 0) {          if (id && duration > 0) {
# Line 90  namespace LinuxSampler { Line 180  namespace LinuxSampler {
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 121  namespace LinuxSampler { Line 211  namespace LinuxSampler {
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      // ignore_event() function
289    
290      InstrumentScriptVMFunction_ignore_event::InstrumentScriptVMFunction_ignore_event(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_ignore_event::InstrumentScriptVMFunction_ignore_event(InstrumentScriptVM* parent)
# Line 128  namespace LinuxSampler { Line 292  namespace LinuxSampler {
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 136  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 184  namespace LinuxSampler { Line 348  namespace LinuxSampler {
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 223  namespace LinuxSampler { Line 404  namespace LinuxSampler {
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 252  namespace LinuxSampler { Line 433  namespace LinuxSampler {
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 290  namespace LinuxSampler { Line 488  namespace LinuxSampler {
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 314  namespace LinuxSampler { Line 529  namespace LinuxSampler {
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 334  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 355  namespace LinuxSampler { Line 587  namespace LinuxSampler {
587      {      {
588      }      }
589    
590      bool InstrumentScriptVMFunction_change_vol::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_vol::acceptsArgType(vmint iArg, ExprType_t type) const {
591          if (iArg == 0)          if (iArg == 0)
592              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
593            else if (iArg == 1)
594                return type == INT_EXPR || type == REAL_EXPR;
595          else          else
596              return INT_EXPR;              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) {      VMFnResult* InstrumentScriptVMFunction_change_vol::exec(VMFnArgs* args) {
615          int volume = args->arg(1)->asInt()->evalInt(); // volume change in milli dB          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;          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 =          AbstractEngineChannel* pEngineChannel =
625              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 383  namespace LinuxSampler { Line 638  namespace LinuxSampler {
638              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
639              if (!pNote) return successResult();              if (!pNote) return successResult();
640    
641              const float fVolumeLin = RTMath::DecibelToLinRatio(float(volume) / 1000.f);              // if change_vol() was called immediately after note was triggered
642              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the volume to note object, but only if
643              /*if (relative)              // change_vol_time() has not been called before
644                  pNote->Override.Volume *= fVolumeLin;              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
645              else                  pNote->Override.VolumeTime <= DEFAULT_NOTE_VOLUME_TIME_S)
646                  pNote->Override.Volume = fVolumeLin;*/              {
647                    if (relative)
648              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                      pNote->Override.Volume.Value *= fVolumeLin;
             e.Init(); // clear IDs  
             e.Type = Event::type_note_synth_param;  
             e.Param.NoteSynthParam.NoteID   = id.noteID();  
             e.Param.NoteSynthParam.Type     = Event::synth_param_volume;  
             e.Param.NoteSynthParam.Delta    = fVolumeLin;  
             e.Param.NoteSynthParam.Relative = relative;  
   
             pEngineChannel->ScheduleEventMicroSec(&e, 0);  
         } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {  
             VMIntArrayExpr* ids = args->arg(0)->asIntArray();  
             for (int i = 0; i < ids->arraySize(); ++i) {  
                 const ScriptID id = ids->evalIntElement(i);  
                 if (!id || !id.isNoteID()) continue;  
   
                 NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );  
                 if (!pNote) continue;  
   
                 const float fVolumeLin = RTMath::DecibelToLinRatio(float(volume) / 1000.f);  
                 // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior  
                 /*if (relative)  
                     pNote->Override.Volume *= fVolumeLin;  
649                  else                  else
650                      pNote->Override.Volume = fVolumeLin;*/                      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"                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
654                  e.Init(); // clear IDs                  e.Init(); // clear IDs
655                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
656                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
657                  e.Param.NoteSynthParam.Type     = Event::synth_param_volume;                  e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
658                  e.Param.NoteSynthParam.Delta    = fVolumeLin;                  e.Param.NoteSynthParam.Delta    = fVolumeLin;
659                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
660                        isFinal, relative, unit
661                    );
662                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  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();          return successResult();
# Line 437  namespace LinuxSampler { Line 706  namespace LinuxSampler {
706      {      {
707      }      }
708    
709      bool InstrumentScriptVMFunction_change_tune::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_tune::acceptsArgType(vmint iArg, ExprType_t type) const {
710          if (iArg == 0)          if (iArg == 0)
711              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
712            else if (iArg == 1)
713                return type == INT_EXPR || type == REAL_EXPR;
714          else          else
715              return INT_EXPR;              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) {      VMFnResult* InstrumentScriptVMFunction_change_tune::exec(VMFnArgs* args) {
727          int tune = args->arg(1)->asInt()->evalInt(); // tuning change in milli cents          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;          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 =          AbstractEngineChannel* pEngineChannel =
737              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 465  namespace LinuxSampler { Line 750  namespace LinuxSampler {
750              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
751              if (!pNote) return successResult();              if (!pNote) return successResult();
752    
753              const float fFreqRatio = RTMath::CentsToFreqRatioUnlimited(float(tune) / 1000.f);              // if change_tune() was called immediately after note was triggered
754              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the tuning to Note object, but only if
755              /*if (relative)              // change_tune_time() has not been called before
756                  pNote->Override.Pitch *= fFreqRatio;              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
757              else                  pNote->Override.PitchTime <= DEFAULT_NOTE_PITCH_TIME_S)
758                  pNote->Override.Pitch = fFreqRatio;*/              {
759                    if (relative)
760              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                      pNote->Override.Pitch.Value *= fFreqRatio;
             e.Init(); // clear IDs  
             e.Type = Event::type_note_synth_param;  
             e.Param.NoteSynthParam.NoteID   = id.noteID();  
             e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;  
             e.Param.NoteSynthParam.Delta    = fFreqRatio;  
             e.Param.NoteSynthParam.Relative = relative;  
   
             pEngineChannel->ScheduleEventMicroSec(&e, 0);  
         } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {  
             VMIntArrayExpr* ids = args->arg(0)->asIntArray();  
             for (int i = 0; i < ids->arraySize(); ++i) {  
                 const ScriptID id = ids->evalIntElement(i);  
                 if (!id || !id.isNoteID()) continue;  
   
                 NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );  
                 if (!pNote) continue;  
   
                 const float fFreqRatio = RTMath::CentsToFreqRatioUnlimited(float(tune) / 1000.f);  
                 // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior  
                 /*if (relative)  
                     pNote->Override.Pitch *= fFreqRatio;  
761                  else                  else
762                      pNote->Override.Pitch = fFreqRatio;*/                      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"                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
766                  e.Init(); // clear IDs                  e.Init(); // clear IDs
767                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
768                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
769                  e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;                  e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;
770                  e.Param.NoteSynthParam.Delta    = fFreqRatio;                  e.Param.NoteSynthParam.Delta    = fFreqRatio;
771                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
772                        isFinal, relative, unit
773                    );
774                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  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();          return successResult();
# Line 519  namespace LinuxSampler { Line 818  namespace LinuxSampler {
818      {      {
819      }      }
820    
821      bool InstrumentScriptVMFunction_change_pan::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_pan::acceptsArgType(vmint iArg, ExprType_t type) const {
822          if (iArg == 0)          if (iArg == 0)
823              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
824          else          else
825              return INT_EXPR;              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) {      VMFnResult* InstrumentScriptVMFunction_change_pan::exec(VMFnArgs* args) {
833          int pan = args->arg(1)->asInt()->evalInt();          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;          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
836    
837          if (pan > 1000) {          if (pan > 1000) {
# Line 537  namespace LinuxSampler { Line 841  namespace LinuxSampler {
841              wrnMsg("change_pan(): argument 2 may not be smaller than -1000");              wrnMsg("change_pan(): argument 2 may not be smaller than -1000");
842              pan = -1000;              pan = -1000;
843          }          }
844            const float fPan = float(pan) / 1000.f;
845    
846          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
847              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 555  namespace LinuxSampler { Line 860  namespace LinuxSampler {
860              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
861              if (!pNote) return successResult();              if (!pNote) return successResult();
862    
863              const float fPan = float(pan) / 1000.f;              // if change_pan() was called immediately after note was triggered
864              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the panning to Note object
865              /*if (relative) {              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
866                  pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, fPan, ++pNote->Override.PanSources);                  if (relative) {
867              } else {                      pNote->Override.Pan.Value = RTMath::RelativeSummedAvg(pNote->Override.Pan.Value, fPan, ++pNote->Override.Pan.Sources);
                 pNote->Override.Pan = fPan;  
                 pNote->Override.PanSources = 1; // only relevant on subsequent change_pan() calls on same note with 'relative' being set  
             }*/  
   
             Event e = m_vm->m_event->cause; // copy to get fragment time for "now"  
             e.Init(); // clear IDs  
             e.Type = Event::type_note_synth_param;  
             e.Param.NoteSynthParam.NoteID   = id.noteID();  
             e.Param.NoteSynthParam.Type     = Event::synth_param_pan;  
             e.Param.NoteSynthParam.Delta    = fPan;  
             e.Param.NoteSynthParam.Relative = relative;  
   
             pEngineChannel->ScheduleEventMicroSec(&e, 0);  
         } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {  
             VMIntArrayExpr* ids = args->arg(0)->asIntArray();  
             for (int i = 0; i < ids->arraySize(); ++i) {  
                 const ScriptID id = ids->evalIntElement(i);  
                 if (!id || !id.isNoteID()) continue;  
   
                 NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );  
                 if (!pNote) continue;  
   
                 const float fPan = float(pan) / 1000.f;  
                 // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior  
                 /*if (relative) {  
                     pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, fPan, ++pNote->Override.PanSources);  
868                  } else {                  } else {
869                      pNote->Override.Pan = fPan;                      pNote->Override.Pan.Value = fPan;
870                      pNote->Override.PanSources = 1; // only relevant on subsequent change_pan() calls on same note with 'relative' being set                      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"                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
875                  e.Init(); // clear IDs                  e.Init(); // clear IDs
876                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
877                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
878                  e.Param.NoteSynthParam.Type     = Event::synth_param_pan;                  e.Param.NoteSynthParam.Type     = Event::synth_param_pan;
879                  e.Param.NoteSynthParam.Delta    = fPan;                  e.Param.NoteSynthParam.Delta    = fPan;
880                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
881                        isFinal, relative, false
882                    );
883                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  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();          return successResult();
920      }      }
921    
922      #define VM_FILTER_PAR_MAX_VALUE 1000000      #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      // change_cutoff() function
927    
# Line 615  namespace LinuxSampler { Line 930  namespace LinuxSampler {
930      {      {
931      }      }
932    
933      bool InstrumentScriptVMFunction_change_cutoff::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_cutoff::acceptsArgType(vmint iArg, ExprType_t type) const {
934          if (iArg == 0)          if (iArg == 0)
935              return type == INT_EXPR || type == INT_ARR_EXPR;              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          else
946              return INT_EXPR;              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) {      VMFnResult* InstrumentScriptVMFunction_change_cutoff::exec(VMFnArgs* args) {
974          int cutoff = args->arg(1)->asInt()->evalInt();          const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
975          if (cutoff > VM_FILTER_PAR_MAX_VALUE) {          vmint cutoff =
976              wrnMsg("change_cutoff(): argument 2 may not be larger than 1000000");              (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            if (!unit && cutoff > VM_FILTER_PAR_MAX_VALUE) {
984                wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_VALUE));
985              cutoff = VM_FILTER_PAR_MAX_VALUE;              cutoff = VM_FILTER_PAR_MAX_VALUE;
986            } else if (unit && cutoff > VM_FILTER_PAR_MAX_HZ) {
987                wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_HZ) " Hz");
988                cutoff = VM_FILTER_PAR_MAX_HZ;
989          } else if (cutoff < 0) {          } else if (cutoff < 0) {
990              wrnMsg("change_cutoff(): argument 2 may not be negative");              wrnMsg("change_cutoff(): argument 2 may not be negative");
991              cutoff = 0;              cutoff = 0;
992          }          }
993            const float fCutoff =
994                (unit) ? cutoff : float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);
995    
996          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
997              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 649  namespace LinuxSampler { Line 1010  namespace LinuxSampler {
1010              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1011              if (!pNote) return successResult();              if (!pNote) return successResult();
1012    
1013              const float fCutoff = float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);              // if change_cutoff() was called immediately after note was triggered
1014                // then immediately apply cutoff to Note object
1015              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1016              e.Init(); // clear IDs                  pNote->Override.Cutoff.Value = fCutoff;
1017              e.Type = Event::type_note_synth_param;                  pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1018              e.Param.NoteSynthParam.NoteID   = id.noteID();              } else { // otherwise schedule cutoff change ...
1019              e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1020              e.Param.NoteSynthParam.Delta    = fCutoff;                  e.Init(); // clear IDs
1021              e.Param.NoteSynthParam.Relative = false;                  e.Type = Event::type_note_synth_param;
1022                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1023              pEngineChannel->ScheduleEventMicroSec(&e, 0);                  e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;
1024                    e.Param.NoteSynthParam.Delta    = fCutoff;
1025                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1026                        isFinal, false, unit
1027                    );
1028                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1029                }
1030          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1031              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1032              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1033                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1034                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1035    
1036                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1037                  if (!pNote) continue;                  if (!pNote) continue;
1038    
1039                  const float fCutoff = float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);                  // if change_cutoff() was called immediately after note was triggered
1040                    // then immediately apply cutoff to Note object
1041                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1042                  e.Init(); // clear IDs                      pNote->Override.Cutoff.Value = fCutoff;
1043                  e.Type = Event::type_note_synth_param;                      pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1044                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  } else { // otherwise schedule cutoff change ...
1045                  e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;                      Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1046                  e.Param.NoteSynthParam.Delta    = fCutoff;                      e.Init(); // clear IDs
1047                  e.Param.NoteSynthParam.Relative = false;                      e.Type = Event::type_note_synth_param;
1048                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1049                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                      e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;
1050                        e.Param.NoteSynthParam.Delta    = fCutoff;
1051                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1052                            isFinal, false, unit
1053                        );
1054                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1055                    }
1056              }              }
1057          }          }
1058    
# Line 693  namespace LinuxSampler { Line 1066  namespace LinuxSampler {
1066      {      {
1067      }      }
1068    
1069      bool InstrumentScriptVMFunction_change_reso::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_reso::acceptsArgType(vmint iArg, ExprType_t type) const {
1070          if (iArg == 0)          if (iArg == 0)
1071              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
1072          else          else
1073              return INT_EXPR;              return type == INT_EXPR;
1074        }
1075    
1076        bool InstrumentScriptVMFunction_change_reso::acceptsArgFinal(vmint iArg) const {
1077            return iArg == 1;
1078      }      }
1079    
1080      VMFnResult* InstrumentScriptVMFunction_change_reso::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_reso::exec(VMFnArgs* args) {
1081          int resonance = args->arg(1)->asInt()->evalInt();          vmint resonance = args->arg(1)->asInt()->evalInt();
1082            bool isFinal    = args->arg(1)->asInt()->isFinal();
1083          if (resonance > VM_FILTER_PAR_MAX_VALUE) {          if (resonance > VM_FILTER_PAR_MAX_VALUE) {
1084              wrnMsg("change_reso(): argument 2 may not be larger than 1000000");              wrnMsg("change_reso(): argument 2 may not be larger than 1000000");
1085              resonance = VM_FILTER_PAR_MAX_VALUE;              resonance = VM_FILTER_PAR_MAX_VALUE;
# Line 709  namespace LinuxSampler { Line 1087  namespace LinuxSampler {
1087              wrnMsg("change_reso(): argument 2 may not be negative");              wrnMsg("change_reso(): argument 2 may not be negative");
1088              resonance = 0;              resonance = 0;
1089          }          }
1090            const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);
1091    
1092          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
1093              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 727  namespace LinuxSampler { Line 1106  namespace LinuxSampler {
1106              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1107              if (!pNote) return successResult();              if (!pNote) return successResult();
1108    
1109              const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);              // if change_reso() was called immediately after note was triggered
1110                // then immediately apply resonance to Note object
1111                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1112                    pNote->Override.Resonance.Value = fResonance;
1113                    pNote->Override.Resonance.Final = isFinal;
1114                } else { // otherwise schedule resonance change ...
1115                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1116                    e.Init(); // clear IDs
1117                    e.Type = Event::type_note_synth_param;
1118                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1119                    e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;
1120                    e.Param.NoteSynthParam.Delta    = fResonance;
1121                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1122                        isFinal, false, false
1123                    );
1124                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1125                }
1126            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1127                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1128                for (vmint i = 0; i < ids->arraySize(); ++i) {
1129                    const ScriptID id = ids->evalIntElement(i);
1130                    if (!id || !id.isNoteID()) continue;
1131    
1132              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1133              e.Init(); // clear IDs                  if (!pNote) continue;
             e.Type = Event::type_note_synth_param;  
             e.Param.NoteSynthParam.NoteID   = id.noteID();  
             e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;  
             e.Param.NoteSynthParam.Delta    = fResonance;  
             e.Param.NoteSynthParam.Relative = false;  
1134    
1135              pEngineChannel->ScheduleEventMicroSec(&e, 0);                  // if change_reso() was called immediately after note was triggered
1136                    // then immediately apply resonance to Note object
1137                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1138                        pNote->Override.Resonance.Value = fResonance;
1139                        pNote->Override.Resonance.Final = isFinal;
1140                    } else { // otherwise schedule resonance change ...
1141                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1142                        e.Init(); // clear IDs
1143                        e.Type = Event::type_note_synth_param;
1144                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1145                        e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;
1146                        e.Param.NoteSynthParam.Delta    = fResonance;
1147                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1148                            isFinal, false, false
1149                        );
1150                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1151                    }
1152                }
1153            }
1154    
1155            return successResult();
1156        }
1157        
1158        // change_attack() function
1159    
1160        InstrumentScriptVMFunction_change_attack::InstrumentScriptVMFunction_change_attack(InstrumentScriptVM* parent)
1161            : m_vm(parent)
1162        {
1163        }
1164    
1165        bool InstrumentScriptVMFunction_change_attack::acceptsArgType(vmint iArg, ExprType_t type) const {
1166            if (iArg == 0)
1167                return type == INT_EXPR || type == INT_ARR_EXPR;
1168            else
1169                return type == INT_EXPR || type == REAL_EXPR;
1170        }
1171    
1172        bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1173            if (iArg == 1)
1174                return type == VM_NO_UNIT || type == VM_SECOND;
1175            else
1176                return type == VM_NO_UNIT;
1177        }
1178    
1179        bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1180            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1181        }
1182    
1183        bool InstrumentScriptVMFunction_change_attack::acceptsArgFinal(vmint iArg) const {
1184            return iArg == 1;
1185        }
1186    
1187        void InstrumentScriptVMFunction_change_attack::checkArgs(VMFnArgs* args,
1188                                                                 std::function<void(String)> err,
1189                                                                 std::function<void(String)> wrn)
1190        {
1191            // super class checks
1192            Super::checkArgs(args, err, wrn);
1193    
1194            // own checks ...
1195            if (args->argsCount() >= 2) {
1196                VMNumberExpr* argTime = args->arg(1)->asNumber();
1197                if (argTime->unitType() && !argTime->isFinal()) {
1198                    wrn("Argument 2 implies 'final' value when using seconds as unit for attack time.");
1199                }
1200            }
1201        }
1202    
1203        VMFnResult* InstrumentScriptVMFunction_change_attack::exec(VMFnArgs* args) {
1204            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1205            vmint attack =
1206                (unit) ?
1207                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1208                    args->arg(1)->asNumber()->evalCastInt();
1209            const bool isFinal =
1210                (unit) ?
1211                    true : // imply 'final' value if unit type is used
1212                    args->arg(1)->asNumber()->isFinal();
1213            // note: intentionally not checking against a max. value here!
1214            // (to allow i.e. passing 2000000 for doubling the attack time)
1215            if (attack < 0) {
1216                wrnMsg("change_attack(): argument 2 may not be negative");
1217                attack = 0;
1218            }
1219            const float fAttack =
1220                (unit) ? attack : 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) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1257              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1258              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1259                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1260                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1261    
1262                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1263                  if (!pNote) continue;                  if (!pNote) continue;
1264    
1265                  const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);                  // 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) ? decay : float(decay) / float(VM_EG_PAR_MAX_VALUE);
1351    
1352            AbstractEngineChannel* pEngineChannel =
1353                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1354    
1355            if (args->arg(0)->exprType() == INT_EXPR) {
1356                const ScriptID id = args->arg(0)->asInt()->evalInt();
1357                if (!id) {
1358                    wrnMsg("change_decay(): note ID for argument 1 may not be zero");
1359                    return successResult();
1360                }
1361                if (!id.isNoteID()) {
1362                    wrnMsg("change_decay(): argument 1 is not a note ID");
1363                    return successResult();
1364                }
1365    
1366                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1367                if (!pNote) return successResult();
1368    
1369                // if change_decay() was called immediately after note was triggered
1370                // then immediately apply decay to Note object
1371                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1372                    pNote->Override.Decay.Value = fDecay;
1373                    pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1374                } else { // otherwise schedule decay change ...
1375                  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"
1376                  e.Init(); // clear IDs                  e.Init(); // clear IDs
1377                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
1378                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
1379                  e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;                  e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1380                  e.Param.NoteSynthParam.Delta    = fResonance;                  e.Param.NoteSynthParam.Delta    = fDecay;
1381                  e.Param.NoteSynthParam.Relative = false;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1382                        isFinal, false, unit
1383                    );
1384                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1385                }
1386            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1387                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1388                for (vmint i = 0; i < ids->arraySize(); ++i) {
1389                    const ScriptID id = ids->evalIntElement(i);
1390                    if (!id || !id.isNoteID()) continue;
1391    
1392                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1393                    if (!pNote) continue;
1394    
1395                    // if change_decay() was called immediately after note was triggered
1396                    // then immediately apply decay to Note object
1397                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1398                        pNote->Override.Decay.Value = fDecay;
1399                        pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1400                    } else { // otherwise schedule decay change ...
1401                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1402                        e.Init(); // clear IDs
1403                        e.Type = Event::type_note_synth_param;
1404                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1405                        e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1406                        e.Param.NoteSynthParam.Delta    = fDecay;
1407                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1408                            isFinal, false, unit
1409                        );
1410                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1411                    }
1412                }
1413            }
1414    
1415            return successResult();
1416        }
1417    
1418        // change_release() function
1419        
1420        InstrumentScriptVMFunction_change_release::InstrumentScriptVMFunction_change_release(InstrumentScriptVM* parent)
1421            : m_vm(parent)
1422        {
1423        }
1424    
1425        bool InstrumentScriptVMFunction_change_release::acceptsArgType(vmint iArg, ExprType_t type) const {
1426            if (iArg == 0)
1427                return type == INT_EXPR || type == INT_ARR_EXPR;
1428            else
1429                return type == INT_EXPR || type == REAL_EXPR;
1430        }
1431    
1432        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1433            if (iArg == 1)
1434                return type == VM_NO_UNIT || type == VM_SECOND;
1435            else
1436                return type == VM_NO_UNIT;
1437        }
1438    
1439        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1440            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1441        }
1442    
1443        bool InstrumentScriptVMFunction_change_release::acceptsArgFinal(vmint iArg) const {
1444            return iArg == 1;
1445        }
1446    
1447        void InstrumentScriptVMFunction_change_release::checkArgs(VMFnArgs* args,
1448                                                                  std::function<void(String)> err,
1449                                                                  std::function<void(String)> wrn)
1450        {
1451            // super class checks
1452            Super::checkArgs(args, err, wrn);
1453    
1454            // own checks ...
1455            if (args->argsCount() >= 2) {
1456                VMNumberExpr* argTime = args->arg(1)->asNumber();
1457                if (argTime->unitType() && !argTime->isFinal()) {
1458                    wrn("Argument 2 implies 'final' value when using seconds as unit for release time.");
1459                }
1460            }
1461        }
1462    
1463        VMFnResult* InstrumentScriptVMFunction_change_release::exec(VMFnArgs* args) {
1464            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1465            vmint release =
1466                (unit) ?
1467                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1468                    args->arg(1)->asNumber()->evalCastInt();
1469            const bool isFinal =
1470                (unit) ?
1471                    true : // imply 'final' value if unit type is used
1472                    args->arg(1)->asNumber()->isFinal();
1473            // note: intentionally not checking against a max. value here!
1474            // (to allow i.e. passing 2000000 for doubling the release time)
1475            if (release < 0) {
1476                wrnMsg("change_release(): argument 2 may not be negative");
1477                release = 0;
1478            }
1479            const float fRelease =
1480                (unit) ? release : float(release) / float(VM_EG_PAR_MAX_VALUE);
1481    
1482            AbstractEngineChannel* pEngineChannel =
1483                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1484    
1485            if (args->arg(0)->exprType() == INT_EXPR) {
1486                const ScriptID id = args->arg(0)->asInt()->evalInt();
1487                if (!id) {
1488                    wrnMsg("change_release(): note ID for argument 1 may not be zero");
1489                    return successResult();
1490                }
1491                if (!id.isNoteID()) {
1492                    wrnMsg("change_release(): argument 1 is not a note ID");
1493                    return successResult();
1494                }
1495    
1496                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1497                if (!pNote) return successResult();
1498    
1499                // if change_release() was called immediately after note was triggered
1500                // then immediately apply relase to Note object
1501                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1502                    pNote->Override.Release.Value = fRelease;
1503                    pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1504                } else { // otherwise schedule release change ...
1505                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1506                    e.Init(); // clear IDs
1507                    e.Type = Event::type_note_synth_param;
1508                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1509                    e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1510                    e.Param.NoteSynthParam.Delta    = fRelease;
1511                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1512                        isFinal, false, unit
1513                    );
1514                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1515                }
1516            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1517                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1518                for (vmint i = 0; i < ids->arraySize(); ++i) {
1519                    const ScriptID id = ids->evalIntElement(i);
1520                    if (!id || !id.isNoteID()) continue;
1521    
1522                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1523                    if (!pNote) continue;
1524    
1525                    // if change_release() was called immediately after note was triggered
1526                    // then immediately apply relase to Note object
1527                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1528                        pNote->Override.Release.Value = fRelease;
1529                        pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1530                    } else { // otherwise schedule release change ...
1531                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1532                        e.Init(); // clear IDs
1533                        e.Type = Event::type_note_synth_param;
1534                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1535                        e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1536                        e.Param.NoteSynthParam.Delta    = fRelease;
1537                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1538                            isFinal, false, unit
1539                        );
1540                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1541                    }
1542                }
1543            }
1544    
1545            return successResult();
1546        }
1547    
1548        // template for change_*() functions
1549    
1550        bool VMChangeSynthParamFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1551            if (iArg == 0)
1552                return type == INT_EXPR || type == INT_ARR_EXPR;
1553            else
1554                return type == INT_EXPR || (m_acceptReal && type == REAL_EXPR);
1555        }
1556    
1557        bool VMChangeSynthParamFunction::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1558            if (iArg == 1)
1559                return type == VM_NO_UNIT || type == m_unit;
1560            else
1561                return type == VM_NO_UNIT;
1562        }
1563    
1564        bool VMChangeSynthParamFunction::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1565            return m_acceptUnitPrefix && iArg == 1 && type == m_unit; // only allow metric prefix(es) if approprirate unit type is used (e.g. Hz)
1566        }
1567    
1568        bool VMChangeSynthParamFunction::acceptsArgFinal(vmint iArg) const {
1569            return (m_acceptFinal) ? (iArg == 1) : false;
1570        }
1571    
1572        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Param& param, const bool bFinal, const StdUnit_t unit) {
1573            param.Scope = NoteBase::scopeBy_FinalUnit(bFinal, unit);
1574        }
1575    
1576        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Norm& param, const bool bFinal, const StdUnit_t unit) {
1577            param.Final = bFinal;
1578        }
1579    
1580        inline static void setNoteParamScopeBy_FinalUnit(float& param, const bool bFinal, const StdUnit_t unit) {
1581            /* NOOP */
1582        }
1583    
1584        template<class T>
1585        inline static void setNoteParamValue(T& param, vmfloat value) {
1586            param.Value = value;
1587        }
1588    
1589        inline static void setNoteParamValue(float& param, vmfloat value) {
1590            param = value;
1591        }
1592    
1593        void VMChangeSynthParamFunction::checkArgs(VMFnArgs* args,
1594                                                   std::function<void(String)> err,
1595                                                   std::function<void(String)> wrn)
1596        {
1597            // super class checks
1598            Super::checkArgs(args, err, wrn);
1599    
1600            // own checks ...
1601            if (m_unit && m_unit != VM_BEL && args->argsCount() >= 2) {
1602                VMNumberExpr* arg = args->arg(1)->asNumber();
1603                if (arg && arg->unitType() && !arg->isFinal()) {
1604                    wrn("Argument 2 implies 'final' value when unit type " +
1605                        unitTypeStr(arg->unitType()) + " is used.");
1606                }
1607            }
1608        }
1609    
1610        // Arbitrarily chosen constant value symbolizing "no limit".
1611        #define NO_LIMIT 1315916909
1612    
1613        template<class T_NoteParamType, T_NoteParamType NoteBase::_Override::*T_noteParam,
1614                 vmint T_synthParam,
1615                 vmint T_minValueNorm, vmint T_maxValueNorm, bool T_normalizeNorm,
1616                 vmint T_minValueUnit, vmint T_maxValueUnit,
1617                 MetricPrefix_t T_unitPrefix0, MetricPrefix_t ... T_unitPrefixN>
1618        VMFnResult* VMChangeSynthParamFunction::execTemplate(VMFnArgs* args, const char* functionName)
1619        {
1620            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1621            const bool isFinal =
1622                (m_unit && m_unit != VM_BEL && unit) ?
1623                    true : // imply 'final' value if unit type is used (except of 'Bel' which may be relative)
1624                    args->arg(1)->asNumber()->isFinal();
1625            vmint value =
1626                (m_acceptUnitPrefix && ((m_unit && unit) || (!m_unit && args->arg(1)->asNumber()->hasUnitFactorNow())))
1627                    ? args->arg(1)->asNumber()->evalCastInt(T_unitPrefix0, T_unitPrefixN ...)
1628                    : args->arg(1)->asNumber()->evalCastInt();
1629    
1630            // check if passed value is in allowed range
1631            if (unit && m_unit) {
1632                if (T_maxValueUnit != NO_LIMIT && value > T_maxValueUnit) {
1633                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueUnit));
1634                    value = T_maxValueUnit;
1635                } else if (T_minValueUnit != NO_LIMIT && value < T_minValueUnit) {
1636                    if (T_minValueUnit == 0)
1637                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1638                    else
1639                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueUnit));
1640                    value = T_minValueUnit;
1641                }
1642            } else { // value was passed to this function without a unit ...
1643                if (T_maxValueNorm != NO_LIMIT && value > T_maxValueNorm) {
1644                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueNorm));
1645                    value = T_maxValueNorm;
1646                } else if (T_minValueNorm != NO_LIMIT && value < T_minValueNorm) {
1647                    if (T_minValueNorm == 0)
1648                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1649                    else
1650                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueNorm));
1651                    value = T_minValueNorm;
1652                }
1653            }
1654    
1655            // convert passed argument value to engine internal expected value range (i.e. 0.0 .. 1.0)
1656            const float fValue =
1657                (unit && m_unit) ?
1658                    (unit == VM_BEL) ?
1659                        RTMath::DecibelToLinRatio(float(value) * float(T_unitPrefix0) /*i.e. mdB -> dB*/) :
1660                        float(value) * VMUnit::unitFactor(T_unitPrefix0, T_unitPrefixN ...) /*i.e. us -> s*/ :
1661                    (T_normalizeNorm) ?
1662                        float(value) / ((T_maxValueNorm != NO_LIMIT) ? float(T_maxValueNorm) : 1000000.f/* fallback: value range used for most */) :
1663                        float(value) /* as is */;
1664    
1665            AbstractEngineChannel* pEngineChannel =
1666                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1667    
1668            if (args->arg(0)->exprType() == INT_EXPR) {
1669                const ScriptID id = args->arg(0)->asInt()->evalInt();
1670                if (!id) {
1671                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1672                    return successResult();
1673                }
1674                if (!id.isNoteID()) {
1675                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1676                    return successResult();
1677                }
1678    
1679                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1680                if (!pNote) return successResult();
1681    
1682                // if this change_*() script function was called immediately after
1683                // note was triggered then immediately apply the synth parameter
1684                // change to Note object
1685                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1686                    setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1687                    setNoteParamScopeBy_FinalUnit(
1688                        (pNote->Override.*T_noteParam),
1689                        isFinal, unit
1690                    );
1691                } else { // otherwise schedule this synth parameter change ...
1692                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1693                    e.Init(); // clear IDs
1694                    e.Type = Event::type_note_synth_param;
1695                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1696                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1697                    e.Param.NoteSynthParam.Delta    = fValue;
1698                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1699                        isFinal, false, unit
1700                    );
1701                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1702                }
1703            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1704                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1705                for (vmint i = 0; i < ids->arraySize(); ++i) {
1706                    const ScriptID id = ids->evalIntElement(i);
1707                    if (!id || !id.isNoteID()) continue;
1708    
1709                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1710                    if (!pNote) continue;
1711    
1712                    // if this change_*() script function was called immediately after
1713                    // note was triggered then immediately apply the synth parameter
1714                    // change to Note object
1715                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1716                        setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1717                        setNoteParamScopeBy_FinalUnit(
1718                            (pNote->Override.*T_noteParam),
1719                            isFinal, unit
1720                        );
1721                    } else { // otherwise schedule this synth parameter change ...
1722                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1723                        e.Init(); // clear IDs
1724                        e.Type = Event::type_note_synth_param;
1725                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1726                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1727                        e.Param.NoteSynthParam.Delta    = fValue;
1728                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1729                            isFinal, false, unit
1730                        );
1731                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1732                    }
1733                }
1734            }
1735    
1736            return successResult();
1737        }
1738    
1739        // change_sustain() function
1740    
1741        VMFnResult* InstrumentScriptVMFunction_change_sustain::exec(VMFnArgs* args) {
1742            return VMChangeSynthParamFunction::execTemplate<
1743                        decltype(NoteBase::_Override::Sustain),
1744                        &NoteBase::_Override::Sustain,
1745                        Event::synth_param_sustain,
1746                        /* if value passed without unit */
1747                        0, NO_LIMIT, true,
1748                        /* if value passed WITH 'Bel' unit */
1749                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_sustain" );
1750        }
1751    
1752        // change_cutoff_attack() function
1753    
1754        VMFnResult* InstrumentScriptVMFunction_change_cutoff_attack::exec(VMFnArgs* args) {
1755            return VMChangeSynthParamFunction::execTemplate<
1756                        decltype(NoteBase::_Override::CutoffAttack),
1757                        &NoteBase::_Override::CutoffAttack,
1758                        Event::synth_param_cutoff_attack,
1759                        /* if value passed without unit */
1760                        0, NO_LIMIT, true,
1761                        /* if value passed with 'seconds' unit */
1762                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_attack" );
1763        }
1764    
1765        // change_cutoff_decay() function
1766    
1767        VMFnResult* InstrumentScriptVMFunction_change_cutoff_decay::exec(VMFnArgs* args) {
1768            return VMChangeSynthParamFunction::execTemplate<
1769                        decltype(NoteBase::_Override::CutoffDecay),
1770                        &NoteBase::_Override::CutoffDecay,
1771                        Event::synth_param_cutoff_decay,
1772                        /* if value passed without unit */
1773                        0, NO_LIMIT, true,
1774                        /* if value passed with 'seconds' unit */
1775                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_decay" );
1776        }
1777    
1778        // change_cutoff_sustain() function
1779    
1780        VMFnResult* InstrumentScriptVMFunction_change_cutoff_sustain::exec(VMFnArgs* args) {
1781            return VMChangeSynthParamFunction::execTemplate<
1782                        decltype(NoteBase::_Override::CutoffSustain),
1783                        &NoteBase::_Override::CutoffSustain,
1784                        Event::synth_param_cutoff_sustain,
1785                        /* if value passed without unit */
1786                        0, NO_LIMIT, true,
1787                        /* if value passed WITH 'Bel' unit */
1788                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_cutoff_sustain" );
1789        }
1790    
1791        // change_cutoff_release() function
1792    
1793        VMFnResult* InstrumentScriptVMFunction_change_cutoff_release::exec(VMFnArgs* args) {
1794            return VMChangeSynthParamFunction::execTemplate<
1795                        decltype(NoteBase::_Override::CutoffRelease),
1796                        &NoteBase::_Override::CutoffRelease,
1797                        Event::synth_param_cutoff_release,
1798                        /* if value passed without unit */
1799                        0, NO_LIMIT, true,
1800                        /* if value passed with 'seconds' unit */
1801                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_release" );
1802        }
1803    
1804        // change_amp_lfo_depth() function
1805    
1806        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_depth::exec(VMFnArgs* args) {
1807            return VMChangeSynthParamFunction::execTemplate<
1808                        decltype(NoteBase::_Override::AmpLFODepth),
1809                        &NoteBase::_Override::AmpLFODepth,
1810                        Event::synth_param_amp_lfo_depth,
1811                        /* if value passed without unit */
1812                        0, 1000000, true,
1813                        /* not used (since this function does not accept unit) */
1814                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_amp_lfo_depth" );
1815        }
1816    
1817        // change_amp_lfo_freq() function
1818    
1819        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_freq::exec(VMFnArgs* args) {
1820            return VMChangeSynthParamFunction::execTemplate<
1821                        decltype(NoteBase::_Override::AmpLFOFreq),
1822                        &NoteBase::_Override::AmpLFOFreq,
1823                        Event::synth_param_amp_lfo_freq,
1824                        /* if value passed without unit */
1825                        0, 1000000, true,
1826                        /* if value passed with 'Hz' unit */
1827                        0, 30000, VM_NO_PREFIX>( args, "change_amp_lfo_freq" );
1828        }
1829    
1830        // change_cutoff_lfo_depth() function
1831    
1832        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_depth::exec(VMFnArgs* args) {
1833            return VMChangeSynthParamFunction::execTemplate<
1834                        decltype(NoteBase::_Override::CutoffLFODepth),
1835                        &NoteBase::_Override::CutoffLFODepth,
1836                        Event::synth_param_cutoff_lfo_depth,
1837                        /* if value passed without unit */
1838                        0, 1000000, true,
1839                        /* not used (since this function does not accept unit) */
1840                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_cutoff_lfo_depth" );
1841        }
1842    
1843        // change_cutoff_lfo_freq() function
1844    
1845        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_freq::exec(VMFnArgs* args) {
1846            return VMChangeSynthParamFunction::execTemplate<
1847                        decltype(NoteBase::_Override::CutoffLFOFreq),
1848                        &NoteBase::_Override::CutoffLFOFreq,
1849                        Event::synth_param_cutoff_lfo_freq,
1850                        /* if value passed without unit */
1851                        0, 1000000, true,
1852                        /* if value passed with 'Hz' unit */
1853                        0, 30000, VM_NO_PREFIX>( args, "change_cutoff_lfo_freq" );
1854        }
1855    
1856        // change_pitch_lfo_depth() function
1857    
1858        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_depth::exec(VMFnArgs* args) {
1859            return VMChangeSynthParamFunction::execTemplate<
1860                        decltype(NoteBase::_Override::PitchLFODepth),
1861                        &NoteBase::_Override::PitchLFODepth,
1862                        Event::synth_param_pitch_lfo_depth,
1863                        /* if value passed without unit */
1864                        0, 1000000, true,
1865                        /* not used (since this function does not accept unit) */
1866                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_pitch_lfo_depth" );
1867        }
1868    
1869        // change_pitch_lfo_freq() function
1870    
1871        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_freq::exec(VMFnArgs* args) {
1872            return VMChangeSynthParamFunction::execTemplate<
1873                        decltype(NoteBase::_Override::PitchLFOFreq),
1874                        &NoteBase::_Override::PitchLFOFreq,
1875                        Event::synth_param_pitch_lfo_freq,
1876                        /* if value passed without unit */
1877                        0, 1000000, true,
1878                        /* if value passed with 'Hz' unit */
1879                        0, 30000, VM_NO_PREFIX>( args, "change_pitch_lfo_freq" );
1880        }
1881    
1882        // change_vol_time() function
1883    
1884        VMFnResult* InstrumentScriptVMFunction_change_vol_time::exec(VMFnArgs* args) {
1885            return VMChangeSynthParamFunction::execTemplate<
1886                        decltype(NoteBase::_Override::VolumeTime),
1887                        &NoteBase::_Override::VolumeTime,
1888                        Event::synth_param_volume_time,
1889                        /* if value passed without unit (implying 'us' unit) */
1890                        0, NO_LIMIT, true,
1891                        /* if value passed with 'seconds' unit */
1892                        0, NO_LIMIT, VM_MICRO>( args, "change_vol_time" );
1893        }
1894    
1895        // change_tune_time() function
1896    
1897        VMFnResult* InstrumentScriptVMFunction_change_tune_time::exec(VMFnArgs* args) {
1898            return VMChangeSynthParamFunction::execTemplate<
1899                        decltype(NoteBase::_Override::PitchTime),
1900                        &NoteBase::_Override::PitchTime,
1901                        Event::synth_param_pitch_time,
1902                        /* if value passed without unit (implying 'us' unit) */
1903                        0, NO_LIMIT, true,
1904                        /* if value passed with 'seconds' unit */
1905                        0, NO_LIMIT, VM_MICRO>( args, "change_tune_time" );
1906        }
1907    
1908        // change_pan_time() function
1909    
1910        VMFnResult* InstrumentScriptVMFunction_change_pan_time::exec(VMFnArgs* args) {
1911            return VMChangeSynthParamFunction::execTemplate<
1912                        decltype(NoteBase::_Override::PanTime),
1913                        &NoteBase::_Override::PanTime,
1914                        Event::synth_param_pan_time,
1915                        /* if value passed without unit (implying 'us' unit) */
1916                        0, NO_LIMIT, true,
1917                        /* if value passed with 'seconds' unit */
1918                        0, NO_LIMIT, VM_MICRO>( args, "change_pan_time" );
1919        }
1920    
1921        // template for change_*_curve() functions
1922    
1923        bool VMChangeFadeCurveFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1924            if (iArg == 0)
1925                return type == INT_EXPR || type == INT_ARR_EXPR;
1926            else
1927                return type == INT_EXPR;
1928        }
1929    
1930        template<fade_curve_t NoteBase::_Override::*T_noteParam, vmint T_synthParam>
1931        VMFnResult* VMChangeFadeCurveFunction::execTemplate(VMFnArgs* args, const char* functionName) {
1932            vmint value = args->arg(1)->asInt()->evalInt();
1933            switch (value) {
1934                case FADE_CURVE_LINEAR:
1935                case FADE_CURVE_EASE_IN_EASE_OUT:
1936                    break;
1937                default:
1938                    wrnMsg(String(functionName) + "(): invalid curve type passed as argument 2");
1939                    return successResult();
1940            }
1941    
1942            AbstractEngineChannel* pEngineChannel =
1943                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1944    
1945            if (args->arg(0)->exprType() == INT_EXPR) {
1946                const ScriptID id = args->arg(0)->asInt()->evalInt();
1947                if (!id) {
1948                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1949                    return successResult();
1950                }
1951                if (!id.isNoteID()) {
1952                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1953                    return successResult();
1954                }
1955    
1956                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1957                if (!pNote) return successResult();
1958    
1959                // if this change_*_curve() script function was called immediately after
1960                // note was triggered then immediately apply the synth parameter
1961                // change to Note object
1962                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1963                    pNote->Override.*T_noteParam = (fade_curve_t) value;
1964                } else { // otherwise schedule this synth parameter change ...
1965                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1966                    e.Init(); // clear IDs
1967                    e.Type = Event::type_note_synth_param;
1968                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1969                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1970                    e.Param.NoteSynthParam.Delta    = value;
1971                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1972    
1973                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1974                }
1975            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1976                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1977                for (vmint i = 0; i < ids->arraySize(); ++i) {
1978                    const ScriptID id = ids->evalIntElement(i);
1979                    if (!id || !id.isNoteID()) continue;
1980    
1981                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1982                    if (!pNote) continue;
1983    
1984                    // if this change_*_curve() script function was called immediately after
1985                    // note was triggered then immediately apply the synth parameter
1986                    // change to Note object
1987                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1988                        pNote->Override.*T_noteParam = (fade_curve_t) value;
1989                    } else { // otherwise schedule this synth parameter change ...
1990                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1991                        e.Init(); // clear IDs
1992                        e.Type = Event::type_note_synth_param;
1993                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1994                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1995                        e.Param.NoteSynthParam.Delta    = value;
1996                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1997    
1998                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1999                    }
2000                }
2001            }
2002    
2003            return successResult();
2004        }
2005    
2006        // change_vol_curve() function
2007    
2008        VMFnResult* InstrumentScriptVMFunction_change_vol_curve::exec(VMFnArgs* args) {
2009            return VMChangeFadeCurveFunction::execTemplate<
2010                        &NoteBase::_Override::VolumeCurve,
2011                        Event::synth_param_volume_curve>( args, "change_vol_curve" );
2012        }
2013    
2014        // change_tune_curve() function
2015    
2016        VMFnResult* InstrumentScriptVMFunction_change_tune_curve::exec(VMFnArgs* args) {
2017            return VMChangeFadeCurveFunction::execTemplate<
2018                        &NoteBase::_Override::PitchCurve,
2019                        Event::synth_param_pitch_curve>( args, "change_tune_curve" );
2020        }
2021    
2022        // change_pan_curve() function
2023    
2024        VMFnResult* InstrumentScriptVMFunction_change_pan_curve::exec(VMFnArgs* args) {
2025            return VMChangeFadeCurveFunction::execTemplate<
2026            &NoteBase::_Override::PanCurve,
2027            Event::synth_param_pan_curve>( args, "change_pan_curve" );
2028        }
2029    
2030        // fade_in() function
2031    
2032        InstrumentScriptVMFunction_fade_in::InstrumentScriptVMFunction_fade_in(InstrumentScriptVM* parent)
2033            : m_vm(parent)
2034        {
2035        }
2036    
2037        bool InstrumentScriptVMFunction_fade_in::acceptsArgType(vmint iArg, ExprType_t type) const {
2038            if (iArg == 0)
2039                return type == INT_EXPR || type == INT_ARR_EXPR;
2040            else
2041                return type == INT_EXPR || type == REAL_EXPR;
2042        }
2043    
2044        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2045            if (iArg == 1)
2046                return type == VM_NO_UNIT || type == VM_SECOND;
2047            else
2048                return type == VM_NO_UNIT;
2049        }
2050    
2051        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2052            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2053        }
2054    
2055        VMFnResult* InstrumentScriptVMFunction_fade_in::exec(VMFnArgs* args) {
2056            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2057            vmint duration =
2058                (unit) ?
2059                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2060                    args->arg(1)->asNumber()->evalCastInt();
2061            if (duration < 0) {
2062                wrnMsg("fade_in(): argument 2 may not be negative");
2063                duration = 0;
2064            }
2065            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2066    
2067            AbstractEngineChannel* pEngineChannel =
2068                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2069    
2070            if (args->arg(0)->exprType() == INT_EXPR) {
2071                const ScriptID id = args->arg(0)->asInt()->evalInt();
2072                if (!id) {
2073                    wrnMsg("fade_in(): note ID for argument 1 may not be zero");
2074                    return successResult();
2075                }
2076                if (!id.isNoteID()) {
2077                    wrnMsg("fade_in(): argument 1 is not a note ID");
2078                    return successResult();
2079                }
2080    
2081                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2082                if (!pNote) return successResult();
2083    
2084                // if fade_in() was called immediately after note was triggered
2085                // then immediately apply a start volume of zero to Note object,
2086                // as well as the fade in duration
2087                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2088                    pNote->Override.Volume.Value = 0.f;
2089                    pNote->Override.VolumeTime = fDuration;
2090                } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2091                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2092                    e.Init(); // clear IDs
2093                    e.Type = Event::type_note_synth_param;
2094                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2095                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2096                    e.Param.NoteSynthParam.Delta    = fDuration;
2097                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2098    
2099                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
2100              }              }
2101                // and finally schedule a "volume" change, simply one time slice
2102                // ahead, with the final fade in volume (1.0)
2103                {
2104                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2105                    e.Init(); // clear IDs
2106                    e.Type = Event::type_note_synth_param;
2107                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2108                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2109                    e.Param.NoteSynthParam.Delta    = 1.f;
2110                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2111    
2112                    // scheduling with 0 delay would also work here, but +1 is more
2113                    // safe regarding potential future implementation changes of the
2114                    // scheduler (see API comments of RTAVLTree::insert())
2115                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2116                }
2117            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2118                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2119                for (vmint i = 0; i < ids->arraySize(); ++i) {
2120                    const ScriptID id = ids->evalIntElement(i);
2121                    if (!id || !id.isNoteID()) continue;
2122    
2123                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2124                    if (!pNote) continue;
2125    
2126                    // if fade_in() was called immediately after note was triggered
2127                    // then immediately apply a start volume of zero to Note object,
2128                    // as well as the fade in duration
2129                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2130                        pNote->Override.Volume.Value = 0.f;
2131                        pNote->Override.VolumeTime = fDuration;
2132                    } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2133                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2134                        e.Init(); // clear IDs
2135                        e.Type = Event::type_note_synth_param;
2136                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2137                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2138                        e.Param.NoteSynthParam.Delta    = fDuration;
2139                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2140    
2141                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2142                    }
2143                    // and finally schedule a "volume" change, simply one time slice
2144                    // ahead, with the final fade in volume (1.0)
2145                    {
2146                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2147                        e.Init(); // clear IDs
2148                        e.Type = Event::type_note_synth_param;
2149                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2150                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2151                        e.Param.NoteSynthParam.Delta    = 1.f;
2152                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2153    
2154                        // scheduling with 0 delay would also work here, but +1 is more
2155                        // safe regarding potential future implementation changes of the
2156                        // scheduler (see API comments of RTAVLTree::insert())
2157                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2158                    }
2159                }
2160          }          }
2161    
2162          return successResult();          return successResult();
2163      }      }
2164    
2165        // fade_out() function
2166    
2167        InstrumentScriptVMFunction_fade_out::InstrumentScriptVMFunction_fade_out(InstrumentScriptVM* parent)
2168            : m_vm(parent)
2169        {
2170        }
2171    
2172        bool InstrumentScriptVMFunction_fade_out::acceptsArgType(vmint iArg, ExprType_t type) const {
2173            if (iArg == 0)
2174                return type == INT_EXPR || type == INT_ARR_EXPR;
2175            else
2176                return type == INT_EXPR || type == REAL_EXPR;
2177        }
2178    
2179        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2180            if (iArg == 1)
2181                return type == VM_NO_UNIT || type == VM_SECOND;
2182            else
2183                return type == VM_NO_UNIT;
2184        }
2185    
2186        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2187            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2188        }
2189    
2190        VMFnResult* InstrumentScriptVMFunction_fade_out::exec(VMFnArgs* args) {
2191            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2192            vmint duration =
2193                (unit) ?
2194                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2195                    args->arg(1)->asNumber()->evalCastInt();
2196            if (duration < 0) {
2197                wrnMsg("fade_out(): argument 2 may not be negative");
2198                duration = 0;
2199            }
2200            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2201    
2202            bool stop = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : true;
2203    
2204            AbstractEngineChannel* pEngineChannel =
2205                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2206    
2207            if (args->arg(0)->exprType() == INT_EXPR) {
2208                const ScriptID id = args->arg(0)->asInt()->evalInt();
2209                if (!id) {
2210                    wrnMsg("fade_out(): note ID for argument 1 may not be zero");
2211                    return successResult();
2212                }
2213                if (!id.isNoteID()) {
2214                    wrnMsg("fade_out(): argument 1 is not a note ID");
2215                    return successResult();
2216                }
2217    
2218                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2219                if (!pNote) return successResult();
2220    
2221                // if fade_out() was called immediately after note was triggered
2222                // then immediately apply fade out duration to Note object
2223                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2224                    pNote->Override.VolumeTime = fDuration;
2225                } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2226                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2227                    e.Init(); // clear IDs
2228                    e.Type = Event::type_note_synth_param;
2229                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2230                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2231                    e.Param.NoteSynthParam.Delta    = fDuration;
2232                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2233    
2234                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
2235                }
2236                // now schedule a "volume" change, simply one time slice ahead, with
2237                // the final fade out volume (0.0)
2238                {
2239                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2240                    e.Init(); // clear IDs
2241                    e.Type = Event::type_note_synth_param;
2242                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2243                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2244                    e.Param.NoteSynthParam.Delta    = 0.f;
2245                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2246    
2247                    // scheduling with 0 delay would also work here, but +1 is more
2248                    // safe regarding potential future implementation changes of the
2249                    // scheduler (see API comments of RTAVLTree::insert())
2250                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2251                }
2252                // and finally if stopping the note was requested after the fade out
2253                // completed, then schedule to kill the voice after the requested
2254                // duration
2255                if (stop) {
2256                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2257                    e.Init(); // clear IDs
2258                    e.Type = Event::type_kill_note;
2259                    e.Param.Note.ID = id.noteID();
2260                    e.Param.Note.Key = pNote->hostKey;
2261    
2262                    pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2263                }
2264            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2265                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2266                for (vmint i = 0; i < ids->arraySize(); ++i) {
2267                    const ScriptID id = ids->evalIntElement(i);
2268                    if (!id || !id.isNoteID()) continue;
2269    
2270                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2271                    if (!pNote) continue;
2272    
2273                    // if fade_out() was called immediately after note was triggered
2274                    // then immediately apply fade out duration to Note object
2275                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2276                        pNote->Override.VolumeTime = fDuration;
2277                    } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2278                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2279                        e.Init(); // clear IDs
2280                        e.Type = Event::type_note_synth_param;
2281                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2282                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2283                        e.Param.NoteSynthParam.Delta    = fDuration;
2284                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2285    
2286                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2287                    }
2288                    // now schedule a "volume" change, simply one time slice ahead, with
2289                    // the final fade out volume (0.0)
2290                    {
2291                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2292                        e.Init(); // clear IDs
2293                        e.Type = Event::type_note_synth_param;
2294                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2295                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2296                        e.Param.NoteSynthParam.Delta    = 0.f;
2297                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2298    
2299                        // scheduling with 0 delay would also work here, but +1 is more
2300                        // safe regarding potential future implementation changes of the
2301                        // scheduler (see API comments of RTAVLTree::insert())
2302                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2303                    }
2304                    // and finally if stopping the note was requested after the fade out
2305                    // completed, then schedule to kill the voice after the requested
2306                    // duration
2307                    if (stop) {
2308                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2309                        e.Init(); // clear IDs
2310                        e.Type = Event::type_kill_note;
2311                        e.Param.Note.ID = id.noteID();
2312                        e.Param.Note.Key = pNote->hostKey;
2313                        
2314                        pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2315                    }
2316                }
2317            }
2318    
2319            return successResult();
2320        }
2321    
2322        // get_event_par() function
2323    
2324        InstrumentScriptVMFunction_get_event_par::InstrumentScriptVMFunction_get_event_par(InstrumentScriptVM* parent)
2325            : m_vm(parent)
2326        {
2327        }
2328    
2329        VMFnResult* InstrumentScriptVMFunction_get_event_par::exec(VMFnArgs* args) {
2330            AbstractEngineChannel* pEngineChannel =
2331                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2332    
2333            const ScriptID id = args->arg(0)->asInt()->evalInt();
2334            if (!id) {
2335                wrnMsg("get_event_par(): note ID for argument 1 may not be zero");
2336                return successResult(0);
2337            }
2338            if (!id.isNoteID()) {
2339                wrnMsg("get_event_par(): argument 1 is not a note ID");
2340                return successResult(0);
2341            }
2342    
2343            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2344            if (!pNote) {
2345                wrnMsg("get_event_par(): no note alive with that note ID of argument 1");
2346                return successResult(0);
2347            }
2348    
2349            const vmint parameter = args->arg(1)->asInt()->evalInt();
2350            switch (parameter) {
2351                case EVENT_PAR_NOTE:
2352                    return successResult(pNote->cause.Param.Note.Key);
2353                case EVENT_PAR_VELOCITY:
2354                    return successResult(pNote->cause.Param.Note.Velocity);
2355                case EVENT_PAR_VOLUME:
2356                    return successResult(
2357                        RTMath::LinRatioToDecibel(pNote->Override.Volume.Value) * 1000.f
2358                    );
2359                case EVENT_PAR_TUNE:
2360                    return successResult(
2361                         RTMath::FreqRatioToCents(pNote->Override.Pitch.Value) * 1000.f
2362                    );
2363                case EVENT_PAR_0:
2364                    return successResult(pNote->userPar[0]);
2365                case EVENT_PAR_1:
2366                    return successResult(pNote->userPar[1]);
2367                case EVENT_PAR_2:
2368                    return successResult(pNote->userPar[2]);
2369                case EVENT_PAR_3:
2370                    return successResult(pNote->userPar[3]);
2371            }
2372    
2373            wrnMsg("get_event_par(): argument 2 is an invalid event parameter");
2374            return successResult(0);
2375        }
2376    
2377        // set_event_par() function
2378    
2379        InstrumentScriptVMFunction_set_event_par::InstrumentScriptVMFunction_set_event_par(InstrumentScriptVM* parent)
2380            : m_vm(parent)
2381        {
2382        }
2383    
2384        VMFnResult* InstrumentScriptVMFunction_set_event_par::exec(VMFnArgs* args) {
2385            AbstractEngineChannel* pEngineChannel =
2386                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2387    
2388            const ScriptID id = args->arg(0)->asInt()->evalInt();
2389            if (!id) {
2390                wrnMsg("set_event_par(): note ID for argument 1 may not be zero");
2391                return successResult();
2392            }
2393            if (!id.isNoteID()) {
2394                wrnMsg("set_event_par(): argument 1 is not a note ID");
2395                return successResult();
2396            }
2397    
2398            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2399            if (!pNote) return successResult();
2400    
2401            const vmint parameter = args->arg(1)->asInt()->evalInt();
2402            const vmint value     = args->arg(2)->asInt()->evalInt();
2403    
2404            switch (parameter) {
2405                case EVENT_PAR_NOTE:
2406                    if (value < 0 || value > 127) {
2407                        wrnMsg("set_event_par(): note number of argument 3 is out of range");
2408                        return successResult();
2409                    }
2410                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2411                        pNote->cause.Param.Note.Key = value;
2412                        m_vm->m_event->cause.Param.Note.Key = value;
2413                    } else {
2414                        wrnMsg("set_event_par(): note number can only be changed when note is new");
2415                    }
2416                    return successResult();
2417                case EVENT_PAR_VELOCITY:
2418                    if (value < 0 || value > 127) {
2419                        wrnMsg("set_event_par(): velocity of argument 3 is out of range");
2420                        return successResult();
2421                    }
2422                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2423                        pNote->cause.Param.Note.Velocity = value;
2424                        m_vm->m_event->cause.Param.Note.Velocity = value;
2425                    } else {
2426                        wrnMsg("set_event_par(): velocity can only be changed when note is new");
2427                    }
2428                    return successResult();
2429                case EVENT_PAR_VOLUME:
2430                    wrnMsg("set_event_par(): changing volume by this function is currently not supported, use change_vol() instead");
2431                    return successResult();
2432                case EVENT_PAR_TUNE:
2433                    wrnMsg("set_event_par(): changing tune by this function is currently not supported, use change_tune() instead");
2434                    return successResult();
2435                case EVENT_PAR_0:
2436                    pNote->userPar[0] = value;
2437                    return successResult();
2438                case EVENT_PAR_1:
2439                    pNote->userPar[1] = value;
2440                    return successResult();
2441                case EVENT_PAR_2:
2442                    pNote->userPar[2] = value;
2443                    return successResult();
2444                case EVENT_PAR_3:
2445                    pNote->userPar[3] = value;
2446                    return successResult();
2447            }
2448    
2449            wrnMsg("set_event_par(): argument 2 is an invalid event parameter");
2450            return successResult();
2451        }
2452    
2453        // change_note() function
2454    
2455        InstrumentScriptVMFunction_change_note::InstrumentScriptVMFunction_change_note(InstrumentScriptVM* parent)
2456        : m_vm(parent)
2457        {
2458        }
2459    
2460        VMFnResult* InstrumentScriptVMFunction_change_note::exec(VMFnArgs* args) {
2461            AbstractEngineChannel* pEngineChannel =
2462                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2463    
2464            const ScriptID id = args->arg(0)->asInt()->evalInt();
2465            if (!id) {
2466                wrnMsg("change_note(): note ID for argument 1 may not be zero");
2467                return successResult();
2468            }
2469            if (!id.isNoteID()) {
2470                wrnMsg("change_note(): argument 1 is not a note ID");
2471                return successResult();
2472            }
2473    
2474            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2475            if (!pNote) return successResult();
2476    
2477            const vmint value = args->arg(1)->asInt()->evalInt();
2478            if (value < 0 || value > 127) {
2479                wrnMsg("change_note(): note number of argument 2 is out of range");
2480                return successResult();
2481            }
2482    
2483            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2484                pNote->cause.Param.Note.Key = value;
2485                m_vm->m_event->cause.Param.Note.Key = value;
2486            } else {
2487                wrnMsg("change_note(): note number can only be changed when note is new");
2488            }
2489    
2490            return successResult();
2491        }
2492    
2493        // change_velo() function
2494    
2495        InstrumentScriptVMFunction_change_velo::InstrumentScriptVMFunction_change_velo(InstrumentScriptVM* parent)
2496        : m_vm(parent)
2497        {
2498        }
2499    
2500        VMFnResult* InstrumentScriptVMFunction_change_velo::exec(VMFnArgs* args) {
2501            AbstractEngineChannel* pEngineChannel =
2502                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2503    
2504            const ScriptID id = args->arg(0)->asInt()->evalInt();
2505            if (!id) {
2506                wrnMsg("change_velo(): note ID for argument 1 may not be zero");
2507                return successResult();
2508            }
2509            if (!id.isNoteID()) {
2510                wrnMsg("change_velo(): argument 1 is not a note ID");
2511                return successResult();
2512            }
2513    
2514            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2515            if (!pNote) return successResult();
2516    
2517            const vmint value = args->arg(1)->asInt()->evalInt();
2518            if (value < 0 || value > 127) {
2519                wrnMsg("change_velo(): velocity of argument 2 is out of range");
2520                return successResult();
2521            }
2522    
2523            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2524                pNote->cause.Param.Note.Velocity = value;
2525                m_vm->m_event->cause.Param.Note.Velocity = value;
2526            } else {
2527                wrnMsg("change_velo(): velocity can only be changed when note is new");
2528            }
2529    
2530            return successResult();
2531        }
2532    
2533        // change_play_pos() function
2534    
2535        InstrumentScriptVMFunction_change_play_pos::InstrumentScriptVMFunction_change_play_pos(InstrumentScriptVM* parent)
2536            : m_vm(parent)
2537        {
2538        }
2539    
2540        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgType(vmint iArg, ExprType_t type) const {
2541            if (iArg == 0)
2542                return type == INT_EXPR;
2543            else
2544                return type == INT_EXPR || type == REAL_EXPR;
2545        }
2546    
2547        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2548            if (iArg == 1)
2549                return type == VM_NO_UNIT || type == VM_SECOND;
2550            else
2551                return type == VM_NO_UNIT;
2552        }
2553    
2554        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2555            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2556        }
2557    
2558        VMFnResult* InstrumentScriptVMFunction_change_play_pos::exec(VMFnArgs* args) {
2559            const ScriptID id = args->arg(0)->asInt()->evalInt();
2560            if (!id) {
2561                wrnMsg("change_play_pos(): note ID for argument 1 may not be zero");
2562                return successResult();
2563            }
2564            if (!id.isNoteID()) {
2565                wrnMsg("change_play_pos(): argument 1 is not a note ID");
2566                return successResult();
2567            }
2568    
2569            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2570            const vmint pos =
2571                (unit) ?
2572                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2573                    args->arg(1)->asNumber()->evalCastInt();
2574            if (pos < 0) {
2575                wrnMsg("change_play_pos(): playback position of argument 2 may not be negative");
2576                return successResult();
2577            }
2578    
2579            AbstractEngineChannel* pEngineChannel =
2580                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2581    
2582            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2583            if (!pNote) return successResult();
2584    
2585            pNote->Override.SampleOffset =
2586                (decltype(pNote->Override.SampleOffset)) pos;
2587    
2588            return successResult();
2589        }
2590    
2591      // event_status() function      // event_status() function
2592    
2593      InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)
# Line 789  namespace LinuxSampler { Line 2613  namespace LinuxSampler {
2613          return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);          return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);
2614      }      }
2615    
2616        // callback_status() function
2617    
2618        InstrumentScriptVMFunction_callback_status::InstrumentScriptVMFunction_callback_status(InstrumentScriptVM* parent)
2619            : m_vm(parent)
2620        {
2621        }
2622    
2623        VMFnResult* InstrumentScriptVMFunction_callback_status::exec(VMFnArgs* args) {
2624            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2625            if (!id) {
2626                wrnMsg("callback_status(): callback ID for argument 1 may not be zero");
2627                return successResult();
2628            }
2629    
2630            AbstractEngineChannel* pEngineChannel =
2631                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2632    
2633            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2634            if (!itCallback)
2635                return successResult(CALLBACK_STATUS_TERMINATED);
2636    
2637            return successResult(
2638                (m_vm->m_event->execCtx == itCallback->execCtx) ?
2639                    CALLBACK_STATUS_RUNNING : CALLBACK_STATUS_QUEUE
2640            );
2641        }
2642    
2643      // wait() function (overrides core wait() implementation)      // wait() function (overrides core wait() implementation)
2644    
2645      InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)
# Line 829  namespace LinuxSampler { Line 2680  namespace LinuxSampler {
2680              (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;              (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;
2681    
2682          pEngineChannel->ScheduleResumeOfScriptCallback(          pEngineChannel->ScheduleResumeOfScriptCallback(
2683              itCallback, m_vm->m_event->cause.SchedTime(), disableWaitForever              itCallback, m_vm->m_event->scheduleTime, disableWaitForever
2684          );          );
2685    
2686          return successResult();          return successResult();
2687      }      }
2688    
2689        // abort() function
2690    
2691        InstrumentScriptVMFunction_abort::InstrumentScriptVMFunction_abort(InstrumentScriptVM* parent)
2692            : m_vm(parent)
2693        {
2694        }
2695    
2696        VMFnResult* InstrumentScriptVMFunction_abort::exec(VMFnArgs* args) {
2697            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2698            if (!id) {
2699                wrnMsg("abort(): callback ID for argument 1 may not be zero");
2700                return successResult();
2701            }
2702    
2703            AbstractEngineChannel* pEngineChannel =
2704                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2705    
2706            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2707            if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2708    
2709            itCallback->execCtx->signalAbort();
2710    
2711            return successResult();
2712        }
2713    
2714        // fork() function
2715    
2716        InstrumentScriptVMFunction_fork::InstrumentScriptVMFunction_fork(InstrumentScriptVM* parent)
2717            : m_vm(parent)
2718        {
2719        }
2720    
2721        VMFnResult* InstrumentScriptVMFunction_fork::exec(VMFnArgs* args) {
2722            // check if this is actually the parent going to fork, or rather one of
2723            // the children which is already forked
2724            if (m_vm->m_event->forkIndex != 0) { // this is the entry point for a child ...
2725                int forkResult = m_vm->m_event->forkIndex;
2726                // reset so that this child may i.e. also call fork() later on
2727                m_vm->m_event->forkIndex = 0;
2728                return successResult(forkResult);
2729            }
2730    
2731            // if we are here, then this is the parent, so we must fork this parent
2732    
2733            const vmint n =
2734                (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : 1;
2735            const bool bAutoAbort =
2736                (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : true;
2737    
2738            if (m_vm->m_event->countChildHandlers() + n > MAX_FORK_PER_SCRIPT_HANDLER) {
2739                wrnMsg("fork(): requested amount would exceed allowed limit per event handler");
2740                return successResult(-1);
2741            }
2742    
2743            AbstractEngineChannel* pEngineChannel =
2744                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2745    
2746            if (!pEngineChannel->hasFreeScriptCallbacks(n)) {
2747                wrnMsg("fork(): global limit of event handlers exceeded");
2748                return successResult(-1);
2749            }
2750    
2751            for (int iChild = 0; iChild < n; ++iChild) {
2752                RTList<ScriptEvent>::Iterator itChild =
2753                    pEngineChannel->forkScriptCallback(m_vm->m_event, bAutoAbort);
2754                if (!itChild) { // should never happen, otherwise its a bug ...
2755                    errMsg("fork(): internal error while allocating child");
2756                    return errorResult(-1); // terminate script
2757                }
2758                // since both parent, as well all child script execution instances
2759                // all land in this exec() method, the following is (more or less)
2760                // the only feature that lets us distinguish the parent and
2761                // respective children from each other in this exec() method
2762                itChild->forkIndex = iChild + 1;
2763            }
2764    
2765            return successResult(0);
2766        }
2767    
2768  } // namespace LinuxSampler  } // namespace LinuxSampler

Legend:
Removed from v.2948  
changed lines
  Added in v.3691

  ViewVC Help
Powered by ViewVC