/[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 2953 by schoenebeck, Sat Jul 16 11:24:39 2016 UTC revision 3588 by schoenebeck, Sun Sep 1 16:06:48 2019 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (c) 2014-2016 Christian Schoenebeck   * Copyright (c) 2014-2019 Christian Schoenebeck
3   *   *
4   * http://www.linuxsampler.org   * http://www.linuxsampler.org
5   *   *
# Line 10  Line 10 
10  #include "InstrumentScriptVMFunctions.h"  #include "InstrumentScriptVMFunctions.h"
11  #include "InstrumentScriptVM.h"  #include "InstrumentScriptVM.h"
12  #include "../AbstractEngineChannel.h"  #include "../AbstractEngineChannel.h"
13    #include "../../common/global_private.h"
14    
15  namespace LinuxSampler {  namespace LinuxSampler {
16            
# 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 128  namespace LinuxSampler { Line 218  namespace LinuxSampler {
218      {      {
219      }      }
220    
221      bool InstrumentScriptVMFunction_ignore_event::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_ignore_event::acceptsArgType(vmint iArg, ExprType_t type) const {
222          return type == INT_EXPR || type == INT_ARR_EXPR;          return type == INT_EXPR || type == INT_ARR_EXPR;
223      }      }
224    
# Line 136  namespace LinuxSampler { Line 226  namespace LinuxSampler {
226          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
227                  static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);                  static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
228    
229          if (args->arg(0)->exprType() == INT_EXPR) {          if (args->argsCount() == 0 || args->arg(0)->exprType() == INT_EXPR) {
230              const ScriptID id = args->arg(0)->asInt()->evalInt();              const ScriptID id = (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : m_vm->m_event->id;
231              if (!id) {              if (!id && args->argsCount() >= 1) {
232                  wrnMsg("ignore_event(): event ID argument may not be zero");                  wrnMsg("ignore_event(): event ID argument may not be zero");
233                  // 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
234                  return successResult();                  return successResult();
# Line 184  namespace LinuxSampler { Line 274  namespace LinuxSampler {
274      {      {
275      }      }
276    
277      bool InstrumentScriptVMFunction_note_off::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_note_off::acceptsArgType(vmint iArg, ExprType_t type) const {
278          return type == INT_EXPR || type == INT_ARR_EXPR;          return type == INT_EXPR || type == INT_ARR_EXPR;
279      }      }
280    
281        void InstrumentScriptVMFunction_note_off::checkArgs(VMFnArgs* args,
282                                                            std::function<void(String)> err,
283                                                            std::function<void(String)> wrn)
284        {
285            // super class checks
286            Super::checkArgs(args, err, wrn);
287    
288            // own checks ...
289            if (args->argsCount() >= 2 && args->arg(1)->isConstExpr() && args->arg(1)->exprType() == INT_EXPR) {
290                vmint velocity = args->arg(1)->asInt()->evalInt();
291                if (velocity < 0 || velocity > 127) {
292                    err("MIDI velocity value for argument 2 must be between 0..127");
293                    return;
294                }
295            }
296        }
297    
298      VMFnResult* InstrumentScriptVMFunction_note_off::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_note_off::exec(VMFnArgs* args) {
299          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
300              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
301    
302          int velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;          vmint velocity = (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : 127;
303          if (velocity < 0 || velocity > 127) {          if (velocity < 0 || velocity > 127) {
304              errMsg("note_off(): argument 2 is an invalid velocity value");              errMsg("note_off(): argument 2 is an invalid velocity value");
305              return errorResult();              return errorResult();
# Line 223  namespace LinuxSampler { Line 330  namespace LinuxSampler {
330              pEngineChannel->ScheduleEventMicroSec(&e, 0);              pEngineChannel->ScheduleEventMicroSec(&e, 0);
331          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
332              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
333              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
334                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
335                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
336    
# Line 252  namespace LinuxSampler { Line 359  namespace LinuxSampler {
359      {      {
360      }      }
361    
362        void InstrumentScriptVMFunction_set_event_mark::checkArgs(VMFnArgs* args,
363                                                                  std::function<void(String)> err,
364                                                                  std::function<void(String)> wrn)
365        {
366            // super class checks
367            Super::checkArgs(args, err, wrn);
368    
369            // own checks ...
370            if (args->argsCount() >= 2 && args->arg(1)->isConstExpr()) {
371                const vmint groupID = args->arg(1)->asInt()->evalInt();
372                if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
373                    err("Argument 2 value is an invalid group id.");
374                    return;
375                }
376            }
377        }
378    
379      VMFnResult* InstrumentScriptVMFunction_set_event_mark::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_set_event_mark::exec(VMFnArgs* args) {
380          const ScriptID id = args->arg(0)->asInt()->evalInt();          const ScriptID id = args->arg(0)->asInt()->evalInt();
381          const int groupID = args->arg(1)->asInt()->evalInt();          const vmint groupID = args->arg(1)->asInt()->evalInt();
382    
383          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
384              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 414  namespace LinuxSampler {
414      {      {
415      }      }
416    
417        void InstrumentScriptVMFunction_delete_event_mark::checkArgs(VMFnArgs* args,
418                                                                     std::function<void(String)> err,
419                                                                     std::function<void(String)> wrn)
420        {
421            // super class checks
422            Super::checkArgs(args, err, wrn);
423    
424            // own checks ...
425            if (args->argsCount() >= 2 && args->arg(1)->isConstExpr()) {
426                const vmint groupID = args->arg(1)->asInt()->evalInt();
427                if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
428                    err("Argument 2 value is an invalid group id.");
429                    return;
430                }
431            }
432        }
433    
434      VMFnResult* InstrumentScriptVMFunction_delete_event_mark::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_delete_event_mark::exec(VMFnArgs* args) {
435          const ScriptID id = args->arg(0)->asInt()->evalInt();          const ScriptID id = args->arg(0)->asInt()->evalInt();
436          const int groupID = args->arg(1)->asInt()->evalInt();          const vmint groupID = args->arg(1)->asInt()->evalInt();
437    
438          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
439              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 455  namespace LinuxSampler {
455      {      {
456      }      }
457    
458      int InstrumentScriptVMFunction_by_marks::Result::arraySize() const {      vmint InstrumentScriptVMFunction_by_marks::Result::arraySize() const {
459          return eventGroup->size();          return eventGroup->size();
460      }      }
461    
462      int InstrumentScriptVMFunction_by_marks::Result::evalIntElement(uint i) {      vmint InstrumentScriptVMFunction_by_marks::Result::evalIntElement(vmuint i) {
463          return (*eventGroup)[i];          return (*eventGroup)[i];
464      }      }
465    
# Line 334  namespace LinuxSampler { Line 475  namespace LinuxSampler {
475          return &m_result;          return &m_result;
476      }      }
477    
478        void InstrumentScriptVMFunction_by_marks::checkArgs(VMFnArgs* args,
479                                                            std::function<void(String)> err,
480                                                            std::function<void(String)> wrn)
481        {
482            // super class checks
483            Super::checkArgs(args, err, wrn);
484    
485            // own checks ...
486            if (args->arg(0)->isConstExpr()) {
487                const vmint groupID = args->arg(0)->asInt()->evalInt();
488                if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
489                    err("Argument value is an invalid group id.");
490                    return;
491                }
492            }
493        }
494    
495      VMFnResult* InstrumentScriptVMFunction_by_marks::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_by_marks::exec(VMFnArgs* args) {
496          int groupID = args->arg(0)->asInt()->evalInt();          vmint groupID = args->arg(0)->asInt()->evalInt();
497    
498          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {          if (groupID < 0 || groupID >= INSTR_SCRIPT_EVENT_GROUPS) {
499              errMsg("by_marks(): argument is an invalid group id");              errMsg("by_marks(): argument is an invalid group id");
# Line 355  namespace LinuxSampler { Line 513  namespace LinuxSampler {
513      {      {
514      }      }
515    
516      bool InstrumentScriptVMFunction_change_vol::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_vol::acceptsArgType(vmint iArg, ExprType_t type) const {
517          if (iArg == 0)          if (iArg == 0)
518              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
519            else if (iArg == 1)
520                return type == INT_EXPR || type == REAL_EXPR;
521            else
522                return type == INT_EXPR;
523        }
524    
525        bool InstrumentScriptVMFunction_change_vol::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
526            if (iArg == 1)
527                return type == VM_NO_UNIT || type == VM_BEL;
528          else          else
529              return INT_EXPR;              return type == VM_NO_UNIT;
530        }
531    
532        bool InstrumentScriptVMFunction_change_vol::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
533            return iArg == 1 && type == VM_BEL; // only allow metric prefix(es) if 'Bel' is used as unit type
534        }
535    
536        bool InstrumentScriptVMFunction_change_vol::acceptsArgFinal(vmint iArg) const {
537            return iArg == 1;
538      }      }
539    
540      VMFnResult* InstrumentScriptVMFunction_change_vol::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_vol::exec(VMFnArgs* args) {
541          int volume = args->arg(1)->asInt()->evalInt(); // volume change in milli dB          StdUnit_t unit = args->arg(1)->asNumber()->unitType();
542            vmint volume =
543                (unit) ?
544                    args->arg(1)->asNumber()->evalCastInt(VM_MILLI,VM_DECI) :
545                    args->arg(1)->asNumber()->evalCastInt(); // volume change in milli dB
546            bool isFinal = args->arg(1)->asNumber()->isFinal();
547          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
548            const float fVolumeLin = RTMath::DecibelToLinRatio(float(volume) / 1000.f);
549    
550          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
551              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 383  namespace LinuxSampler { Line 564  namespace LinuxSampler {
564              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
565              if (!pNote) return successResult();              if (!pNote) return successResult();
566    
567              const float fVolumeLin = RTMath::DecibelToLinRatio(float(volume) / 1000.f);              // if change_vol() was called immediately after note was triggered
568              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the volume to note object, but only if
569              /*if (relative)              // change_vol_time() has not been called before
570                  pNote->Override.Volume *= fVolumeLin;              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
571              else                  pNote->Override.VolumeTime <= DEFAULT_NOTE_VOLUME_TIME_S)
572                  pNote->Override.Volume = fVolumeLin;*/              {
573                    if (relative)
574              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;  
575                  else                  else
576                      pNote->Override.Volume = fVolumeLin;*/                      pNote->Override.Volume.Value = fVolumeLin;
577                    pNote->Override.Volume.Final = isFinal;
578                } else { // otherwise schedule the volume change ...
579                  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"
580                  e.Init(); // clear IDs                  e.Init(); // clear IDs
581                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
582                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
583                  e.Param.NoteSynthParam.Type     = Event::synth_param_volume;                  e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
584                  e.Param.NoteSynthParam.Delta    = fVolumeLin;                  e.Param.NoteSynthParam.Delta    = fVolumeLin;
585                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
586                        isFinal, relative, unit
587                    );
588                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
589              }              }
590            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
591                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
592                for (vmint i = 0; i < ids->arraySize(); ++i) {
593                    const ScriptID id = ids->evalIntElement(i);
594                    if (!id || !id.isNoteID()) continue;
595    
596                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
597                    if (!pNote) continue;
598    
599                    // if change_vol() was called immediately after note was triggered
600                    // then immediately apply the volume to Note object, but only if
601                    // change_vol_time() has not been called before
602                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
603                        pNote->Override.VolumeTime <= DEFAULT_NOTE_VOLUME_TIME_S)
604                    {
605                        if (relative)
606                            pNote->Override.Volume.Value *= fVolumeLin;
607                        else
608                            pNote->Override.Volume.Value = fVolumeLin;
609                        pNote->Override.Volume.Final = isFinal;
610                    } else { // otherwise schedule the volume change ...
611                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
612                        e.Init(); // clear IDs
613                        e.Type = Event::type_note_synth_param;
614                        e.Param.NoteSynthParam.NoteID   = id.noteID();
615                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
616                        e.Param.NoteSynthParam.Delta    = fVolumeLin;
617                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
618                            isFinal, relative, unit
619                        );
620                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
621                    }
622                }
623          }          }
624    
625          return successResult();          return successResult();
# Line 437  namespace LinuxSampler { Line 632  namespace LinuxSampler {
632      {      {
633      }      }
634    
635      bool InstrumentScriptVMFunction_change_tune::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_tune::acceptsArgType(vmint iArg, ExprType_t type) const {
636          if (iArg == 0)          if (iArg == 0)
637              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
638            else if (iArg == 1)
639                return type == INT_EXPR || type == REAL_EXPR;
640          else          else
641              return INT_EXPR;              return type == INT_EXPR;
642        }
643    
644        bool InstrumentScriptVMFunction_change_tune::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
645            return iArg == 1;
646        }
647    
648        bool InstrumentScriptVMFunction_change_tune::acceptsArgFinal(vmint iArg) const {
649            return iArg == 1;
650      }      }
651    
652      VMFnResult* InstrumentScriptVMFunction_change_tune::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_tune::exec(VMFnArgs* args) {
653          int tune = args->arg(1)->asInt()->evalInt(); // tuning change in milli cents          vmint tune =
654                (args->arg(1)->asNumber()->hasUnitFactorNow())
655                    ? args->arg(1)->asNumber()->evalCastInt(VM_MILLI,VM_CENTI)
656                    : args->arg(1)->asNumber()->evalCastInt(); // tuning change in milli cents
657            bool isFinal = args->arg(1)->asNumber()->isFinal();
658            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
659          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
660            const float fFreqRatio = RTMath::CentsToFreqRatioUnlimited(float(tune) / 1000.f);
661    
662          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
663              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 465  namespace LinuxSampler { Line 676  namespace LinuxSampler {
676              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
677              if (!pNote) return successResult();              if (!pNote) return successResult();
678    
679              const float fFreqRatio = RTMath::CentsToFreqRatioUnlimited(float(tune) / 1000.f);              // if change_tune() was called immediately after note was triggered
680              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the tuning to Note object, but only if
681              /*if (relative)              // change_tune_time() has not been called before
682                  pNote->Override.Pitch *= fFreqRatio;              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
683              else                  pNote->Override.PitchTime <= DEFAULT_NOTE_PITCH_TIME_S)
684                  pNote->Override.Pitch = fFreqRatio;*/              {
685                    if (relative)
686              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;  
687                  else                  else
688                      pNote->Override.Pitch = fFreqRatio;*/                      pNote->Override.Pitch.Value = fFreqRatio;
689                    pNote->Override.Pitch.Final = isFinal;
690                } else { // otherwise schedule tuning change ...
691                  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"
692                  e.Init(); // clear IDs                  e.Init(); // clear IDs
693                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
694                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
695                  e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;                  e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;
696                  e.Param.NoteSynthParam.Delta    = fFreqRatio;                  e.Param.NoteSynthParam.Delta    = fFreqRatio;
697                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
698                        isFinal, relative, unit
699                    );
700                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
701              }              }
702            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
703                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
704                for (vmint i = 0; i < ids->arraySize(); ++i) {
705                    const ScriptID id = ids->evalIntElement(i);
706                    if (!id || !id.isNoteID()) continue;
707    
708                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
709                    if (!pNote) continue;
710    
711                    // if change_tune() was called immediately after note was triggered
712                    // then immediately apply the tuning to Note object, but only if
713                    // change_tune_time() has not been called before
714                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime &&
715                        pNote->Override.PitchTime <= DEFAULT_NOTE_PITCH_TIME_S)
716                    {
717                        if (relative)
718                            pNote->Override.Pitch.Value *= fFreqRatio;
719                        else
720                            pNote->Override.Pitch.Value = fFreqRatio;
721                        pNote->Override.Pitch.Final = isFinal;
722                    } else { // otherwise schedule tuning change ...
723                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
724                        e.Init(); // clear IDs
725                        e.Type = Event::type_note_synth_param;
726                        e.Param.NoteSynthParam.NoteID   = id.noteID();
727                        e.Param.NoteSynthParam.Type     = Event::synth_param_pitch;
728                        e.Param.NoteSynthParam.Delta    = fFreqRatio;
729                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
730                            isFinal, relative, unit
731                        );
732                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
733                    }
734                }
735          }          }
736    
737          return successResult();          return successResult();
# Line 519  namespace LinuxSampler { Line 744  namespace LinuxSampler {
744      {      {
745      }      }
746    
747      bool InstrumentScriptVMFunction_change_pan::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_pan::acceptsArgType(vmint iArg, ExprType_t type) const {
748          if (iArg == 0)          if (iArg == 0)
749              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
750          else          else
751              return INT_EXPR;              return type == INT_EXPR;
752        }
753    
754        bool InstrumentScriptVMFunction_change_pan::acceptsArgFinal(vmint iArg) const {
755            return iArg == 1;
756      }      }
757    
758      VMFnResult* InstrumentScriptVMFunction_change_pan::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_pan::exec(VMFnArgs* args) {
759          int pan = args->arg(1)->asInt()->evalInt();          vmint pan    = args->arg(1)->asInt()->evalInt();
760            bool isFinal = args->arg(1)->asInt()->isFinal();
761          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;          bool relative = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : false;
762    
763          if (pan > 1000) {          if (pan > 1000) {
# Line 537  namespace LinuxSampler { Line 767  namespace LinuxSampler {
767              wrnMsg("change_pan(): argument 2 may not be smaller than -1000");              wrnMsg("change_pan(): argument 2 may not be smaller than -1000");
768              pan = -1000;              pan = -1000;
769          }          }
770            const float fPan = float(pan) / 1000.f;
771    
772          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
773              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 555  namespace LinuxSampler { Line 786  namespace LinuxSampler {
786              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
787              if (!pNote) return successResult();              if (!pNote) return successResult();
788    
789              const float fPan = float(pan) / 1000.f;              // if change_pan() was called immediately after note was triggered
790              // commented out, performed by EngineBase::ProcessNoteSynthParam() for time accuracy behavior              // then immediately apply the panning to Note object
791              /*if (relative) {              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
792                  pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, fPan, ++pNote->Override.PanSources);                  if (relative) {
793              } 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);  
794                  } else {                  } else {
795                      pNote->Override.Pan = fPan;                      pNote->Override.Pan.Value = fPan;
796                      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
797                  }*/                  }
798                    pNote->Override.Pan.Final = isFinal;
799                } else { // otherwise schedule panning change ...
800                  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"
801                  e.Init(); // clear IDs                  e.Init(); // clear IDs
802                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
803                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
804                  e.Param.NoteSynthParam.Type     = Event::synth_param_pan;                  e.Param.NoteSynthParam.Type     = Event::synth_param_pan;
805                  e.Param.NoteSynthParam.Delta    = fPan;                  e.Param.NoteSynthParam.Delta    = fPan;
806                  e.Param.NoteSynthParam.Relative = relative;                  e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
807                        isFinal, relative, false
808                    );
809                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
810              }              }
811            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
812                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
813                for (vmint i = 0; i < ids->arraySize(); ++i) {
814                    const ScriptID id = ids->evalIntElement(i);
815                    if (!id || !id.isNoteID()) continue;
816    
817                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
818                    if (!pNote) continue;
819    
820                    // if change_pan() was called immediately after note was triggered
821                    // then immediately apply the panning to Note object
822                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
823                        if (relative) {
824                            pNote->Override.Pan.Value = RTMath::RelativeSummedAvg(pNote->Override.Pan.Value, fPan, ++pNote->Override.Pan.Sources);
825                        } else {
826                            pNote->Override.Pan.Value = fPan;
827                            pNote->Override.Pan.Sources = 1; // only relevant on subsequent change_pan() calls on same note with 'relative' being set
828                        }
829                        pNote->Override.Pan.Final = isFinal;
830                    } else { // otherwise schedule panning change ...
831                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
832                        e.Init(); // clear IDs
833                        e.Type = Event::type_note_synth_param;
834                        e.Param.NoteSynthParam.NoteID   = id.noteID();
835                        e.Param.NoteSynthParam.Type     = Event::synth_param_pan;
836                        e.Param.NoteSynthParam.Delta    = fPan;
837                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
838                            isFinal, relative, false
839                        );
840                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
841                    }
842                }
843          }          }
844    
845          return successResult();          return successResult();
846      }      }
847    
848      #define VM_FILTER_PAR_MAX_VALUE 1000000      #define VM_FILTER_PAR_MAX_VALUE 1000000
849        #define VM_FILTER_PAR_MAX_HZ 30000
850      #define VM_EG_PAR_MAX_VALUE 1000000      #define VM_EG_PAR_MAX_VALUE 1000000
851    
852      // change_cutoff() function      // change_cutoff() function
# Line 616  namespace LinuxSampler { Line 856  namespace LinuxSampler {
856      {      {
857      }      }
858    
859      bool InstrumentScriptVMFunction_change_cutoff::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_cutoff::acceptsArgType(vmint iArg, ExprType_t type) const {
860          if (iArg == 0)          if (iArg == 0)
861              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
862            else if (iArg == 1)
863                return type == INT_EXPR || type == REAL_EXPR;
864          else          else
865              return INT_EXPR;              return type == INT_EXPR;
866        }
867    
868        bool InstrumentScriptVMFunction_change_cutoff::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
869            if (iArg == 1)
870                return type == VM_NO_UNIT || type == VM_HERTZ;
871            else
872                return type == VM_NO_UNIT;
873        }
874    
875        bool InstrumentScriptVMFunction_change_cutoff::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
876            return iArg == 1 && type == VM_HERTZ; // only allow metric prefix(es) if 'Hz' is used as unit type
877        }
878    
879        bool InstrumentScriptVMFunction_change_cutoff::acceptsArgFinal(vmint iArg) const {
880            return iArg == 1;
881        }
882    
883        void InstrumentScriptVMFunction_change_cutoff::checkArgs(VMFnArgs* args,
884                                                                 std::function<void(String)> err,
885                                                                 std::function<void(String)> wrn)
886        {
887            // super class checks
888            Super::checkArgs(args, err, wrn);
889    
890            // own checks ...
891            if (args->argsCount() >= 2) {
892                VMNumberExpr* argCutoff = args->arg(1)->asNumber();
893                if (argCutoff->unitType() && !argCutoff->isFinal()) {
894                    wrn("Argument 2 implies 'final' value when using Hz as unit for cutoff frequency.");
895                }
896            }
897      }      }
898    
899      VMFnResult* InstrumentScriptVMFunction_change_cutoff::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_cutoff::exec(VMFnArgs* args) {
900          int cutoff = args->arg(1)->asInt()->evalInt();          const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
901          if (cutoff > VM_FILTER_PAR_MAX_VALUE) {          vmint cutoff =
902              wrnMsg("change_cutoff(): argument 2 may not be larger than 1000000");              (unit) ?
903                    args->arg(1)->asNumber()->evalCastInt(VM_NO_PREFIX) :
904                    args->arg(1)->asNumber()->evalCastInt();
905            const bool isFinal =
906                (unit) ?
907                    true : // imply 'final' value if unit type is used
908                    args->arg(1)->asNumber()->isFinal();
909            if (!unit && cutoff > VM_FILTER_PAR_MAX_VALUE) {
910                wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_VALUE));
911              cutoff = VM_FILTER_PAR_MAX_VALUE;              cutoff = VM_FILTER_PAR_MAX_VALUE;
912            } else if (unit && cutoff > VM_FILTER_PAR_MAX_HZ) {
913                wrnMsg("change_cutoff(): argument 2 may not be larger than " strfy(VM_FILTER_PAR_MAX_HZ) " Hz");
914                cutoff = VM_FILTER_PAR_MAX_HZ;
915          } else if (cutoff < 0) {          } else if (cutoff < 0) {
916              wrnMsg("change_cutoff(): argument 2 may not be negative");              wrnMsg("change_cutoff(): argument 2 may not be negative");
917              cutoff = 0;              cutoff = 0;
918          }          }
919            const float fCutoff =
920                (unit) ? cutoff : float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);
921    
922          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
923              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 650  namespace LinuxSampler { Line 936  namespace LinuxSampler {
936              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
937              if (!pNote) return successResult();              if (!pNote) return successResult();
938    
939              const float fCutoff = float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);              // if change_cutoff() was called immediately after note was triggered
940                // then immediately apply cutoff to Note object
941              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
942              e.Init(); // clear IDs                  pNote->Override.Cutoff.Value = fCutoff;
943              e.Type = Event::type_note_synth_param;                  pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
944              e.Param.NoteSynthParam.NoteID   = id.noteID();              } else { // otherwise schedule cutoff change ...
945              e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
946              e.Param.NoteSynthParam.Delta    = fCutoff;                  e.Init(); // clear IDs
947              e.Param.NoteSynthParam.Relative = false;                  e.Type = Event::type_note_synth_param;
948                    e.Param.NoteSynthParam.NoteID   = id.noteID();
949              pEngineChannel->ScheduleEventMicroSec(&e, 0);                  e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;
950                    e.Param.NoteSynthParam.Delta    = fCutoff;
951                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
952                        isFinal, false, unit
953                    );
954                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
955                }
956          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
957              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
958              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
959                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
960                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
961    
962                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
963                  if (!pNote) continue;                  if (!pNote) continue;
964    
965                  const float fCutoff = float(cutoff) / float(VM_FILTER_PAR_MAX_VALUE);                  // if change_cutoff() was called immediately after note was triggered
966                    // then immediately apply cutoff to Note object
967                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
968                  e.Init(); // clear IDs                      pNote->Override.Cutoff.Value = fCutoff;
969                  e.Type = Event::type_note_synth_param;                      pNote->Override.Cutoff.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
970                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  } else { // otherwise schedule cutoff change ...
971                  e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;                      Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
972                  e.Param.NoteSynthParam.Delta    = fCutoff;                      e.Init(); // clear IDs
973                  e.Param.NoteSynthParam.Relative = false;                      e.Type = Event::type_note_synth_param;
974                        e.Param.NoteSynthParam.NoteID   = id.noteID();
975                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                      e.Param.NoteSynthParam.Type     = Event::synth_param_cutoff;
976                        e.Param.NoteSynthParam.Delta    = fCutoff;
977                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
978                            isFinal, false, unit
979                        );
980                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
981                    }
982              }              }
983          }          }
984    
# Line 694  namespace LinuxSampler { Line 992  namespace LinuxSampler {
992      {      {
993      }      }
994    
995      bool InstrumentScriptVMFunction_change_reso::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_reso::acceptsArgType(vmint iArg, ExprType_t type) const {
996          if (iArg == 0)          if (iArg == 0)
997              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
998          else          else
999              return INT_EXPR;              return type == INT_EXPR;
1000        }
1001    
1002        bool InstrumentScriptVMFunction_change_reso::acceptsArgFinal(vmint iArg) const {
1003            return iArg == 1;
1004      }      }
1005    
1006      VMFnResult* InstrumentScriptVMFunction_change_reso::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_reso::exec(VMFnArgs* args) {
1007          int resonance = args->arg(1)->asInt()->evalInt();          vmint resonance = args->arg(1)->asInt()->evalInt();
1008            bool isFinal    = args->arg(1)->asInt()->isFinal();
1009          if (resonance > VM_FILTER_PAR_MAX_VALUE) {          if (resonance > VM_FILTER_PAR_MAX_VALUE) {
1010              wrnMsg("change_reso(): argument 2 may not be larger than 1000000");              wrnMsg("change_reso(): argument 2 may not be larger than 1000000");
1011              resonance = VM_FILTER_PAR_MAX_VALUE;              resonance = VM_FILTER_PAR_MAX_VALUE;
# Line 710  namespace LinuxSampler { Line 1013  namespace LinuxSampler {
1013              wrnMsg("change_reso(): argument 2 may not be negative");              wrnMsg("change_reso(): argument 2 may not be negative");
1014              resonance = 0;              resonance = 0;
1015          }          }
1016            const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);
1017    
1018          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
1019              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 728  namespace LinuxSampler { Line 1032  namespace LinuxSampler {
1032              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1033              if (!pNote) return successResult();              if (!pNote) return successResult();
1034    
1035              const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);              // if change_reso() was called immediately after note was triggered
1036                // then immediately apply resonance to Note object
1037              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1038              e.Init(); // clear IDs                  pNote->Override.Resonance.Value = fResonance;
1039              e.Type = Event::type_note_synth_param;                  pNote->Override.Resonance.Final = isFinal;
1040              e.Param.NoteSynthParam.NoteID   = id.noteID();              } else { // otherwise schedule resonance change ...
1041              e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1042              e.Param.NoteSynthParam.Delta    = fResonance;                  e.Init(); // clear IDs
1043              e.Param.NoteSynthParam.Relative = false;                  e.Type = Event::type_note_synth_param;
1044                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1045              pEngineChannel->ScheduleEventMicroSec(&e, 0);                  e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;
1046                    e.Param.NoteSynthParam.Delta    = fResonance;
1047                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1048                        isFinal, false, false
1049                    );
1050                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1051                }
1052          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1053              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1054              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1055                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1056                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1057    
1058                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1059                  if (!pNote) continue;                  if (!pNote) continue;
1060    
1061                  const float fResonance = float(resonance) / float(VM_FILTER_PAR_MAX_VALUE);                  // if change_reso() was called immediately after note was triggered
1062                    // then immediately apply resonance to Note object
1063                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1064                  e.Init(); // clear IDs                      pNote->Override.Resonance.Value = fResonance;
1065                  e.Type = Event::type_note_synth_param;                      pNote->Override.Resonance.Final = isFinal;
1066                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  } else { // otherwise schedule resonance change ...
1067                  e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;                      Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1068                  e.Param.NoteSynthParam.Delta    = fResonance;                      e.Init(); // clear IDs
1069                  e.Param.NoteSynthParam.Relative = false;                      e.Type = Event::type_note_synth_param;
1070                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1071                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                      e.Param.NoteSynthParam.Type     = Event::synth_param_resonance;
1072                        e.Param.NoteSynthParam.Delta    = fResonance;
1073                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1074                            isFinal, false, false
1075                        );
1076                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1077                    }
1078              }              }
1079          }          }
1080    
# Line 772  namespace LinuxSampler { Line 1088  namespace LinuxSampler {
1088      {      {
1089      }      }
1090    
1091      bool InstrumentScriptVMFunction_change_attack::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_attack::acceptsArgType(vmint iArg, ExprType_t type) const {
1092          if (iArg == 0)          if (iArg == 0)
1093              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
1094          else          else
1095              return INT_EXPR;              return type == INT_EXPR || type == REAL_EXPR;
1096        }
1097    
1098        bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1099            if (iArg == 1)
1100                return type == VM_NO_UNIT || type == VM_SECOND;
1101            else
1102                return type == VM_NO_UNIT;
1103        }
1104    
1105        bool InstrumentScriptVMFunction_change_attack::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1106            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1107        }
1108    
1109        bool InstrumentScriptVMFunction_change_attack::acceptsArgFinal(vmint iArg) const {
1110            return iArg == 1;
1111        }
1112    
1113        void InstrumentScriptVMFunction_change_attack::checkArgs(VMFnArgs* args,
1114                                                                 std::function<void(String)> err,
1115                                                                 std::function<void(String)> wrn)
1116        {
1117            // super class checks
1118            Super::checkArgs(args, err, wrn);
1119    
1120            // own checks ...
1121            if (args->argsCount() >= 2) {
1122                VMNumberExpr* argTime = args->arg(1)->asNumber();
1123                if (argTime->unitType() && !argTime->isFinal()) {
1124                    wrn("Argument 2 implies 'final' value when using seconds as unit for attack time.");
1125                }
1126            }
1127      }      }
1128    
1129      VMFnResult* InstrumentScriptVMFunction_change_attack::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_attack::exec(VMFnArgs* args) {
1130          int attack = args->arg(1)->asInt()->evalInt();          const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1131          if (attack > VM_EG_PAR_MAX_VALUE) {          vmint attack =
1132              wrnMsg("change_attack(): argument 2 may not be larger than 1000000");              (unit) ?
1133              attack = VM_EG_PAR_MAX_VALUE;                  args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1134          } else if (attack < 0) {                  args->arg(1)->asNumber()->evalCastInt();
1135            const bool isFinal =
1136                (unit) ?
1137                    true : // imply 'final' value if unit type is used
1138                    args->arg(1)->asNumber()->isFinal();
1139            // note: intentionally not checking against a max. value here!
1140            // (to allow i.e. passing 2000000 for doubling the attack time)
1141            if (attack < 0) {
1142              wrnMsg("change_attack(): argument 2 may not be negative");              wrnMsg("change_attack(): argument 2 may not be negative");
1143              attack = 0;              attack = 0;
1144          }          }
1145          const float fAttack = float(attack) / float(VM_EG_PAR_MAX_VALUE);          const float fAttack =
1146                (unit) ? attack : float(attack) / float(VM_EG_PAR_MAX_VALUE);
1147    
1148          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
1149              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 805  namespace LinuxSampler { Line 1160  namespace LinuxSampler {
1160              }              }
1161    
1162              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1163              if (!pNote) return successResult();                          if (!pNote) return successResult();
   
             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_attack;  
             e.Param.NoteSynthParam.Delta    = fAttack;  
             e.Param.NoteSynthParam.Relative = false;  
1164    
1165              pEngineChannel->ScheduleEventMicroSec(&e, 0);              // if change_attack() was called immediately after note was triggered
1166                // then immediately apply attack to Note object
1167                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1168                    pNote->Override.Attack.Value = fAttack;
1169                    pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1170                } else { // otherwise schedule attack change ...
1171                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1172                    e.Init(); // clear IDs
1173                    e.Type = Event::type_note_synth_param;
1174                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1175                    e.Param.NoteSynthParam.Type     = Event::synth_param_attack;
1176                    e.Param.NoteSynthParam.Delta    = fAttack;
1177                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1178                        isFinal, false, unit
1179                    );
1180                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1181                }
1182          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1183              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1184              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1185                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1186                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1187    
1188                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1189                  if (!pNote) continue;                  if (!pNote) continue;
1190    
1191                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  // if change_attack() was called immediately after note was triggered
1192                  e.Init(); // clear IDs                  // then immediately apply attack to Note object
1193                  e.Type = Event::type_note_synth_param;                  if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1194                  e.Param.NoteSynthParam.NoteID   = id.noteID();                      pNote->Override.Attack.Value = fAttack;
1195                  e.Param.NoteSynthParam.Type     = Event::synth_param_attack;                      pNote->Override.Attack.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1196                  e.Param.NoteSynthParam.Delta    = fAttack;                  } else { // otherwise schedule attack change ...
1197                  e.Param.NoteSynthParam.Relative = false;                      Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1198                        e.Init(); // clear IDs
1199                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                      e.Type = Event::type_note_synth_param;
1200                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1201                        e.Param.NoteSynthParam.Type     = Event::synth_param_attack;
1202                        e.Param.NoteSynthParam.Delta    = fAttack;
1203                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1204                            isFinal, false, unit
1205                        );
1206                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1207                    }
1208              }              }
1209          }          }
1210    
# Line 847  namespace LinuxSampler { Line 1218  namespace LinuxSampler {
1218      {      {
1219      }      }
1220    
1221      bool InstrumentScriptVMFunction_change_decay::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_decay::acceptsArgType(vmint iArg, ExprType_t type) const {
1222          if (iArg == 0)          if (iArg == 0)
1223              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
1224          else          else
1225              return INT_EXPR;              return type == INT_EXPR || type == REAL_EXPR;
1226        }
1227    
1228        bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1229            if (iArg == 1)
1230                return type == VM_NO_UNIT || type == VM_SECOND;
1231            else
1232                return type == VM_NO_UNIT;
1233        }
1234    
1235        bool InstrumentScriptVMFunction_change_decay::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1236            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1237        }
1238    
1239        bool InstrumentScriptVMFunction_change_decay::acceptsArgFinal(vmint iArg) const {
1240            return iArg == 1;
1241        }
1242    
1243        void InstrumentScriptVMFunction_change_decay::checkArgs(VMFnArgs* args,
1244                                                                std::function<void(String)> err,
1245                                                                std::function<void(String)> wrn)
1246        {
1247            // super class checks
1248            Super::checkArgs(args, err, wrn);
1249    
1250            // own checks ...
1251            if (args->argsCount() >= 2) {
1252                VMNumberExpr* argTime = args->arg(1)->asNumber();
1253                if (argTime->unitType() && !argTime->isFinal()) {
1254                    wrn("Argument 2 implies 'final' value when using seconds as unit for decay time.");
1255                }
1256            }
1257      }      }
1258    
1259      VMFnResult* InstrumentScriptVMFunction_change_decay::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_decay::exec(VMFnArgs* args) {
1260          int decay = args->arg(1)->asInt()->evalInt();          const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1261          if (decay > VM_EG_PAR_MAX_VALUE) {          vmint decay =
1262              wrnMsg("change_decay(): argument 2 may not be larger than 1000000");              (unit) ?
1263              decay = VM_EG_PAR_MAX_VALUE;                  args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1264          } else if (decay < 0) {                  args->arg(1)->asNumber()->evalCastInt();
1265            const bool isFinal =
1266                (unit) ?
1267                    true : // imply 'final' value if unit type is used
1268                    args->arg(1)->asNumber()->isFinal();
1269            // note: intentionally not checking against a max. value here!
1270            // (to allow i.e. passing 2000000 for doubling the decay time)
1271            if (decay < 0) {
1272              wrnMsg("change_decay(): argument 2 may not be negative");              wrnMsg("change_decay(): argument 2 may not be negative");
1273              decay = 0;              decay = 0;
1274          }          }
1275          const float fDecay = float(decay) / float(VM_EG_PAR_MAX_VALUE);          const float fDecay =
1276                (unit) ? decay : float(decay) / float(VM_EG_PAR_MAX_VALUE);
1277    
1278          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
1279              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 880  namespace LinuxSampler { Line 1290  namespace LinuxSampler {
1290              }              }
1291    
1292              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1293              if (!pNote) return successResult();                          if (!pNote) return successResult();
   
             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_decay;  
             e.Param.NoteSynthParam.Delta    = fDecay;  
             e.Param.NoteSynthParam.Relative = false;  
1294    
1295              pEngineChannel->ScheduleEventMicroSec(&e, 0);              // if change_decay() was called immediately after note was triggered
1296                // then immediately apply decay to Note object
1297                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1298                    pNote->Override.Decay.Value = fDecay;
1299                    pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1300                } else { // otherwise schedule decay change ...
1301                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1302                    e.Init(); // clear IDs
1303                    e.Type = Event::type_note_synth_param;
1304                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1305                    e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1306                    e.Param.NoteSynthParam.Delta    = fDecay;
1307                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1308                        isFinal, false, unit
1309                    );
1310                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1311                }
1312          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1313              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1314              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1315                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1316                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1317    
1318                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1319                  if (!pNote) continue;                  if (!pNote) continue;
1320    
1321                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"                  // if change_decay() was called immediately after note was triggered
1322                  e.Init(); // clear IDs                  // then immediately apply decay to Note object
1323                  e.Type = Event::type_note_synth_param;                  if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1324                  e.Param.NoteSynthParam.NoteID   = id.noteID();                      pNote->Override.Decay.Value = fDecay;
1325                  e.Param.NoteSynthParam.Type     = Event::synth_param_decay;                      pNote->Override.Decay.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1326                  e.Param.NoteSynthParam.Delta    = fDecay;                  } else { // otherwise schedule decay change ...
1327                  e.Param.NoteSynthParam.Relative = false;                      Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1328                        e.Init(); // clear IDs
1329                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                      e.Type = Event::type_note_synth_param;
1330                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1331                        e.Param.NoteSynthParam.Type     = Event::synth_param_decay;
1332                        e.Param.NoteSynthParam.Delta    = fDecay;
1333                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1334                            isFinal, false, unit
1335                        );
1336                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1337                    }
1338              }              }
1339          }          }
1340    
# Line 922  namespace LinuxSampler { Line 1348  namespace LinuxSampler {
1348      {      {
1349      }      }
1350    
1351      bool InstrumentScriptVMFunction_change_release::acceptsArgType(int iArg, ExprType_t type) const {      bool InstrumentScriptVMFunction_change_release::acceptsArgType(vmint iArg, ExprType_t type) const {
1352          if (iArg == 0)          if (iArg == 0)
1353              return type == INT_EXPR || type == INT_ARR_EXPR;              return type == INT_EXPR || type == INT_ARR_EXPR;
1354          else          else
1355              return INT_EXPR;              return type == INT_EXPR || type == REAL_EXPR;
1356        }
1357    
1358        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1359            if (iArg == 1)
1360                return type == VM_NO_UNIT || type == VM_SECOND;
1361            else
1362                return type == VM_NO_UNIT;
1363        }
1364    
1365        bool InstrumentScriptVMFunction_change_release::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1366            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1367        }
1368    
1369        bool InstrumentScriptVMFunction_change_release::acceptsArgFinal(vmint iArg) const {
1370            return iArg == 1;
1371        }
1372    
1373        void InstrumentScriptVMFunction_change_release::checkArgs(VMFnArgs* args,
1374                                                                  std::function<void(String)> err,
1375                                                                  std::function<void(String)> wrn)
1376        {
1377            // super class checks
1378            Super::checkArgs(args, err, wrn);
1379    
1380            // own checks ...
1381            if (args->argsCount() >= 2) {
1382                VMNumberExpr* argTime = args->arg(1)->asNumber();
1383                if (argTime->unitType() && !argTime->isFinal()) {
1384                    wrn("Argument 2 implies 'final' value when using seconds as unit for release time.");
1385                }
1386            }
1387      }      }
1388    
1389      VMFnResult* InstrumentScriptVMFunction_change_release::exec(VMFnArgs* args) {      VMFnResult* InstrumentScriptVMFunction_change_release::exec(VMFnArgs* args) {
1390          int release = args->arg(1)->asInt()->evalInt();          const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1391          if (release > VM_EG_PAR_MAX_VALUE) {          vmint release =
1392              wrnMsg("change_release(): argument 2 may not be larger than 1000000");              (unit) ?
1393              release = VM_EG_PAR_MAX_VALUE;                  args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1394          } else if (release < 0) {                  args->arg(1)->asNumber()->evalCastInt();
1395            const bool isFinal =
1396                (unit) ?
1397                    true : // imply 'final' value if unit type is used
1398                    args->arg(1)->asNumber()->isFinal();
1399            // note: intentionally not checking against a max. value here!
1400            // (to allow i.e. passing 2000000 for doubling the release time)
1401            if (release < 0) {
1402              wrnMsg("change_release(): argument 2 may not be negative");              wrnMsg("change_release(): argument 2 may not be negative");
1403              release = 0;              release = 0;
1404          }          }
1405          const float fRelease = float(release) / float(VM_EG_PAR_MAX_VALUE);          const float fRelease =
1406                (unit) ? release : float(release) / float(VM_EG_PAR_MAX_VALUE);
1407    
1408          AbstractEngineChannel* pEngineChannel =          AbstractEngineChannel* pEngineChannel =
1409              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);              static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
# Line 955  namespace LinuxSampler { Line 1420  namespace LinuxSampler {
1420              }              }
1421    
1422              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );              NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1423              if (!pNote) return successResult();                          if (!pNote) return successResult();
1424    
1425              Event e = m_vm->m_event->cause; // copy to get fragment time for "now"              // if change_release() was called immediately after note was triggered
1426              e.Init(); // clear IDs              // then immediately apply relase to Note object
1427              e.Type = Event::type_note_synth_param;              if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1428              e.Param.NoteSynthParam.NoteID   = id.noteID();                  pNote->Override.Release.Value = fRelease;
1429              e.Param.NoteSynthParam.Type     = Event::synth_param_release;                  pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1430              e.Param.NoteSynthParam.Delta    = fRelease;              } else { // otherwise schedule release change ...
1431              e.Param.NoteSynthParam.Relative = false;                  Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1432                    e.Init(); // clear IDs
1433                    e.Type = Event::type_note_synth_param;
1434                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1435                    e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1436                    e.Param.NoteSynthParam.Delta    = fRelease;
1437                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1438                        isFinal, false, unit
1439                    );
1440                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1441                }
1442            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1443                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1444                for (vmint i = 0; i < ids->arraySize(); ++i) {
1445                    const ScriptID id = ids->evalIntElement(i);
1446                    if (!id || !id.isNoteID()) continue;
1447    
1448              pEngineChannel->ScheduleEventMicroSec(&e, 0);                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1449                    if (!pNote) continue;
1450    
1451                    // if change_release() was called immediately after note was triggered
1452                    // then immediately apply relase to Note object
1453                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1454                        pNote->Override.Release.Value = fRelease;
1455                        pNote->Override.Release.Scope = NoteBase::scopeBy_FinalUnit(isFinal, unit);
1456                    } else { // otherwise schedule release change ...
1457                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1458                        e.Init(); // clear IDs
1459                        e.Type = Event::type_note_synth_param;
1460                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1461                        e.Param.NoteSynthParam.Type     = Event::synth_param_release;
1462                        e.Param.NoteSynthParam.Delta    = fRelease;
1463                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1464                            isFinal, false, unit
1465                        );
1466                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1467                    }
1468                }
1469            }
1470    
1471            return successResult();
1472        }
1473    
1474        // template for change_*() functions
1475    
1476        bool VMChangeSynthParamFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1477            if (iArg == 0)
1478                return type == INT_EXPR || type == INT_ARR_EXPR;
1479            else
1480                return type == INT_EXPR || (m_acceptReal && type == REAL_EXPR);
1481        }
1482    
1483        bool VMChangeSynthParamFunction::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1484            if (iArg == 1)
1485                return type == VM_NO_UNIT || type == m_unit;
1486            else
1487                return type == VM_NO_UNIT;
1488        }
1489    
1490        bool VMChangeSynthParamFunction::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1491            return m_acceptUnitPrefix && iArg == 1 && type == m_unit; // only allow metric prefix(es) if approprirate unit type is used (e.g. Hz)
1492        }
1493    
1494        bool VMChangeSynthParamFunction::acceptsArgFinal(vmint iArg) const {
1495            return (m_acceptFinal) ? (iArg == 1) : false;
1496        }
1497    
1498        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Param& param, const bool bFinal, const StdUnit_t unit) {
1499            param.Scope = NoteBase::scopeBy_FinalUnit(bFinal, unit);
1500        }
1501    
1502        inline static void setNoteParamScopeBy_FinalUnit(NoteBase::Norm& param, const bool bFinal, const StdUnit_t unit) {
1503            param.Final = bFinal;
1504        }
1505    
1506        inline static void setNoteParamScopeBy_FinalUnit(float& param, const bool bFinal, const StdUnit_t unit) {
1507            /* NOOP */
1508        }
1509    
1510        template<class T>
1511        inline static void setNoteParamValue(T& param, vmfloat value) {
1512            param.Value = value;
1513        }
1514    
1515        inline static void setNoteParamValue(float& param, vmfloat value) {
1516            param = value;
1517        }
1518    
1519        void VMChangeSynthParamFunction::checkArgs(VMFnArgs* args,
1520                                                   std::function<void(String)> err,
1521                                                   std::function<void(String)> wrn)
1522        {
1523            // super class checks
1524            Super::checkArgs(args, err, wrn);
1525    
1526            // own checks ...
1527            if (m_unit && m_unit != VM_BEL && args->argsCount() >= 2) {
1528                VMNumberExpr* arg = args->arg(1)->asNumber();
1529                if (arg && arg->unitType() && !arg->isFinal()) {
1530                    wrn("Argument 2 implies 'final' value when unit type " +
1531                        unitTypeStr(arg->unitType()) + " is used.");
1532                }
1533            }
1534        }
1535    
1536        // Arbitrarily chosen constant value symbolizing "no limit".
1537        #define NO_LIMIT 1315916909
1538    
1539        template<class T_NoteParamType, T_NoteParamType NoteBase::_Override::*T_noteParam,
1540                 vmint T_synthParam,
1541                 vmint T_minValueNorm, vmint T_maxValueNorm, bool T_normalizeNorm,
1542                 vmint T_minValueUnit, vmint T_maxValueUnit,
1543                 MetricPrefix_t T_unitPrefix0, MetricPrefix_t ... T_unitPrefixN>
1544        VMFnResult* VMChangeSynthParamFunction::execTemplate(VMFnArgs* args, const char* functionName)
1545        {
1546            const StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1547            const bool isFinal =
1548                (m_unit && m_unit != VM_BEL && unit) ?
1549                    true : // imply 'final' value if unit type is used (except of 'Bel' which may be relative)
1550                    args->arg(1)->asNumber()->isFinal();
1551            vmint value =
1552                (m_acceptUnitPrefix && ((m_unit && unit) || (!m_unit && args->arg(1)->asNumber()->hasUnitFactorNow())))
1553                    ? args->arg(1)->asNumber()->evalCastInt(T_unitPrefix0, T_unitPrefixN ...)
1554                    : args->arg(1)->asNumber()->evalCastInt();
1555    
1556            // check if passed value is in allowed range
1557            if (unit && m_unit) {
1558                if (T_maxValueUnit != NO_LIMIT && value > T_maxValueUnit) {
1559                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueUnit));
1560                    value = T_maxValueUnit;
1561                } else if (T_minValueUnit != NO_LIMIT && value < T_minValueUnit) {
1562                    if (T_minValueUnit == 0)
1563                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1564                    else
1565                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueUnit));
1566                    value = T_minValueUnit;
1567                }
1568            } else { // value was passed to this function without a unit ...
1569                if (T_maxValueNorm != NO_LIMIT && value > T_maxValueNorm) {
1570                    wrnMsg(String(functionName) + "(): argument 2 may not be larger than " + ToString(T_maxValueNorm));
1571                    value = T_maxValueNorm;
1572                } else if (T_minValueNorm != NO_LIMIT && value < T_minValueNorm) {
1573                    if (T_minValueNorm == 0)
1574                        wrnMsg(String(functionName) + "(): argument 2 may not be negative");
1575                    else
1576                        wrnMsg(String(functionName) + "(): argument 2 may not be smaller than " + ToString(T_minValueNorm));
1577                    value = T_minValueNorm;
1578                }
1579            }
1580    
1581            // convert passed argument value to engine internal expected value range (i.e. 0.0 .. 1.0)
1582            const float fValue =
1583                (unit && m_unit) ?
1584                    (unit == VM_BEL) ?
1585                        RTMath::DecibelToLinRatio(float(value) * float(T_unitPrefix0) /*i.e. mdB -> dB*/) :
1586                        float(value) * VMUnit::unitFactor(T_unitPrefix0, T_unitPrefixN ...) /*i.e. us -> s*/ :
1587                    (T_normalizeNorm) ?
1588                        float(value) / ((T_maxValueNorm != NO_LIMIT) ? float(T_maxValueNorm) : 1000000.f/* fallback: value range used for most */) :
1589                        float(value) /* as is */;
1590    
1591            AbstractEngineChannel* pEngineChannel =
1592                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1593    
1594            if (args->arg(0)->exprType() == INT_EXPR) {
1595                const ScriptID id = args->arg(0)->asInt()->evalInt();
1596                if (!id) {
1597                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1598                    return successResult();
1599                }
1600                if (!id.isNoteID()) {
1601                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1602                    return successResult();
1603                }
1604    
1605                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1606                if (!pNote) return successResult();
1607    
1608                // if this change_*() script function was called immediately after
1609                // note was triggered then immediately apply the synth parameter
1610                // change to Note object
1611                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1612                    setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1613                    setNoteParamScopeBy_FinalUnit(
1614                        (pNote->Override.*T_noteParam),
1615                        isFinal, unit
1616                    );
1617                } else { // otherwise schedule this synth parameter change ...
1618                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1619                    e.Init(); // clear IDs
1620                    e.Type = Event::type_note_synth_param;
1621                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1622                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1623                    e.Param.NoteSynthParam.Delta    = fValue;
1624                    e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1625                        isFinal, false, unit
1626                    );
1627                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1628                }
1629          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {          } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1630              VMIntArrayExpr* ids = args->arg(0)->asIntArray();              VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1631              for (int i = 0; i < ids->arraySize(); ++i) {              for (vmint i = 0; i < ids->arraySize(); ++i) {
1632                    const ScriptID id = ids->evalIntElement(i);
1633                    if (!id || !id.isNoteID()) continue;
1634    
1635                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1636                    if (!pNote) continue;
1637    
1638                    // if this change_*() script function was called immediately after
1639                    // note was triggered then immediately apply the synth parameter
1640                    // change to Note object
1641                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1642                        setNoteParamValue(pNote->Override.*T_noteParam, fValue);
1643                        setNoteParamScopeBy_FinalUnit(
1644                            (pNote->Override.*T_noteParam),
1645                            isFinal, unit
1646                        );
1647                    } else { // otherwise schedule this synth parameter change ...
1648                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1649                        e.Init(); // clear IDs
1650                        e.Type = Event::type_note_synth_param;
1651                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1652                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1653                        e.Param.NoteSynthParam.Delta    = fValue;
1654                        e.Param.NoteSynthParam.Scope = Event::scopeBy_FinalRelativeUnit(
1655                            isFinal, false, unit
1656                        );
1657                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1658                    }
1659                }
1660            }
1661    
1662            return successResult();
1663        }
1664    
1665        // change_sustain() function
1666    
1667        VMFnResult* InstrumentScriptVMFunction_change_sustain::exec(VMFnArgs* args) {
1668            return VMChangeSynthParamFunction::execTemplate<
1669                        decltype(NoteBase::_Override::Sustain),
1670                        &NoteBase::_Override::Sustain,
1671                        Event::synth_param_sustain,
1672                        /* if value passed without unit */
1673                        0, NO_LIMIT, true,
1674                        /* if value passed WITH 'Bel' unit */
1675                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_sustain" );
1676        }
1677    
1678        // change_cutoff_attack() function
1679    
1680        VMFnResult* InstrumentScriptVMFunction_change_cutoff_attack::exec(VMFnArgs* args) {
1681            return VMChangeSynthParamFunction::execTemplate<
1682                        decltype(NoteBase::_Override::CutoffAttack),
1683                        &NoteBase::_Override::CutoffAttack,
1684                        Event::synth_param_cutoff_attack,
1685                        /* if value passed without unit */
1686                        0, NO_LIMIT, true,
1687                        /* if value passed with 'seconds' unit */
1688                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_attack" );
1689        }
1690    
1691        // change_cutoff_decay() function
1692    
1693        VMFnResult* InstrumentScriptVMFunction_change_cutoff_decay::exec(VMFnArgs* args) {
1694            return VMChangeSynthParamFunction::execTemplate<
1695                        decltype(NoteBase::_Override::CutoffDecay),
1696                        &NoteBase::_Override::CutoffDecay,
1697                        Event::synth_param_cutoff_decay,
1698                        /* if value passed without unit */
1699                        0, NO_LIMIT, true,
1700                        /* if value passed with 'seconds' unit */
1701                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_decay" );
1702        }
1703    
1704        // change_cutoff_sustain() function
1705    
1706        VMFnResult* InstrumentScriptVMFunction_change_cutoff_sustain::exec(VMFnArgs* args) {
1707            return VMChangeSynthParamFunction::execTemplate<
1708                        decltype(NoteBase::_Override::CutoffSustain),
1709                        &NoteBase::_Override::CutoffSustain,
1710                        Event::synth_param_cutoff_sustain,
1711                        /* if value passed without unit */
1712                        0, NO_LIMIT, true,
1713                        /* if value passed WITH 'Bel' unit */
1714                        NO_LIMIT, NO_LIMIT, VM_MILLI, VM_DECI>( args, "change_cutoff_sustain" );
1715        }
1716    
1717        // change_cutoff_release() function
1718    
1719        VMFnResult* InstrumentScriptVMFunction_change_cutoff_release::exec(VMFnArgs* args) {
1720            return VMChangeSynthParamFunction::execTemplate<
1721                        decltype(NoteBase::_Override::CutoffRelease),
1722                        &NoteBase::_Override::CutoffRelease,
1723                        Event::synth_param_cutoff_release,
1724                        /* if value passed without unit */
1725                        0, NO_LIMIT, true,
1726                        /* if value passed with 'seconds' unit */
1727                        0, NO_LIMIT, VM_MICRO>( args, "change_cutoff_release" );
1728        }
1729    
1730        // change_amp_lfo_depth() function
1731    
1732        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_depth::exec(VMFnArgs* args) {
1733            return VMChangeSynthParamFunction::execTemplate<
1734                        decltype(NoteBase::_Override::AmpLFODepth),
1735                        &NoteBase::_Override::AmpLFODepth,
1736                        Event::synth_param_amp_lfo_depth,
1737                        /* if value passed without unit */
1738                        0, 1000000, true,
1739                        /* not used (since this function does not accept unit) */
1740                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_amp_lfo_depth" );
1741        }
1742    
1743        // change_amp_lfo_freq() function
1744    
1745        VMFnResult* InstrumentScriptVMFunction_change_amp_lfo_freq::exec(VMFnArgs* args) {
1746            return VMChangeSynthParamFunction::execTemplate<
1747                        decltype(NoteBase::_Override::AmpLFOFreq),
1748                        &NoteBase::_Override::AmpLFOFreq,
1749                        Event::synth_param_amp_lfo_freq,
1750                        /* if value passed without unit */
1751                        0, 1000000, true,
1752                        /* if value passed with 'Hz' unit */
1753                        0, 30000, VM_NO_PREFIX>( args, "change_amp_lfo_freq" );
1754        }
1755    
1756        // change_cutoff_lfo_depth() function
1757    
1758        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_depth::exec(VMFnArgs* args) {
1759            return VMChangeSynthParamFunction::execTemplate<
1760                        decltype(NoteBase::_Override::CutoffLFODepth),
1761                        &NoteBase::_Override::CutoffLFODepth,
1762                        Event::synth_param_cutoff_lfo_depth,
1763                        /* if value passed without unit */
1764                        0, 1000000, true,
1765                        /* not used (since this function does not accept unit) */
1766                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_cutoff_lfo_depth" );
1767        }
1768    
1769        // change_cutoff_lfo_freq() function
1770    
1771        VMFnResult* InstrumentScriptVMFunction_change_cutoff_lfo_freq::exec(VMFnArgs* args) {
1772            return VMChangeSynthParamFunction::execTemplate<
1773                        decltype(NoteBase::_Override::CutoffLFOFreq),
1774                        &NoteBase::_Override::CutoffLFOFreq,
1775                        Event::synth_param_cutoff_lfo_freq,
1776                        /* if value passed without unit */
1777                        0, 1000000, true,
1778                        /* if value passed with 'Hz' unit */
1779                        0, 30000, VM_NO_PREFIX>( args, "change_cutoff_lfo_freq" );
1780        }
1781    
1782        // change_pitch_lfo_depth() function
1783    
1784        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_depth::exec(VMFnArgs* args) {
1785            return VMChangeSynthParamFunction::execTemplate<
1786                        decltype(NoteBase::_Override::PitchLFODepth),
1787                        &NoteBase::_Override::PitchLFODepth,
1788                        Event::synth_param_pitch_lfo_depth,
1789                        /* if value passed without unit */
1790                        0, 1000000, true,
1791                        /* not used (since this function does not accept unit) */
1792                        NO_LIMIT, NO_LIMIT, VM_NO_PREFIX>( args, "change_pitch_lfo_depth" );
1793        }
1794    
1795        // change_pitch_lfo_freq() function
1796    
1797        VMFnResult* InstrumentScriptVMFunction_change_pitch_lfo_freq::exec(VMFnArgs* args) {
1798            return VMChangeSynthParamFunction::execTemplate<
1799                        decltype(NoteBase::_Override::PitchLFOFreq),
1800                        &NoteBase::_Override::PitchLFOFreq,
1801                        Event::synth_param_pitch_lfo_freq,
1802                        /* if value passed without unit */
1803                        0, 1000000, true,
1804                        /* if value passed with 'Hz' unit */
1805                        0, 30000, VM_NO_PREFIX>( args, "change_pitch_lfo_freq" );
1806        }
1807    
1808        // change_vol_time() function
1809    
1810        VMFnResult* InstrumentScriptVMFunction_change_vol_time::exec(VMFnArgs* args) {
1811            return VMChangeSynthParamFunction::execTemplate<
1812                        decltype(NoteBase::_Override::VolumeTime),
1813                        &NoteBase::_Override::VolumeTime,
1814                        Event::synth_param_volume_time,
1815                        /* if value passed without unit (implying 'us' unit) */
1816                        0, NO_LIMIT, true,
1817                        /* if value passed with 'seconds' unit */
1818                        0, NO_LIMIT, VM_MICRO>( args, "change_vol_time" );
1819        }
1820    
1821        // change_tune_time() function
1822    
1823        VMFnResult* InstrumentScriptVMFunction_change_tune_time::exec(VMFnArgs* args) {
1824            return VMChangeSynthParamFunction::execTemplate<
1825                        decltype(NoteBase::_Override::PitchTime),
1826                        &NoteBase::_Override::PitchTime,
1827                        Event::synth_param_pitch_time,
1828                        /* if value passed without unit (implying 'us' unit) */
1829                        0, NO_LIMIT, true,
1830                        /* if value passed with 'seconds' unit */
1831                        0, NO_LIMIT, VM_MICRO>( args, "change_tune_time" );
1832        }
1833    
1834        // change_pan_time() function
1835    
1836        VMFnResult* InstrumentScriptVMFunction_change_pan_time::exec(VMFnArgs* args) {
1837            return VMChangeSynthParamFunction::execTemplate<
1838                        decltype(NoteBase::_Override::PanTime),
1839                        &NoteBase::_Override::PanTime,
1840                        Event::synth_param_pan_time,
1841                        /* if value passed without unit (implying 'us' unit) */
1842                        0, NO_LIMIT, true,
1843                        /* if value passed with 'seconds' unit */
1844                        0, NO_LIMIT, VM_MICRO>( args, "change_pan_time" );
1845        }
1846    
1847        // template for change_*_curve() functions
1848    
1849        bool VMChangeFadeCurveFunction::acceptsArgType(vmint iArg, ExprType_t type) const {
1850            if (iArg == 0)
1851                return type == INT_EXPR || type == INT_ARR_EXPR;
1852            else
1853                return type == INT_EXPR;
1854        }
1855    
1856        template<fade_curve_t NoteBase::_Override::*T_noteParam, vmint T_synthParam>
1857        VMFnResult* VMChangeFadeCurveFunction::execTemplate(VMFnArgs* args, const char* functionName) {
1858            vmint value = args->arg(1)->asInt()->evalInt();
1859            switch (value) {
1860                case FADE_CURVE_LINEAR:
1861                case FADE_CURVE_EASE_IN_EASE_OUT:
1862                    break;
1863                default:
1864                    wrnMsg(String(functionName) + "(): invalid curve type passed as argument 2");
1865                    return successResult();
1866            }
1867    
1868            AbstractEngineChannel* pEngineChannel =
1869                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1870    
1871            if (args->arg(0)->exprType() == INT_EXPR) {
1872                const ScriptID id = args->arg(0)->asInt()->evalInt();
1873                if (!id) {
1874                    wrnMsg(String(functionName) + "(): note ID for argument 1 may not be zero");
1875                    return successResult();
1876                }
1877                if (!id.isNoteID()) {
1878                    wrnMsg(String(functionName) + "(): argument 1 is not a note ID");
1879                    return successResult();
1880                }
1881    
1882                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1883                if (!pNote) return successResult();
1884    
1885                // if this change_*_curve() script function was called immediately after
1886                // note was triggered then immediately apply the synth parameter
1887                // change to Note object
1888                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1889                    pNote->Override.*T_noteParam = (fade_curve_t) value;
1890                } else { // otherwise schedule this synth parameter change ...
1891                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1892                    e.Init(); // clear IDs
1893                    e.Type = Event::type_note_synth_param;
1894                    e.Param.NoteSynthParam.NoteID   = id.noteID();
1895                    e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1896                    e.Param.NoteSynthParam.Delta    = value;
1897                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1898    
1899                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
1900                }
1901            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
1902                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
1903                for (vmint i = 0; i < ids->arraySize(); ++i) {
1904                  const ScriptID id = ids->evalIntElement(i);                  const ScriptID id = ids->evalIntElement(i);
1905                  if (!id || !id.isNoteID()) continue;                  if (!id || !id.isNoteID()) continue;
1906    
1907                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );                  NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
1908                  if (!pNote) continue;                  if (!pNote) continue;
1909    
1910                    // if this change_*_curve() script function was called immediately after
1911                    // note was triggered then immediately apply the synth parameter
1912                    // change to Note object
1913                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
1914                        pNote->Override.*T_noteParam = (fade_curve_t) value;
1915                    } else { // otherwise schedule this synth parameter change ...
1916                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
1917                        e.Init(); // clear IDs
1918                        e.Type = Event::type_note_synth_param;
1919                        e.Param.NoteSynthParam.NoteID   = id.noteID();
1920                        e.Param.NoteSynthParam.Type     = (Event::synth_param_t) T_synthParam;
1921                        e.Param.NoteSynthParam.Delta    = value;
1922                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
1923    
1924                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
1925                    }
1926                }
1927            }
1928    
1929            return successResult();
1930        }
1931    
1932        // change_vol_curve() function
1933    
1934        VMFnResult* InstrumentScriptVMFunction_change_vol_curve::exec(VMFnArgs* args) {
1935            return VMChangeFadeCurveFunction::execTemplate<
1936                        &NoteBase::_Override::VolumeCurve,
1937                        Event::synth_param_volume_curve>( args, "change_vol_curve" );
1938        }
1939    
1940        // change_tune_curve() function
1941    
1942        VMFnResult* InstrumentScriptVMFunction_change_tune_curve::exec(VMFnArgs* args) {
1943            return VMChangeFadeCurveFunction::execTemplate<
1944                        &NoteBase::_Override::PitchCurve,
1945                        Event::synth_param_pitch_curve>( args, "change_tune_curve" );
1946        }
1947    
1948        // change_pan_curve() function
1949    
1950        VMFnResult* InstrumentScriptVMFunction_change_pan_curve::exec(VMFnArgs* args) {
1951            return VMChangeFadeCurveFunction::execTemplate<
1952            &NoteBase::_Override::PanCurve,
1953            Event::synth_param_pan_curve>( args, "change_pan_curve" );
1954        }
1955    
1956        // fade_in() function
1957    
1958        InstrumentScriptVMFunction_fade_in::InstrumentScriptVMFunction_fade_in(InstrumentScriptVM* parent)
1959            : m_vm(parent)
1960        {
1961        }
1962    
1963        bool InstrumentScriptVMFunction_fade_in::acceptsArgType(vmint iArg, ExprType_t type) const {
1964            if (iArg == 0)
1965                return type == INT_EXPR || type == INT_ARR_EXPR;
1966            else
1967                return type == INT_EXPR || type == REAL_EXPR;
1968        }
1969    
1970        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
1971            if (iArg == 1)
1972                return type == VM_NO_UNIT || type == VM_SECOND;
1973            else
1974                return type == VM_NO_UNIT;
1975        }
1976    
1977        bool InstrumentScriptVMFunction_fade_in::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
1978            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
1979        }
1980    
1981        VMFnResult* InstrumentScriptVMFunction_fade_in::exec(VMFnArgs* args) {
1982            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
1983            vmint duration =
1984                (unit) ?
1985                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
1986                    args->arg(1)->asNumber()->evalCastInt();
1987            if (duration < 0) {
1988                wrnMsg("fade_in(): argument 2 may not be negative");
1989                duration = 0;
1990            }
1991            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
1992    
1993            AbstractEngineChannel* pEngineChannel =
1994                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
1995    
1996            if (args->arg(0)->exprType() == INT_EXPR) {
1997                const ScriptID id = args->arg(0)->asInt()->evalInt();
1998                if (!id) {
1999                    wrnMsg("fade_in(): note ID for argument 1 may not be zero");
2000                    return successResult();
2001                }
2002                if (!id.isNoteID()) {
2003                    wrnMsg("fade_in(): argument 1 is not a note ID");
2004                    return successResult();
2005                }
2006    
2007                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2008                if (!pNote) return successResult();
2009    
2010                // if fade_in() was called immediately after note was triggered
2011                // then immediately apply a start volume of zero to Note object,
2012                // as well as the fade in duration
2013                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2014                    pNote->Override.Volume.Value = 0.f;
2015                    pNote->Override.VolumeTime = fDuration;
2016                } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2017                  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"
2018                  e.Init(); // clear IDs                  e.Init(); // clear IDs
2019                  e.Type = Event::type_note_synth_param;                  e.Type = Event::type_note_synth_param;
2020                  e.Param.NoteSynthParam.NoteID   = id.noteID();                  e.Param.NoteSynthParam.NoteID   = id.noteID();
2021                  e.Param.NoteSynthParam.Type     = Event::synth_param_release;                  e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2022                  e.Param.NoteSynthParam.Delta    = fRelease;                  e.Param.NoteSynthParam.Delta    = fDuration;
2023                  e.Param.NoteSynthParam.Relative = false;                  e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2024    
2025                  pEngineChannel->ScheduleEventMicroSec(&e, 0);                  pEngineChannel->ScheduleEventMicroSec(&e, 0);
2026              }              }
2027                // and finally schedule a "volume" change, simply one time slice
2028                // ahead, with the final fade in volume (1.0)
2029                {
2030                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2031                    e.Init(); // clear IDs
2032                    e.Type = Event::type_note_synth_param;
2033                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2034                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2035                    e.Param.NoteSynthParam.Delta    = 1.f;
2036                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2037    
2038                    // scheduling with 0 delay would also work here, but +1 is more
2039                    // safe regarding potential future implementation changes of the
2040                    // scheduler (see API comments of RTAVLTree::insert())
2041                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2042                }
2043            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2044                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2045                for (vmint i = 0; i < ids->arraySize(); ++i) {
2046                    const ScriptID id = ids->evalIntElement(i);
2047                    if (!id || !id.isNoteID()) continue;
2048    
2049                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2050                    if (!pNote) continue;
2051    
2052                    // if fade_in() was called immediately after note was triggered
2053                    // then immediately apply a start volume of zero to Note object,
2054                    // as well as the fade in duration
2055                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2056                        pNote->Override.Volume.Value = 0.f;
2057                        pNote->Override.VolumeTime = fDuration;
2058                    } else { // otherwise schedule a "volume time" change with the requested fade in duration ...
2059                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2060                        e.Init(); // clear IDs
2061                        e.Type = Event::type_note_synth_param;
2062                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2063                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2064                        e.Param.NoteSynthParam.Delta    = fDuration;
2065                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2066    
2067                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2068                    }
2069                    // and finally schedule a "volume" change, simply one time slice
2070                    // ahead, with the final fade in volume (1.0)
2071                    {
2072                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2073                        e.Init(); // clear IDs
2074                        e.Type = Event::type_note_synth_param;
2075                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2076                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2077                        e.Param.NoteSynthParam.Delta    = 1.f;
2078                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2079    
2080                        // scheduling with 0 delay would also work here, but +1 is more
2081                        // safe regarding potential future implementation changes of the
2082                        // scheduler (see API comments of RTAVLTree::insert())
2083                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2084                    }
2085                }
2086          }          }
2087    
2088          return successResult();          return successResult();
2089      }      }
2090    
2091        // fade_out() function
2092    
2093        InstrumentScriptVMFunction_fade_out::InstrumentScriptVMFunction_fade_out(InstrumentScriptVM* parent)
2094            : m_vm(parent)
2095        {
2096        }
2097    
2098        bool InstrumentScriptVMFunction_fade_out::acceptsArgType(vmint iArg, ExprType_t type) const {
2099            if (iArg == 0)
2100                return type == INT_EXPR || type == INT_ARR_EXPR;
2101            else
2102                return type == INT_EXPR || type == REAL_EXPR;
2103        }
2104    
2105        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2106            if (iArg == 1)
2107                return type == VM_NO_UNIT || type == VM_SECOND;
2108            else
2109                return type == VM_NO_UNIT;
2110        }
2111    
2112        bool InstrumentScriptVMFunction_fade_out::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2113            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2114        }
2115    
2116        VMFnResult* InstrumentScriptVMFunction_fade_out::exec(VMFnArgs* args) {
2117            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2118            vmint duration =
2119                (unit) ?
2120                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2121                    args->arg(1)->asNumber()->evalCastInt();
2122            if (duration < 0) {
2123                wrnMsg("fade_out(): argument 2 may not be negative");
2124                duration = 0;
2125            }
2126            const float fDuration = float(duration) / 1000000.f; // convert microseconds to seconds
2127    
2128            bool stop = (args->argsCount() >= 3) ? (args->arg(2)->asInt()->evalInt() & 1) : true;
2129    
2130            AbstractEngineChannel* pEngineChannel =
2131                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2132    
2133            if (args->arg(0)->exprType() == INT_EXPR) {
2134                const ScriptID id = args->arg(0)->asInt()->evalInt();
2135                if (!id) {
2136                    wrnMsg("fade_out(): note ID for argument 1 may not be zero");
2137                    return successResult();
2138                }
2139                if (!id.isNoteID()) {
2140                    wrnMsg("fade_out(): argument 1 is not a note ID");
2141                    return successResult();
2142                }
2143    
2144                NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2145                if (!pNote) return successResult();
2146    
2147                // if fade_out() was called immediately after note was triggered
2148                // then immediately apply fade out duration to Note object
2149                if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2150                    pNote->Override.VolumeTime = fDuration;
2151                } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2152                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2153                    e.Init(); // clear IDs
2154                    e.Type = Event::type_note_synth_param;
2155                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2156                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2157                    e.Param.NoteSynthParam.Delta    = fDuration;
2158                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2159    
2160                    pEngineChannel->ScheduleEventMicroSec(&e, 0);
2161                }
2162                // now schedule a "volume" change, simply one time slice ahead, with
2163                // the final fade out volume (0.0)
2164                {
2165                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2166                    e.Init(); // clear IDs
2167                    e.Type = Event::type_note_synth_param;
2168                    e.Param.NoteSynthParam.NoteID   = id.noteID();
2169                    e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2170                    e.Param.NoteSynthParam.Delta    = 0.f;
2171                    e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2172    
2173                    // scheduling with 0 delay would also work here, but +1 is more
2174                    // safe regarding potential future implementation changes of the
2175                    // scheduler (see API comments of RTAVLTree::insert())
2176                    pEngineChannel->ScheduleEventMicroSec(&e, 1);
2177                }
2178                // and finally if stopping the note was requested after the fade out
2179                // completed, then schedule to kill the voice after the requested
2180                // duration
2181                if (stop) {
2182                    Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2183                    e.Init(); // clear IDs
2184                    e.Type = Event::type_kill_note;
2185                    e.Param.Note.ID = id.noteID();
2186                    e.Param.Note.Key = pNote->hostKey;
2187    
2188                    pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2189                }
2190            } else if (args->arg(0)->exprType() == INT_ARR_EXPR) {
2191                VMIntArrayExpr* ids = args->arg(0)->asIntArray();
2192                for (vmint i = 0; i < ids->arraySize(); ++i) {
2193                    const ScriptID id = ids->evalIntElement(i);
2194                    if (!id || !id.isNoteID()) continue;
2195    
2196                    NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2197                    if (!pNote) continue;
2198    
2199                    // if fade_out() was called immediately after note was triggered
2200                    // then immediately apply fade out duration to Note object
2201                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2202                        pNote->Override.VolumeTime = fDuration;
2203                    } else { // otherwise schedule a "volume time" change with the requested fade out duration ...
2204                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2205                        e.Init(); // clear IDs
2206                        e.Type = Event::type_note_synth_param;
2207                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2208                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume_time;
2209                        e.Param.NoteSynthParam.Delta    = fDuration;
2210                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2211    
2212                        pEngineChannel->ScheduleEventMicroSec(&e, 0);
2213                    }
2214                    // now schedule a "volume" change, simply one time slice ahead, with
2215                    // the final fade out volume (0.0)
2216                    {
2217                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2218                        e.Init(); // clear IDs
2219                        e.Type = Event::type_note_synth_param;
2220                        e.Param.NoteSynthParam.NoteID   = id.noteID();
2221                        e.Param.NoteSynthParam.Type     = Event::synth_param_volume;
2222                        e.Param.NoteSynthParam.Delta    = 0.f;
2223                        e.Param.NoteSynthParam.Scope = Event::ValueScope::RELATIVE; // actually ignored
2224    
2225                        // scheduling with 0 delay would also work here, but +1 is more
2226                        // safe regarding potential future implementation changes of the
2227                        // scheduler (see API comments of RTAVLTree::insert())
2228                        pEngineChannel->ScheduleEventMicroSec(&e, 1);
2229                    }
2230                    // and finally if stopping the note was requested after the fade out
2231                    // completed, then schedule to kill the voice after the requested
2232                    // duration
2233                    if (stop) {
2234                        Event e = m_vm->m_event->cause; // copy to get fragment time for "now"
2235                        e.Init(); // clear IDs
2236                        e.Type = Event::type_kill_note;
2237                        e.Param.Note.ID = id.noteID();
2238                        e.Param.Note.Key = pNote->hostKey;
2239                        
2240                        pEngineChannel->ScheduleEventMicroSec(&e, duration + 1);
2241                    }
2242                }
2243            }
2244    
2245            return successResult();
2246        }
2247    
2248        // get_event_par() function
2249    
2250        InstrumentScriptVMFunction_get_event_par::InstrumentScriptVMFunction_get_event_par(InstrumentScriptVM* parent)
2251            : m_vm(parent)
2252        {
2253        }
2254    
2255        VMFnResult* InstrumentScriptVMFunction_get_event_par::exec(VMFnArgs* args) {
2256            AbstractEngineChannel* pEngineChannel =
2257                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2258    
2259            const ScriptID id = args->arg(0)->asInt()->evalInt();
2260            if (!id) {
2261                wrnMsg("get_event_par(): note ID for argument 1 may not be zero");
2262                return successResult(0);
2263            }
2264            if (!id.isNoteID()) {
2265                wrnMsg("get_event_par(): argument 1 is not a note ID");
2266                return successResult(0);
2267            }
2268    
2269            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2270            if (!pNote) {
2271                wrnMsg("get_event_par(): no note alive with that note ID of argument 1");
2272                return successResult(0);
2273            }
2274    
2275            const vmint parameter = args->arg(1)->asInt()->evalInt();
2276            switch (parameter) {
2277                case EVENT_PAR_NOTE:
2278                    return successResult(pNote->cause.Param.Note.Key);
2279                case EVENT_PAR_VELOCITY:
2280                    return successResult(pNote->cause.Param.Note.Velocity);
2281                case EVENT_PAR_VOLUME:
2282                    return successResult(
2283                        RTMath::LinRatioToDecibel(pNote->Override.Volume.Value) * 1000.f
2284                    );
2285                case EVENT_PAR_TUNE:
2286                    return successResult(
2287                         RTMath::FreqRatioToCents(pNote->Override.Pitch.Value) * 1000.f
2288                    );
2289                case EVENT_PAR_0:
2290                    return successResult(pNote->userPar[0]);
2291                case EVENT_PAR_1:
2292                    return successResult(pNote->userPar[1]);
2293                case EVENT_PAR_2:
2294                    return successResult(pNote->userPar[2]);
2295                case EVENT_PAR_3:
2296                    return successResult(pNote->userPar[3]);
2297            }
2298    
2299            wrnMsg("get_event_par(): argument 2 is an invalid event parameter");
2300            return successResult(0);
2301        }
2302    
2303        // set_event_par() function
2304    
2305        InstrumentScriptVMFunction_set_event_par::InstrumentScriptVMFunction_set_event_par(InstrumentScriptVM* parent)
2306            : m_vm(parent)
2307        {
2308        }
2309    
2310        VMFnResult* InstrumentScriptVMFunction_set_event_par::exec(VMFnArgs* args) {
2311            AbstractEngineChannel* pEngineChannel =
2312                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2313    
2314            const ScriptID id = args->arg(0)->asInt()->evalInt();
2315            if (!id) {
2316                wrnMsg("set_event_par(): note ID for argument 1 may not be zero");
2317                return successResult();
2318            }
2319            if (!id.isNoteID()) {
2320                wrnMsg("set_event_par(): argument 1 is not a note ID");
2321                return successResult();
2322            }
2323    
2324            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2325            if (!pNote) return successResult();
2326    
2327            const vmint parameter = args->arg(1)->asInt()->evalInt();
2328            const vmint value     = args->arg(2)->asInt()->evalInt();
2329    
2330            switch (parameter) {
2331                case EVENT_PAR_NOTE:
2332                    if (value < 0 || value > 127) {
2333                        wrnMsg("set_event_par(): note number of argument 3 is out of range");
2334                        return successResult();
2335                    }
2336                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2337                        pNote->cause.Param.Note.Key = value;
2338                        m_vm->m_event->cause.Param.Note.Key = value;
2339                    } else {
2340                        wrnMsg("set_event_par(): note number can only be changed when note is new");
2341                    }
2342                    return successResult();
2343                case EVENT_PAR_VELOCITY:
2344                    if (value < 0 || value > 127) {
2345                        wrnMsg("set_event_par(): velocity of argument 3 is out of range");
2346                        return successResult();
2347                    }
2348                    if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2349                        pNote->cause.Param.Note.Velocity = value;
2350                        m_vm->m_event->cause.Param.Note.Velocity = value;
2351                    } else {
2352                        wrnMsg("set_event_par(): velocity can only be changed when note is new");
2353                    }
2354                    return successResult();
2355                case EVENT_PAR_VOLUME:
2356                    wrnMsg("set_event_par(): changing volume by this function is currently not supported, use change_vol() instead");
2357                    return successResult();
2358                case EVENT_PAR_TUNE:
2359                    wrnMsg("set_event_par(): changing tune by this function is currently not supported, use change_tune() instead");
2360                    return successResult();
2361                case EVENT_PAR_0:
2362                    pNote->userPar[0] = value;
2363                    return successResult();
2364                case EVENT_PAR_1:
2365                    pNote->userPar[1] = value;
2366                    return successResult();
2367                case EVENT_PAR_2:
2368                    pNote->userPar[2] = value;
2369                    return successResult();
2370                case EVENT_PAR_3:
2371                    pNote->userPar[3] = value;
2372                    return successResult();
2373            }
2374    
2375            wrnMsg("set_event_par(): argument 2 is an invalid event parameter");
2376            return successResult();
2377        }
2378    
2379        // change_note() function
2380    
2381        InstrumentScriptVMFunction_change_note::InstrumentScriptVMFunction_change_note(InstrumentScriptVM* parent)
2382        : m_vm(parent)
2383        {
2384        }
2385    
2386        VMFnResult* InstrumentScriptVMFunction_change_note::exec(VMFnArgs* args) {
2387            AbstractEngineChannel* pEngineChannel =
2388                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2389    
2390            const ScriptID id = args->arg(0)->asInt()->evalInt();
2391            if (!id) {
2392                wrnMsg("change_note(): note ID for argument 1 may not be zero");
2393                return successResult();
2394            }
2395            if (!id.isNoteID()) {
2396                wrnMsg("change_note(): argument 1 is not a note ID");
2397                return successResult();
2398            }
2399    
2400            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2401            if (!pNote) return successResult();
2402    
2403            const vmint value = args->arg(1)->asInt()->evalInt();
2404            if (value < 0 || value > 127) {
2405                wrnMsg("change_note(): note number of argument 2 is out of range");
2406                return successResult();
2407            }
2408    
2409            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2410                pNote->cause.Param.Note.Key = value;
2411                m_vm->m_event->cause.Param.Note.Key = value;
2412            } else {
2413                wrnMsg("change_note(): note number can only be changed when note is new");
2414            }
2415    
2416            return successResult();
2417        }
2418    
2419        // change_velo() function
2420    
2421        InstrumentScriptVMFunction_change_velo::InstrumentScriptVMFunction_change_velo(InstrumentScriptVM* parent)
2422        : m_vm(parent)
2423        {
2424        }
2425    
2426        VMFnResult* InstrumentScriptVMFunction_change_velo::exec(VMFnArgs* args) {
2427            AbstractEngineChannel* pEngineChannel =
2428                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2429    
2430            const ScriptID id = args->arg(0)->asInt()->evalInt();
2431            if (!id) {
2432                wrnMsg("change_velo(): note ID for argument 1 may not be zero");
2433                return successResult();
2434            }
2435            if (!id.isNoteID()) {
2436                wrnMsg("change_velo(): argument 1 is not a note ID");
2437                return successResult();
2438            }
2439    
2440            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2441            if (!pNote) return successResult();
2442    
2443            const vmint value = args->arg(1)->asInt()->evalInt();
2444            if (value < 0 || value > 127) {
2445                wrnMsg("change_velo(): velocity of argument 2 is out of range");
2446                return successResult();
2447            }
2448    
2449            if (m_vm->m_event->scheduleTime == pNote->triggerSchedTime) {
2450                pNote->cause.Param.Note.Velocity = value;
2451                m_vm->m_event->cause.Param.Note.Velocity = value;
2452            } else {
2453                wrnMsg("change_velo(): velocity can only be changed when note is new");
2454            }
2455    
2456            return successResult();
2457        }
2458    
2459        // change_play_pos() function
2460    
2461        InstrumentScriptVMFunction_change_play_pos::InstrumentScriptVMFunction_change_play_pos(InstrumentScriptVM* parent)
2462            : m_vm(parent)
2463        {
2464        }
2465    
2466        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgType(vmint iArg, ExprType_t type) const {
2467            if (iArg == 0)
2468                return type == INT_EXPR;
2469            else
2470                return type == INT_EXPR || type == REAL_EXPR;
2471        }
2472    
2473        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitType(vmint iArg, StdUnit_t type) const {
2474            if (iArg == 1)
2475                return type == VM_NO_UNIT || type == VM_SECOND;
2476            else
2477                return type == VM_NO_UNIT;
2478        }
2479    
2480        bool InstrumentScriptVMFunction_change_play_pos::acceptsArgUnitPrefix(vmint iArg, StdUnit_t type) const {
2481            return iArg == 1 && type == VM_SECOND; // only allow metric prefix(es) if 'seconds' is used as unit type
2482        }
2483    
2484        VMFnResult* InstrumentScriptVMFunction_change_play_pos::exec(VMFnArgs* args) {
2485            const ScriptID id = args->arg(0)->asInt()->evalInt();
2486            if (!id) {
2487                wrnMsg("change_play_pos(): note ID for argument 1 may not be zero");
2488                return successResult();
2489            }
2490            if (!id.isNoteID()) {
2491                wrnMsg("change_play_pos(): argument 1 is not a note ID");
2492                return successResult();
2493            }
2494    
2495            StdUnit_t unit = args->arg(1)->asNumber()->unitType();
2496            const vmint pos =
2497                (unit) ?
2498                    args->arg(1)->asNumber()->evalCastInt(VM_MICRO) :
2499                    args->arg(1)->asNumber()->evalCastInt();
2500            if (pos < 0) {
2501                wrnMsg("change_play_pos(): playback position of argument 2 may not be negative");
2502                return successResult();
2503            }
2504    
2505            AbstractEngineChannel* pEngineChannel =
2506                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2507    
2508            NoteBase* pNote = pEngineChannel->pEngine->NoteByID( id.noteID() );
2509            if (!pNote) return successResult();
2510    
2511            pNote->Override.SampleOffset =
2512                (decltype(pNote->Override.SampleOffset)) pos;
2513    
2514            return successResult();
2515        }
2516    
2517      // event_status() function      // event_status() function
2518    
2519      InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_event_status::InstrumentScriptVMFunction_event_status(InstrumentScriptVM* parent)
# Line 1015  namespace LinuxSampler { Line 2539  namespace LinuxSampler {
2539          return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);          return successResult(pNote ? EVENT_STATUS_NOTE_QUEUE : EVENT_STATUS_INACTIVE);
2540      }      }
2541    
2542        // callback_status() function
2543    
2544        InstrumentScriptVMFunction_callback_status::InstrumentScriptVMFunction_callback_status(InstrumentScriptVM* parent)
2545            : m_vm(parent)
2546        {
2547        }
2548    
2549        VMFnResult* InstrumentScriptVMFunction_callback_status::exec(VMFnArgs* args) {
2550            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2551            if (!id) {
2552                wrnMsg("callback_status(): callback ID for argument 1 may not be zero");
2553                return successResult();
2554            }
2555    
2556            AbstractEngineChannel* pEngineChannel =
2557                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2558    
2559            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2560            if (!itCallback)
2561                return successResult(CALLBACK_STATUS_TERMINATED);
2562    
2563            return successResult(
2564                (m_vm->m_event->execCtx == itCallback->execCtx) ?
2565                    CALLBACK_STATUS_RUNNING : CALLBACK_STATUS_QUEUE
2566            );
2567        }
2568    
2569      // wait() function (overrides core wait() implementation)      // wait() function (overrides core wait() implementation)
2570    
2571      InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)      InstrumentScriptVMFunction_wait::InstrumentScriptVMFunction_wait(InstrumentScriptVM* parent)
# Line 1055  namespace LinuxSampler { Line 2606  namespace LinuxSampler {
2606              (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;              (args->argsCount() >= 2) ? (args->arg(1)->asInt()->evalInt() == 1) : false;
2607    
2608          pEngineChannel->ScheduleResumeOfScriptCallback(          pEngineChannel->ScheduleResumeOfScriptCallback(
2609              itCallback, m_vm->m_event->cause.SchedTime(), disableWaitForever              itCallback, m_vm->m_event->scheduleTime, disableWaitForever
2610          );          );
2611    
2612          return successResult();          return successResult();
2613      }      }
2614    
2615        // abort() function
2616    
2617        InstrumentScriptVMFunction_abort::InstrumentScriptVMFunction_abort(InstrumentScriptVM* parent)
2618            : m_vm(parent)
2619        {
2620        }
2621    
2622        VMFnResult* InstrumentScriptVMFunction_abort::exec(VMFnArgs* args) {
2623            const script_callback_id_t id = args->arg(0)->asInt()->evalInt();
2624            if (!id) {
2625                wrnMsg("abort(): callback ID for argument 1 may not be zero");
2626                return successResult();
2627            }
2628    
2629            AbstractEngineChannel* pEngineChannel =
2630                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2631    
2632            RTList<ScriptEvent>::Iterator itCallback = pEngineChannel->ScriptCallbackByID(id);
2633            if (!itCallback) return successResult(); // ignore if callback is i.e. not alive anymore
2634    
2635            itCallback->execCtx->signalAbort();
2636    
2637            return successResult();
2638        }
2639    
2640        // fork() function
2641    
2642        InstrumentScriptVMFunction_fork::InstrumentScriptVMFunction_fork(InstrumentScriptVM* parent)
2643            : m_vm(parent)
2644        {
2645        }
2646    
2647        VMFnResult* InstrumentScriptVMFunction_fork::exec(VMFnArgs* args) {
2648            // check if this is actually the parent going to fork, or rather one of
2649            // the children which is already forked
2650            if (m_vm->m_event->forkIndex != 0) { // this is the entry point for a child ...
2651                int forkResult = m_vm->m_event->forkIndex;
2652                // reset so that this child may i.e. also call fork() later on
2653                m_vm->m_event->forkIndex = 0;
2654                return successResult(forkResult);
2655            }
2656    
2657            // if we are here, then this is the parent, so we must fork this parent
2658    
2659            const vmint n =
2660                (args->argsCount() >= 1) ? args->arg(0)->asInt()->evalInt() : 1;
2661            const bool bAutoAbort =
2662                (args->argsCount() >= 2) ? args->arg(1)->asInt()->evalInt() : true;
2663    
2664            if (m_vm->m_event->countChildHandlers() + n > MAX_FORK_PER_SCRIPT_HANDLER) {
2665                wrnMsg("fork(): requested amount would exceed allowed limit per event handler");
2666                return successResult(-1);
2667            }
2668    
2669            AbstractEngineChannel* pEngineChannel =
2670                static_cast<AbstractEngineChannel*>(m_vm->m_event->cause.pEngineChannel);
2671    
2672            if (!pEngineChannel->hasFreeScriptCallbacks(n)) {
2673                wrnMsg("fork(): global limit of event handlers exceeded");
2674                return successResult(-1);
2675            }
2676    
2677            for (int iChild = 0; iChild < n; ++iChild) {
2678                RTList<ScriptEvent>::Iterator itChild =
2679                    pEngineChannel->forkScriptCallback(m_vm->m_event, bAutoAbort);
2680                if (!itChild) { // should never happen, otherwise its a bug ...
2681                    errMsg("fork(): internal error while allocating child");
2682                    return errorResult(-1); // terminate script
2683                }
2684                // since both parent, as well all child script execution instances
2685                // all land in this exec() method, the following is (more or less)
2686                // the only feature that lets us distinguish the parent and
2687                // respective children from each other in this exec() method
2688                itChild->forkIndex = iChild + 1;
2689            }
2690    
2691            return successResult(0);
2692        }
2693    
2694  } // namespace LinuxSampler  } // namespace LinuxSampler

Legend:
Removed from v.2953  
changed lines
  Added in v.3588

  ViewVC Help
Powered by ViewVC