/[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 3747 - (hide annotations) (download)
Sun Feb 16 11:31:46 2020 UTC (4 years, 2 months ago) by schoenebeck
File size: 122339 byte(s)
* NKSP: Fixed re-entrant issue with function calls which caused wrong
  result values if the same function was called multiple times in a term
  (specifically if metric prefixes were used).

* Tests: Added NKSP core language test cases to guard this fixed issue.

* Bumped version (2.1.1.svn50).

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

  ViewVC Help
Powered by ViewVC