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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3581 - (hide annotations) (download)
Fri Aug 30 11:40:25 2019 UTC (4 years, 7 months ago) by schoenebeck
File size: 107929 byte(s)
NKSP: Allow more wider support of standard measuring units & 'final'ness.

* Raised compiler requirement to be C++14 compliant (due to severe
  restrictions regarding C-style aggregate initializer lists in C++11
  which are now massively used throughout the code base).

* NKSP VM API: Allow units and 'final'ness to be returned as result from
  built-in functions (added methods VMFunction::returnUnitType() and
  VMFunction::returnsFinal() for that purpose which must be implemented by
  built-in function implementations).

* NKSP language: Allow metric unit prefixes of numeric scalar and array
  variables to be changed freely at runtime (unlike unit types like Hz etc.
  which are still sticky parse-time features of variables which cannot be
  changed at runtime for the intentional sake of determinism).

* NKSP language: 'final' values are prohibited for array variables for now
  (attempt causes a parsers error).

* NKSP language: expressions with unit types (e.g. Hz) are prohibited for
  conditions of runtime control structures like if(), while(), select()
  (attempt causes a parser error).

* NKSP VM API: Allow built-in functions to perform their own, individual
  parse time checks of arguments going to be passed to the function at
  runtime (added method VMFunction::checkArgs() for that purpose).

* NKSP language: raise parser warning if only one operand of binary
  operators (like logical 'or' comparison) contain a 'final' value (because
  it would always yield in a 'final' result in such cases).

* NKSP language: Allow comparison (=, #, <, >, <=, >=) of values with
  different metric unit prefixes, which will behave as expected (e.g.
  result of expression '1000us < 2ms' is true).

* NKSP language: Allow adding values with different metric unit prefixes
  (e.g. result of expression '100Hz + 5kHz' is '5100Hz').

* NKSP language: Allow subtracting values with different metric unit
  prefixes (e.g. result of expression '1ms - 20us' is '980us').

* NKSP language: Allow multiplying with any metric unit prefixes
  (e.g. result of expression '2k * 3ms' is '6s'), however multiplications
  with unit types on both sides (e.g. '2s * 2s') is still prohibited since
  we don't have any considerable practical use for a term like '4s^2'
  (hence any attempt multiplying two unit types still causes parser error).

* NKSP language: Allow dividing by any metric unit prefixes and allow
  division of same unit type on both sides (e.g. expression '8kHz / 1000Hz'
  yields in unit free result '8'). So this is now a way to cast units away
  e.g. for passing the result to other expressions, certain function calls
  or variables which are not accepting any units (or that specific unit).

* NKSP language: integer arrays and real number arrays can now be converted
  to strings (e.g. for dumping their content with message() calls for
  script debugging purposes).

* NKSP language: expressions and variables with units are now correctly
  casted to strings (e.g. with message() calls).

* NKSP language: comparing real numbers for equalness (e.g. '~foo = 3.1') or
  unequalness (e.g. '~foo # 3.1') is now less strict and takes the expected
  floating point tolerances into account.

* NKSP VM API: Added methods VMScalarNumberExpr::evalCastInt() and
  VMScalarNumberExpr::evalCastReal().

* NKSP VM API: Added base class 'VMNumberArrayExpr' for classes
  'VMIntArrayExpr' and 'VMRealArrayExpr'.

* NKSP VM API: replaced all unitPrefix() (parse time) methods by
  unitFactor() (runtime) methods.

* Built-in function "exit()" supports now returning units and 'final'ness
  exclusively for test cases.

* The following built-in functions support now units as well: "abs()",
  "random()", "inc()", "dec()", "in_range()", "min()", "max()",
  "real_to_int()", "int()", "int_to_real()" and "real()".

* Built-in functions "array_equal()", "search()" and "sort()" support now
  real number arrays (correctly) as well.

* Added individual parse time checks of arguments to be passed to built-in
  functions "random()", "inc()", "dec()", "in_range()", "min()", "max()",
  "array_equal()" and "search()" specific for their individual purposes.

* Test cases: Added massive amount of NKSP test cases for standard
  measuring units and 'final' operator usage cases.

* Test cases: Added NKSP test cases for (floating point tolerance aware)
  real number equalness / unequalness comparison.

* Bumped version (2.1.1.svn8).

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

  ViewVC Help
Powered by ViewVC