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

Contents of /linuxsampler/trunk/src/engines/common/InstrumentScriptVM.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3971 - (show annotations) (download)
Fri Jun 25 09:26:00 2021 UTC (2 years, 9 months ago) by schoenebeck
File size: 22139 byte(s)
* Fix: Do not share compiled instrument scripts among multiple engine
  channels (sampler parts) as this can lead to crashes and would allow the
  same global script variables to be accessible from multiple engine
  channels which would not be the expected behaviour by instrument script
  authors.

* Bumped version (2.2.0.svn5).

1 /*
2 * Copyright (c) 2014 - 2021 Christian Schoenebeck
3 *
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 "../../common/global_private.h"
11 #include "InstrumentScriptVM.h"
12 #include "../AbstractEngineChannel.h"
13 #include "../../common/global_private.h"
14 #include "AbstractInstrumentManager.h"
15 #include "MidiKeyboardManager.h"
16 #include "Fade.h"
17
18 namespace LinuxSampler {
19
20 ///////////////////////////////////////////////////////////////////////
21 // class 'EventGroup'
22
23 void EventGroup::insert(vmint eventID) {
24 if (contains(eventID)) return;
25
26 AbstractEngine* pEngine = m_script->pEngineChannel->pEngine;
27
28 // before adding the new event ID, check if there are any dead events
29 // and remove them in that case, before otherwise we might run in danger
30 // to run out of free space on this group for event IDs if a lot of
31 // events die before being removed explicitly from the group by script
32 //
33 // NOTE: or should we do this "dead ones" check only once in a while?
34 ssize_t firstDead = -1;
35 for (size_t i = 0; i < size(); ++i) {
36 if (firstDead >= 0) {
37 if (pEngine->EventByID(eventID)) {
38 remove(firstDead, i - firstDead);
39 firstDead = -1;
40 }
41 } else {
42 if (!pEngine->EventByID(eventID)) firstDead = i;
43 }
44 }
45 if (firstDead >= 0)
46 remove(firstDead, size() - firstDead);
47
48 append(eventID);
49 }
50
51 void EventGroup::erase(vmint eventID) {
52 size_t index = find(eventID);
53 remove(index);
54 }
55
56 ///////////////////////////////////////////////////////////////////////
57 // class 'InstrumentScript'
58
59 InstrumentScript::InstrumentScript(AbstractEngineChannel* pEngineChannel) {
60 parserContext = NULL;
61 bHasValidScript = false;
62 handlerInit = NULL;
63 handlerNote = NULL;
64 handlerRelease = NULL;
65 handlerController = NULL;
66 handlerRpn = NULL;
67 handlerNrpn = NULL;
68 pEvents = NULL;
69 for (int i = 0; i < 128; ++i)
70 pKeyEvents[i] = NULL;
71 this->pEngineChannel = pEngineChannel;
72 for (int i = 0; i < INSTR_SCRIPT_EVENT_GROUPS; ++i)
73 eventGroups[i].setScript(this);
74 }
75
76 InstrumentScript::~InstrumentScript() {
77 resetAll();
78 if (pEvents) {
79 for (int i = 0; i < 128; ++i) delete pKeyEvents[i];
80 delete pEvents;
81 }
82 }
83
84 /** @brief Load real-time instrument script.
85 *
86 * Loads the real-time instrument script given by @a text on the engine
87 * channel this InstrumentScript object belongs to (defined by
88 * pEngineChannel member variable). The sampler engine's resource manager is
89 * used to allocate and share equivalent scripts on multiple engine
90 * channels.
91 *
92 * @param text - source code of script
93 * @param patchVars - 'patch' variables being overridden by instrument
94 */
95 void InstrumentScript::load(const String& text,
96 const std::map<String,String>& patchVars)
97 {
98 dmsg(1,("Loading real-time instrument script ... "));
99
100 // hand back old script reference and VM execution contexts
101 // (if not done already)
102 unload();
103
104 code = text;
105
106 AbstractInstrumentManager* pManager =
107 dynamic_cast<AbstractInstrumentManager*>(pEngineChannel->pEngine->GetInstrumentManager());
108
109 /*
110 Get new script reference.
111
112 Note: every engine channel now has its own compiled script object
113 (a.k.a. VMParserContext). Originally a compiled script was shared by
114 multiple engine channels. This was wrong: we cannot share compiled
115 script instances among multiple engine channels (parts), for two
116 reasons:
117
118 1. VMParserContext not only encompasses the compiled tree
119 presentation of the requested script, but also global variables
120 and we don't want those global variables to be modified by
121 different sampler parts, as this would not be expected behaviour
122 by instrument script authors.
123
124 2. If there is more than one sampler engine instance (e.g. if there
125 are multiple audio output device instances) this would even crash,
126 because each sampler engine instance has its own ScriptVM
127 instance, and a (VM)ParserContext is always tied to exactly one
128 ScriptVM instance.
129
130 We would not be buying much by sharing compiled scripts anyway, as a
131 script usually compiles in couple microseconds and RAM usage is also
132 neglectable.
133 */
134 parserContext = pManager->scripts.Borrow({
135 .code = text,
136 .patchVars = patchVars,
137 .engineChannel = pEngineChannel /* unique owner of script */
138 },
139 pEngineChannel /* who is asking to borrow */
140 );
141 if (!parserContext->errors().empty()) {
142 std::vector<ParserIssue> errors = parserContext->errors();
143 std::cerr << "[ScriptVM] Could not load instrument script, there were "
144 << errors.size() << " parser errors:\n";
145 for (int i = 0; i < errors.size(); ++i)
146 errors[i].dump();
147 return; // stop here if there were any parser errors
148 }
149
150 handlerInit = parserContext->eventHandlerByName("init");
151 handlerNote = parserContext->eventHandlerByName("note");
152 handlerRelease = parserContext->eventHandlerByName("release");
153 handlerController = parserContext->eventHandlerByName("controller");
154 handlerRpn = parserContext->eventHandlerByName("rpn");
155 handlerNrpn = parserContext->eventHandlerByName("nrpn");
156 bHasValidScript =
157 handlerInit || handlerNote || handlerRelease || handlerController ||
158 handlerRpn || handlerNrpn;
159
160 // amount of script handlers each script event has to execute
161 int handlerExecCount = 0;
162 if (handlerNote || handlerRelease || handlerController || handlerRpn ||
163 handlerNrpn) // only one of these are executed after "init" handler
164 handlerExecCount++;
165
166 // create script event pool (if it doesn't exist already)
167 if (!pEvents) {
168 pEvents = new Pool<ScriptEvent>(CONFIG_MAX_EVENTS_PER_FRAGMENT);
169 for (int i = 0; i < 128; ++i)
170 pKeyEvents[i] = new RTList<ScriptEvent>(pEvents);
171 // reset RTAVLNode's tree node member variables after nodes are allocated
172 // (since we can't use a constructor right now, we do that initialization here)
173 while (!pEvents->poolIsEmpty()) {
174 RTList<ScriptEvent>::Iterator it = pEvents->allocAppend();
175 it->reset();
176 }
177 }
178 pEvents->clear(); // outside of upper block, as loop below must always start from cleared list
179
180 // create new VM execution contexts for new script
181 while (!pEvents->poolIsEmpty()) {
182 RTList<ScriptEvent>::Iterator it = pEvents->allocAppend();
183 it->execCtx = pEngineChannel->pEngine->pScriptVM->createExecContext(
184 parserContext
185 );
186 it->handlers = new VMEventHandler*[handlerExecCount+1];
187 }
188 pEvents->clear();
189
190 dmsg(1,("Done\n"));
191 }
192
193 /** @brief Unload real-time instrument script.
194 *
195 * Unloads the currently used real-time instrument script and frees all
196 * resources allocated for that script. The sampler engine's resource manager
197 * is used to share equivalent scripts among multiple sampler channels, and
198 * to deallocate the parsed script once not used on any engine channel
199 * anymore.
200 *
201 * Calling this method will however not clear the @c code member variable.
202 * Thus, the script can be parsed again afterwards.
203 */
204 void InstrumentScript::unload() {
205 //dmsg(1,("InstrumentScript::unload(this=0x%llx)\n", this));
206
207 if (parserContext)
208 dmsg(1,("Unloading current instrument script.\n"));
209
210 resetEvents();
211
212 // free allocated VM execution contexts
213 if (pEvents) {
214 pEvents->clear();
215 while (!pEvents->poolIsEmpty()) {
216 RTList<ScriptEvent>::Iterator it = pEvents->allocAppend();
217 if (!it) break;
218 if (it->execCtx) {
219 // free VM execution context object
220 delete it->execCtx;
221 it->execCtx = NULL;
222 // free C array of handler pointers
223 delete [] it->handlers;
224 it->handlers = NULL;
225 }
226 }
227 pEvents->clear();
228 }
229 // hand back VM representation of script
230 if (parserContext) {
231 AbstractInstrumentManager* pManager =
232 dynamic_cast<AbstractInstrumentManager*>(pEngineChannel->pEngine->GetInstrumentManager());
233
234 pManager->scripts.HandBack(parserContext, pEngineChannel);
235 parserContext = NULL;
236 handlerInit = NULL;
237 handlerNote = NULL;
238 handlerRelease = NULL;
239 handlerController = NULL;
240 handlerRpn = NULL;
241 handlerNrpn = NULL;
242 }
243 bHasValidScript = false;
244 }
245
246 /**
247 * Same as unload(), but this one also empties the @c code member variable
248 * to an empty string.
249 */
250 void InstrumentScript::resetAll() {
251 unload();
252 code.clear();
253 }
254
255 /**
256 * Clears all currently active script events. This should be called
257 * whenever the engine or engine channel was reset for some reason.
258 */
259 void InstrumentScript::resetEvents() {
260 for (int i = 0; i < INSTR_SCRIPT_EVENT_GROUPS; ++i)
261 eventGroups[i].clear();
262
263 for (int i = 0; i < 128; ++i)
264 if (pKeyEvents[i])
265 pKeyEvents[i]->clear();
266
267 suspendedEvents.clear();
268
269 if (pEvents) pEvents->clear();
270 }
271
272 ///////////////////////////////////////////////////////////////////////
273 // class 'InstrumentScriptVM'
274
275 InstrumentScriptVM::InstrumentScriptVM() :
276 m_event(NULL), m_fnPlayNote(this), m_fnSetController(this),
277 m_fnSetRpn(this), m_fnSetNrpn(this),
278 m_fnIgnoreEvent(this), m_fnIgnoreController(this), m_fnNoteOff(this),
279 m_fnSetEventMark(this), m_fnDeleteEventMark(this), m_fnByMarks(this),
280 m_fnChangeVol(this), m_fnChangeVolTime(this),
281 m_fnChangeTune(this), m_fnChangeTuneTime(this), m_fnChangePan(this),
282 m_fnChangePanTime(this), m_fnChangePanCurve(this),
283 m_fnChangeCutoff(this), m_fnChangeReso(this), m_fnChangeAttack(this),
284 m_fnChangeDecay(this), m_fnChangeSustain(this), m_fnChangeRelease(this),
285 m_fnChangeCutoffAttack(this), m_fnChangeCutoffDecay(this),
286 m_fnChangeCutoffSustain(this), m_fnChangeCutoffRelease(this),
287 m_fnChangeAmpLFODepth(this), m_fnChangeAmpLFOFreq(this),
288 m_fnChangeCutoffLFODepth(this), m_fnChangeCutoffLFOFreq(this),
289 m_fnChangePitchLFODepth(this), m_fnChangePitchLFOFreq(this),
290 m_fnChangeNote(this), m_fnChangeVelo(this), m_fnFork(this),
291 m_fnEventStatus(this), m_fnWait2(this), m_fnStopWait(this),
292 m_fnAbort(this), m_fnFadeIn(this), m_fnFadeOut(this),
293 m_fnChangeVolCurve(this), m_fnChangeTuneCurve(this),
294 m_fnGetEventPar(this), m_fnSetEventPar(this), m_fnChangePlayPos(this),
295 m_fnCallbackStatus(this),
296 m_varEngineUptime(this), m_varCallbackID(this), m_varAllEvents(this),
297 m_varCallbackChildID(this)
298 {
299 m_CC.size = _MEMBER_SIZEOF(AbstractEngineChannel, ControllerTable);
300 m_CC_NUM = DECLARE_VMINT(m_event, class ScriptEvent, cause.Param.CC.Controller);
301 m_EVENT_ID = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, id);
302 m_EVENT_NOTE = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, cause.Param.Note.Key);
303 m_EVENT_VELOCITY = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, cause.Param.Note.Velocity);
304 m_RPN_ADDRESS = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, cause.Param.RPN.Parameter);
305 m_RPN_VALUE = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, cause.Param.RPN.Value);
306 m_KEY_DOWN.size = 128;
307 m_KEY_DOWN.readonly = true;
308 m_NI_CALLBACK_TYPE = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, handlerType);
309 m_NKSP_IGNORE_WAIT = DECLARE_VMINT(m_event, class ScriptEvent, ignoreAllWaitCalls);
310 m_NKSP_CALLBACK_PARENT_ID = DECLARE_VMINT_READONLY(m_event, class ScriptEvent, parentHandlerID);
311 }
312
313 VMExecStatus_t InstrumentScriptVM::exec(VMParserContext* parserCtx, ScriptEvent* event) {
314 AbstractEngineChannel* pEngineChannel =
315 static_cast<AbstractEngineChannel*>(event->cause.pEngineChannel);
316
317 // prepare built-in script variables for script execution
318 m_event = event;
319 m_CC.data = (int8_t*) &pEngineChannel->ControllerTable[0];
320 m_KEY_DOWN.data = &pEngineChannel->GetMidiKeyboardManager()->KeyDown[0];
321
322 // if script is in start condition, then do mandatory MIDI event
323 // preprocessing tasks, which essentially means updating i.e. controller
324 // table with new CC value in case of a controller event, because the
325 // script might access the new CC value
326 if (!event->executionSlices) {
327 switch (event->cause.Type) {
328 case Event::type_control_change:
329 pEngineChannel->ControllerTable[event->cause.Param.CC.Controller] =
330 event->cause.Param.CC.Value;
331 break;
332 case Event::type_channel_pressure:
333 pEngineChannel->ControllerTable[CTRL_TABLE_IDX_AFTERTOUCH] =
334 event->cause.Param.ChannelPressure.Value;
335 break;
336 case Event::type_pitchbend:
337 pEngineChannel->ControllerTable[CTRL_TABLE_IDX_PITCHBEND] =
338 event->cause.Param.Pitch.Pitch;
339 break;
340 default:
341 ; // noop
342 }
343 }
344
345 // run the script handler(s)
346 VMExecStatus_t res = VM_EXEC_NOT_RUNNING;
347 for ( ; event->handlers[event->currentHandler]; event->currentHandler++) {
348 res = ScriptVM::exec(
349 parserCtx, event->execCtx, event->handlers[event->currentHandler]
350 );
351 event->executionSlices++;
352 if (!(res & VM_EXEC_SUSPENDED)) { // if script terminated ...
353 // check if this script handler instance has any forked children
354 // to be auto aborted
355 for (int iChild = 0; iChild < MAX_FORK_PER_SCRIPT_HANDLER &&
356 event->childHandlerID[iChild]; ++iChild)
357 {
358 RTList<ScriptEvent>::Iterator itChild =
359 pEngineChannel->ScriptCallbackByID(event->childHandlerID[iChild]);
360 if (itChild && itChild->autoAbortByParent)
361 itChild->execCtx->signalAbort();
362 }
363 }
364 if (res & VM_EXEC_SUSPENDED || res & VM_EXEC_ERROR) return res;
365 }
366
367 return res;
368 }
369
370 std::map<String,VMIntPtr*> InstrumentScriptVM::builtInIntVariables() {
371 // first get built-in integer variables of derived VM class
372 std::map<String,VMIntPtr*> m = ScriptVM::builtInIntVariables();
373
374 // now add own built-in variables
375 m["$CC_NUM"] = &m_CC_NUM;
376 m["$EVENT_ID"] = &m_EVENT_ID;
377 m["$EVENT_NOTE"] = &m_EVENT_NOTE;
378 m["$EVENT_VELOCITY"] = &m_EVENT_VELOCITY;
379 // m["$POLY_AT_NUM"] = &m_POLY_AT_NUM;
380 m["$RPN_ADDRESS"] = &m_RPN_ADDRESS; // used for both RPN and NRPN events
381 m["$RPN_VALUE"] = &m_RPN_VALUE; // used for both RPN and NRPN events
382 m["$NI_CALLBACK_TYPE"] = &m_NI_CALLBACK_TYPE;
383 m["$NKSP_IGNORE_WAIT"] = &m_NKSP_IGNORE_WAIT;
384 m["$NKSP_CALLBACK_PARENT_ID"] = &m_NKSP_CALLBACK_PARENT_ID;
385
386 return m;
387 }
388
389 std::map<String,VMInt8Array*> InstrumentScriptVM::builtInIntArrayVariables() {
390 // first get built-in integer array variables of derived VM class
391 std::map<String,VMInt8Array*> m = ScriptVM::builtInIntArrayVariables();
392
393 // now add own built-in variables
394 m["%CC"] = &m_CC;
395 m["%KEY_DOWN"] = &m_KEY_DOWN;
396 //m["%POLY_AT"] = &m_POLY_AT;
397
398 return m;
399 }
400
401 std::map<String,vmint> InstrumentScriptVM::builtInConstIntVariables() {
402 // first get built-in integer variables of derived VM class
403 std::map<String,vmint> m = ScriptVM::builtInConstIntVariables();
404
405 m["$EVENT_STATUS_INACTIVE"] = EVENT_STATUS_INACTIVE;
406 m["$EVENT_STATUS_NOTE_QUEUE"] = EVENT_STATUS_NOTE_QUEUE;
407 m["$VCC_MONO_AT"] = CTRL_TABLE_IDX_AFTERTOUCH;
408 m["$VCC_PITCH_BEND"] = CTRL_TABLE_IDX_PITCHBEND;
409 for (int i = 0; i < INSTR_SCRIPT_EVENT_GROUPS; ++i) {
410 m["$MARK_" + ToString(i+1)] = i;
411 }
412 m["$EVENT_PAR_NOTE"] = EVENT_PAR_NOTE;
413 m["$EVENT_PAR_VELOCITY"] = EVENT_PAR_VELOCITY;
414 m["$EVENT_PAR_VOLUME"] = EVENT_PAR_VOLUME;
415 m["$EVENT_PAR_TUNE"] = EVENT_PAR_TUNE;
416 m["$EVENT_PAR_0"] = EVENT_PAR_0;
417 m["$EVENT_PAR_1"] = EVENT_PAR_1;
418 m["$EVENT_PAR_2"] = EVENT_PAR_2;
419 m["$EVENT_PAR_3"] = EVENT_PAR_3;
420 m["$NKSP_LINEAR"] = FADE_CURVE_LINEAR;
421 m["$NKSP_EASE_IN_EASE_OUT"] = FADE_CURVE_EASE_IN_EASE_OUT;
422 m["$CALLBACK_STATUS_TERMINATED"] = CALLBACK_STATUS_TERMINATED;
423 m["$CALLBACK_STATUS_QUEUE"] = CALLBACK_STATUS_QUEUE;
424 m["$CALLBACK_STATUS_RUNNING"] = CALLBACK_STATUS_RUNNING;
425
426 return m;
427 }
428
429 std::map<String,VMDynVar*> InstrumentScriptVM::builtInDynamicVariables() {
430 // first get built-in dynamic variables of derived VM class
431 std::map<String,VMDynVar*> m = ScriptVM::builtInDynamicVariables();
432
433 m["%ALL_EVENTS"] = &m_varAllEvents;
434 m["$ENGINE_UPTIME"] = &m_varEngineUptime;
435 m["$NI_CALLBACK_ID"] = &m_varCallbackID;
436 m["%NKSP_CALLBACK_CHILD_ID"] = &m_varCallbackChildID;
437
438 return m;
439 }
440
441 VMFunction* InstrumentScriptVM::functionByName(const String& name) {
442 // built-in script functions of this class
443 if (name == "play_note") return &m_fnPlayNote;
444 else if (name == "set_controller") return &m_fnSetController;
445 else if (name == "set_rpn") return &m_fnSetRpn;
446 else if (name == "set_nrpn") return &m_fnSetNrpn;
447 else if (name == "ignore_event") return &m_fnIgnoreEvent;
448 else if (name == "ignore_controller") return &m_fnIgnoreController;
449 else if (name == "note_off") return &m_fnNoteOff;
450 else if (name == "set_event_mark") return &m_fnSetEventMark;
451 else if (name == "delete_event_mark") return &m_fnDeleteEventMark;
452 else if (name == "by_marks") return &m_fnByMarks;
453 else if (name == "change_vol") return &m_fnChangeVol;
454 else if (name == "change_vol_time") return &m_fnChangeVolTime;
455 else if (name == "change_tune") return &m_fnChangeTune;
456 else if (name == "change_tune_time") return &m_fnChangeTuneTime;
457 else if (name == "change_note") return &m_fnChangeNote;
458 else if (name == "change_velo") return &m_fnChangeVelo;
459 else if (name == "change_pan") return &m_fnChangePan;
460 else if (name == "change_pan_time") return &m_fnChangePanTime;
461 else if (name == "change_pan_curve") return &m_fnChangePanCurve;
462 else if (name == "change_cutoff") return &m_fnChangeCutoff;
463 else if (name == "change_reso") return &m_fnChangeReso;
464 else if (name == "change_attack") return &m_fnChangeAttack;
465 else if (name == "change_decay") return &m_fnChangeDecay;
466 else if (name == "change_sustain") return &m_fnChangeSustain;
467 else if (name == "change_release") return &m_fnChangeRelease;
468 else if (name == "change_cutoff_attack") return &m_fnChangeCutoffAttack;
469 else if (name == "change_cutoff_decay") return &m_fnChangeCutoffDecay;
470 else if (name == "change_cutoff_sustain") return &m_fnChangeCutoffSustain;
471 else if (name == "change_cutoff_release") return &m_fnChangeCutoffRelease;
472 else if (name == "change_amp_lfo_depth") return &m_fnChangeAmpLFODepth;
473 else if (name == "change_amp_lfo_freq") return &m_fnChangeAmpLFOFreq;
474 else if (name == "change_cutoff_lfo_depth") return &m_fnChangeCutoffLFODepth;
475 else if (name == "change_cutoff_lfo_freq") return &m_fnChangeCutoffLFOFreq;
476 else if (name == "change_pitch_lfo_depth") return &m_fnChangePitchLFODepth;
477 else if (name == "change_pitch_lfo_freq") return &m_fnChangePitchLFOFreq;
478 else if (name == "fade_in") return &m_fnFadeIn;
479 else if (name == "fade_out") return &m_fnFadeOut;
480 else if (name == "change_vol_curve") return &m_fnChangeVolCurve;
481 else if (name == "change_tune_curve") return &m_fnChangeTuneCurve;
482 else if (name == "change_play_pos") return &m_fnChangePlayPos;
483 else if (name == "get_event_par") return &m_fnGetEventPar;
484 else if (name == "set_event_par") return &m_fnSetEventPar;
485 else if (name == "event_status") return &m_fnEventStatus;
486 else if (name == "wait") return &m_fnWait2; // override wait() core implementation
487 else if (name == "stop_wait") return &m_fnStopWait;
488 else if (name == "abort") return &m_fnAbort;
489 else if (name == "fork") return &m_fnFork;
490 else if (name == "callback_status") return &m_fnCallbackStatus;
491
492 // built-in script functions of derived VM class
493 return ScriptVM::functionByName(name);
494 }
495
496 } // namespace LinuxSampler

  ViewVC Help
Powered by ViewVC