1 |
schoenebeck |
2581 |
/* |
2 |
schoenebeck |
3733 |
* Copyright (c) 2014-2020 Christian Schoenebeck |
3 |
schoenebeck |
2581 |
* |
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 |
|
|
#ifndef LS_SCRIPTVM_H |
11 |
|
|
#define LS_SCRIPTVM_H |
12 |
|
|
|
13 |
|
|
#include <iostream> |
14 |
|
|
#include <vector> |
15 |
|
|
|
16 |
|
|
#include "../common/global.h" |
17 |
|
|
#include "common.h" |
18 |
|
|
|
19 |
|
|
namespace LinuxSampler { |
20 |
|
|
|
21 |
|
|
class ParserContext; |
22 |
schoenebeck |
2588 |
class ExecContext; |
23 |
schoenebeck |
2581 |
|
24 |
schoenebeck |
2594 |
/** @brief Core virtual machine for real-time instrument scripts. |
25 |
|
|
* |
26 |
schoenebeck |
2727 |
* This is the core of the virtual machine and main entry class, used for |
27 |
|
|
* running real-time instrument scripts. This VM core encompasses the |
28 |
|
|
* instrument script parser, generalized virtual machine and very generic |
29 |
|
|
* built-in script functions. Thus this class only provides functionalities |
30 |
|
|
* which are yet independent of the actual purpose the virtual machine is |
31 |
|
|
* going to be used for. |
32 |
schoenebeck |
2594 |
* |
33 |
|
|
* The actual use case specific functionalites (i.e. MIDI processing) is |
34 |
schoenebeck |
2727 |
* then implemented by sampler engines' VM classes which are derived from |
35 |
|
|
* this generalized ScriptVM class. |
36 |
schoenebeck |
2594 |
* |
37 |
schoenebeck |
2727 |
* Typical usage of this class: |
38 |
|
|
* |
39 |
|
|
* - 1. Create an instance of this ScriptVM class (or of one of its deriving |
40 |
|
|
* classes). |
41 |
|
|
* - 2. Load a script by passing its source code to method loadScript(), |
42 |
schoenebeck |
2729 |
* which will return the parsed representation of the script. |
43 |
schoenebeck |
2727 |
* - 3. Create a VM execution context by calling createExecContext(). |
44 |
|
|
* - 4. Execute the script by calling method exec(). |
45 |
|
|
* |
46 |
schoenebeck |
2594 |
* This class is re-entrant safe, but not thread safe. So you can share one |
47 |
|
|
* instance of this class between multiple (native) threads, but you @b must |
48 |
|
|
* @b not execute methods of the same class instance simultaniously from |
49 |
|
|
* different (native) threads. If you want to execute scripts simultaniously |
50 |
|
|
* multi threaded, then create a separate ScriptVM instance for each |
51 |
|
|
* (native) thread. Also note that one VMParserContext instance is tied to |
52 |
|
|
* exactly one ScriptVM instance. So you @b must @b not create a |
53 |
|
|
* VMParserContext with one ScriptVM instance and run it with a different |
54 |
|
|
* ScriptVM instance! |
55 |
|
|
*/ |
56 |
schoenebeck |
2581 |
class ScriptVM : public VMFunctionProvider { |
57 |
|
|
public: |
58 |
|
|
ScriptVM(); |
59 |
|
|
virtual ~ScriptVM(); |
60 |
schoenebeck |
2727 |
|
61 |
|
|
/** |
62 |
|
|
* Loads a script given by its source code (passed as argument @a s to |
63 |
schoenebeck |
2729 |
* this method) and returns the parsed representation of that script. |
64 |
schoenebeck |
2727 |
* After calling this method you must check the returned VMParserContext |
65 |
|
|
* object whether there had been any parser errors. If there were no |
66 |
|
|
* parser errors, you may pass the VMParserContext object to method |
67 |
|
|
* exec() for actually executing the script. |
68 |
|
|
* |
69 |
schoenebeck |
2889 |
* It is your responsibility to free the returned VMParserContext |
70 |
|
|
* object once you don't need it anymore. |
71 |
|
|
* |
72 |
schoenebeck |
3733 |
* The NKSP language supports so called 'patch' variables, which are |
73 |
|
|
* declared by the dedicated keyword 'patch' (as variable qualifier) in |
74 |
|
|
* real-time instrument scripts, like e.g.: |
75 |
|
|
* @code |
76 |
|
|
* on init |
77 |
|
|
* declare patch ~foo := 0.435 |
78 |
|
|
* end on |
79 |
|
|
* @endcode |
80 |
|
|
* These 'patch' variables allow to override their initial value (i.e. |
81 |
|
|
* on a per instrument basis). In the example above, the script variable |
82 |
|
|
* @c ~foo would be initialized with value @c 0.435 by default. However |
83 |
|
|
* by simply passing an appropriate key-value pair with argument |
84 |
|
|
* @p patchVars when calling this method, the NKSP parser will replace |
85 |
|
|
* that default initialization value by the passed replacement value. |
86 |
|
|
* So key of the optional @p patchVars map argument is the ('patch') |
87 |
|
|
* variable name to be patched, and value is the replacement |
88 |
|
|
* initialization value for the respective variable. You can see this as |
89 |
|
|
* kind of preprocessor mechanism of the NKSP parser, so you are not |
90 |
|
|
* limited to simply replace a scalar value with a different scalar |
91 |
|
|
* value, you can actually replace any complex default initialization |
92 |
|
|
* expression with a new (potentially complex) replacement expression, |
93 |
|
|
* e.g. including function calls, formulas, etc. |
94 |
|
|
* |
95 |
|
|
* The optional 3rd argument @p patchVarsDef allows you to retrieve the |
96 |
|
|
* default initialization value(s) of all 'patch' variables declared in |
97 |
|
|
* the passed script itself. This is useful for instrument editors. |
98 |
|
|
* |
99 |
schoenebeck |
2727 |
* @param s - entire source code of the script to be loaded |
100 |
schoenebeck |
3733 |
* @param patchVars - (optional) replacement value for patch variables |
101 |
|
|
* @param patchVarsDef - (optional) output of original values of patch |
102 |
|
|
* variables |
103 |
schoenebeck |
2729 |
* @returns parsed representation of the script |
104 |
schoenebeck |
2727 |
*/ |
105 |
schoenebeck |
3733 |
VMParserContext* loadScript(const String& s, |
106 |
|
|
const std::map<String,String>& patchVars = |
107 |
|
|
std::map<String,String>(), |
108 |
|
|
std::map<String,String>* patchVarsDef = NULL); |
109 |
schoenebeck |
2727 |
|
110 |
|
|
/** |
111 |
|
|
* Same as above's loadScript() method, but this one reads the script's |
112 |
|
|
* source code from an input stream object (i.e. stdin or a file). |
113 |
|
|
* |
114 |
|
|
* @param is - input stream from which the entire source code of the |
115 |
|
|
* script is to be read and loaded from |
116 |
schoenebeck |
3733 |
* @param patchVars - (optional) replacement value for patch variables |
117 |
|
|
* @param patchVarsDef - (optional) output of original values of patch |
118 |
|
|
* variables |
119 |
schoenebeck |
2729 |
* @returns parsed representation of the script |
120 |
schoenebeck |
2727 |
*/ |
121 |
schoenebeck |
3733 |
VMParserContext* loadScript(std::istream* is, |
122 |
|
|
const std::map<String,String>& patchVars = |
123 |
|
|
std::map<String,String>(), |
124 |
|
|
std::map<String,String>* patchVarsDef = NULL); |
125 |
schoenebeck |
2727 |
|
126 |
|
|
/** |
127 |
schoenebeck |
2885 |
* Parses a script's source code (passed as argument @a s to this |
128 |
|
|
* method), splits that input up in its individual tokens (i.e. |
129 |
|
|
* keyword, variable name, event name, etc.) and returns all those |
130 |
|
|
* tokens, for the purpose that the caller can provide syntax syntax |
131 |
|
|
* highlighting for the passed script. |
132 |
|
|
* |
133 |
|
|
* This method is actually not used by the sampler at all, it is rather |
134 |
|
|
* provided for external script editor applications, to provide them a |
135 |
|
|
* convenient backend for parsing scripts and providing syntax |
136 |
|
|
* highlighting. |
137 |
|
|
* |
138 |
|
|
* @returns recognized tokens of passed script's source code |
139 |
|
|
*/ |
140 |
|
|
std::vector<VMSourceToken> syntaxHighlighting(const String& s); |
141 |
|
|
|
142 |
|
|
/** |
143 |
|
|
* Same as above's syntaxHighlighting() method, but this one reads the |
144 |
|
|
* script's source code from an input stream object (i.e. stdin or a |
145 |
|
|
* file). |
146 |
|
|
* |
147 |
|
|
* @param is - input stream from which the entire source code of the |
148 |
|
|
* script is to be read and loaded from |
149 |
|
|
* @returns recognized tokens of passed script's source code |
150 |
|
|
*/ |
151 |
|
|
std::vector<VMSourceToken> syntaxHighlighting(std::istream* is); |
152 |
|
|
|
153 |
|
|
/** |
154 |
schoenebeck |
2727 |
* Dumps the translated tree of the already parsed script, given by |
155 |
|
|
* argument @a context, to stdout. This method is for debugging purposes |
156 |
|
|
* only. |
157 |
|
|
* |
158 |
schoenebeck |
2729 |
* @param context - parsed representation of the script |
159 |
schoenebeck |
2728 |
* @see loadScript() |
160 |
schoenebeck |
2727 |
*/ |
161 |
schoenebeck |
2588 |
void dumpParsedScript(VMParserContext* context); |
162 |
schoenebeck |
2727 |
|
163 |
|
|
/** |
164 |
|
|
* Creates a so called VM exceution context for a specific, already |
165 |
|
|
* parsed script (provided by argument @a parserContext). Due to the |
166 |
|
|
* general real-time design of this virtual machine, the VM execution |
167 |
|
|
* context differs for every script. So you must (re)create the |
168 |
|
|
* execution context for each script being loaded. |
169 |
schoenebeck |
2728 |
* |
170 |
schoenebeck |
2729 |
* @param parserContext - parsed representation of the script |
171 |
schoenebeck |
2728 |
* @see loadScript() |
172 |
schoenebeck |
2727 |
*/ |
173 |
schoenebeck |
2588 |
VMExecContext* createExecContext(VMParserContext* parserContext); |
174 |
schoenebeck |
2727 |
|
175 |
|
|
/** |
176 |
|
|
* Execute a script by virtual machine. Since scripts are event-driven, |
177 |
|
|
* you actually execute only one specific event handler block (i.e. a |
178 |
|
|
* "on note ... end on" code block) by calling this method (not the |
179 |
|
|
* entire script), and hence you must provide one precise handler of the |
180 |
|
|
* script to be executed by this method. |
181 |
|
|
* |
182 |
|
|
* This method usually blocks until the entire script event handler |
183 |
|
|
* block has been executed completely. It may however also return before |
184 |
|
|
* completion if either a) a script runtime error occurred or b) the |
185 |
schoenebeck |
2871 |
* script was suspended by the VM (either because script execution |
186 |
schoenebeck |
2727 |
* exceeded a certain limit of time or the script called the built-in |
187 |
|
|
* wait() function). You must check the return value of this method to |
188 |
|
|
* find out which case applies. |
189 |
|
|
* |
190 |
schoenebeck |
2729 |
* @param parserContext - parsed representation of the script (see loadScript()) |
191 |
schoenebeck |
2727 |
* @param execContext - VM execution context (see createExecContext()) |
192 |
|
|
* @param handler - precise event handler (i.e. "on note ... end on" |
193 |
|
|
* code block) to be executed |
194 |
|
|
* (see VMParserContext::eventHandlerByName()) |
195 |
|
|
* @returns current status of the vitual machine (i.e. script succeeded, |
196 |
|
|
* script runtime error occurred or script was suspended for |
197 |
|
|
* some reason). |
198 |
|
|
*/ |
199 |
|
|
VMExecStatus_t exec(VMParserContext* parserContext, VMExecContext* execContext, VMEventHandler* handler); |
200 |
|
|
|
201 |
|
|
/** |
202 |
|
|
* Returns built-in script function for the given function @a name. To |
203 |
|
|
* get the implementation of the built-in message() script function for |
204 |
|
|
* example, you would pass "message" here). |
205 |
|
|
* |
206 |
|
|
* This method is re-implemented by deriving classes to add more use |
207 |
|
|
* case specific built-in functions. |
208 |
|
|
* |
209 |
|
|
* @param name - name of the function to be retrieved (i.e. "wait" for the |
210 |
|
|
* built-in wait() function). |
211 |
|
|
*/ |
212 |
schoenebeck |
2594 |
VMFunction* functionByName(const String& name) OVERRIDE; |
213 |
schoenebeck |
2727 |
|
214 |
|
|
/** |
215 |
schoenebeck |
3311 |
* Whether the passed built-in function is disabled and should thus be |
216 |
|
|
* ignored by the parser at the passed parser context (parser state |
217 |
|
|
* where the built-in function call occurs). |
218 |
|
|
* |
219 |
|
|
* @param fn - built-in function to be checked |
220 |
|
|
* @param ctx - parser context at the position where the built-in |
221 |
|
|
* function call is located within the script |
222 |
|
|
*/ |
223 |
|
|
bool isFunctionDisabled(VMFunction* fn, VMParserContext* ctx) OVERRIDE; |
224 |
|
|
|
225 |
|
|
/** |
226 |
schoenebeck |
2727 |
* Returns all built-in integer script variables. This method returns a |
227 |
|
|
* STL map, where the map's key is the variable name and the map's value |
228 |
|
|
* is the native pointer to the actual built-in variable. |
229 |
|
|
* |
230 |
|
|
* This method is re-implemented by deriving classes to add more use |
231 |
|
|
* case specific built-in variables. |
232 |
|
|
*/ |
233 |
schoenebeck |
3557 |
std::map<String,VMIntPtr*> builtInIntVariables() OVERRIDE; |
234 |
schoenebeck |
2727 |
|
235 |
|
|
/** |
236 |
|
|
* Returns all built-in (8 bit) integer array script variables. This |
237 |
|
|
* method returns a STL map, where the map's key is the array variable |
238 |
|
|
* name and the map's value is the native pointer to the actual built-in |
239 |
|
|
* array variable. |
240 |
|
|
* |
241 |
|
|
* This method is re-implemented by deriving classes to add more use |
242 |
|
|
* case specific built-in array variables. |
243 |
|
|
*/ |
244 |
schoenebeck |
2594 |
std::map<String,VMInt8Array*> builtInIntArrayVariables() OVERRIDE; |
245 |
schoenebeck |
2727 |
|
246 |
|
|
/** |
247 |
schoenebeck |
2948 |
* Returns all built-in constant integer script variables, which are |
248 |
|
|
* constant and their final data is already available at parser time |
249 |
|
|
* and won't change during runtime. Providing your built-in constants |
250 |
|
|
* this way may lead to performance benefits compared to using other |
251 |
|
|
* ways of providing built-in variables, because the script parser |
252 |
|
|
* can perform optimizations when the script is refering to such |
253 |
|
|
* constants. |
254 |
schoenebeck |
2727 |
* |
255 |
schoenebeck |
2948 |
* This type of built-in variable can only be read, but not be altered |
256 |
|
|
* by scripts. This method returns a STL map, where the map's key is |
257 |
|
|
* the variable name and the map's value is the final constant data. |
258 |
|
|
* |
259 |
schoenebeck |
2727 |
* This method is re-implemented by deriving classes to add more use |
260 |
|
|
* case specific built-in constant integers. |
261 |
|
|
* |
262 |
schoenebeck |
2948 |
* @b Note: In case your built-in variable should be read-only but its |
263 |
|
|
* value is not already available at parser time (i.e. because its |
264 |
|
|
* value may change at runtime), then you should add it to |
265 |
|
|
* builtInIntVariables() instead and use the macro |
266 |
|
|
* DECLARE_VMINT_READONLY() to define the variable for read-only |
267 |
|
|
* access by scripts. |
268 |
schoenebeck |
2727 |
*/ |
269 |
schoenebeck |
3557 |
std::map<String,vmint> builtInConstIntVariables() OVERRIDE; |
270 |
schoenebeck |
2588 |
|
271 |
schoenebeck |
2942 |
/** |
272 |
schoenebeck |
3590 |
* Returns all built-in constant real number (floating point) script |
273 |
|
|
* variables, which are constant and their final data is already |
274 |
|
|
* available at parser time and won't change during runtime. Providing |
275 |
|
|
* your built-in constants this way may lead to performance benefits |
276 |
|
|
* compared to using other ways of providing built-in variables, because |
277 |
|
|
* the script parser can perform optimizations when the script is |
278 |
|
|
* refering to such constants. |
279 |
|
|
* |
280 |
|
|
* This type of built-in variable can only be read, but not be altered |
281 |
|
|
* by scripts. This method returns a STL map, where the map's key is |
282 |
|
|
* the variable name and the map's value is the final constant data. |
283 |
|
|
* |
284 |
|
|
* This method is re-implemented by deriving classes to add more use |
285 |
|
|
* case specific built-in constant real numbers. |
286 |
|
|
*/ |
287 |
|
|
std::map<String,vmfloat> builtInConstRealVariables() OVERRIDE; |
288 |
|
|
|
289 |
|
|
/** |
290 |
schoenebeck |
2942 |
* Returns all built-in dynamic variables. This method returns a STL |
291 |
|
|
* map, where the map's key is the dynamic variable's name and the |
292 |
|
|
* map's value is the pointer to the actual object implementing the |
293 |
|
|
* behavior which is actually generating the content of the dynamic |
294 |
|
|
* variable. |
295 |
|
|
* |
296 |
|
|
* This method is re-implemented by deriving classes to add more use |
297 |
|
|
* case specific built-in dynamic variables. |
298 |
|
|
*/ |
299 |
|
|
std::map<String,VMDynVar*> builtInDynamicVariables() OVERRIDE; |
300 |
|
|
|
301 |
schoenebeck |
2974 |
/** |
302 |
|
|
* Enables or disables automatic suspension of scripts by the VM. |
303 |
|
|
* If automatic suspension is enabled then scripts are monitored |
304 |
|
|
* regarding their execution time and in case they are execution |
305 |
|
|
* for too long, then they are automatically suspended by the VM for |
306 |
|
|
* a certain amount of time in order to avoid any RT instablity |
307 |
|
|
* issues caused by bugs in the script, i.e. endless while() loops |
308 |
|
|
* or very large scripts. |
309 |
|
|
* |
310 |
|
|
* Automatic suspension is enabled by default due to the aimed |
311 |
|
|
* real-time context of this virtual machine. |
312 |
|
|
* |
313 |
|
|
* @param b - true: enable auto suspension [default], |
314 |
|
|
* false: disable auto suspension |
315 |
|
|
*/ |
316 |
|
|
void setAutoSuspendEnabled(bool b = true); |
317 |
|
|
|
318 |
|
|
/** |
319 |
|
|
* Returns true in case automatic suspension of scripts by the VM is |
320 |
|
|
* enabled. See setAutoSuspendEnabled() for details. |
321 |
|
|
* |
322 |
|
|
* Automatic suspension is enabled by default due to the aimed |
323 |
|
|
* real-time context of this virtual machine. |
324 |
|
|
*/ |
325 |
|
|
bool isAutoSuspendEnabled() const; |
326 |
|
|
|
327 |
schoenebeck |
3551 |
/** |
328 |
|
|
* By default (i.e. in production use) the built-in exit() function |
329 |
|
|
* prohibits any arguments to be passed to its function by scripts. So |
330 |
|
|
* by default, scripts trying to pass any arguments to the built-in |
331 |
|
|
* exit() function will yield in a parser error. |
332 |
|
|
* |
333 |
|
|
* By calling this method the built-in exit() function will optionally |
334 |
|
|
* accept one argument to be passed to its function call by scripts. The |
335 |
|
|
* value of that function argument will become available by calling |
336 |
|
|
* VMExecContext::exitResult() after execution of the script. |
337 |
|
|
* |
338 |
|
|
* @see VMExecContext::exitResult() |
339 |
|
|
*/ |
340 |
|
|
void setExitResultEnabled(bool b = true); |
341 |
|
|
|
342 |
|
|
/** |
343 |
|
|
* Returns @c true if the built-in exit() function optionally accepts |
344 |
|
|
* a function argument by scripts. |
345 |
|
|
* |
346 |
|
|
* @see setExitResultEnabled() |
347 |
|
|
*/ |
348 |
|
|
bool isExitResultEnabled() const; |
349 |
|
|
|
350 |
schoenebeck |
2879 |
VMEventHandler* currentVMEventHandler(); //TODO: should be protected (only usable during exec() calls, intended only for VMFunctions) |
351 |
schoenebeck |
2588 |
VMParserContext* currentVMParserContext(); //TODO: should be protected (only usable during exec() calls, intended only for VMFunctions) |
352 |
|
|
VMExecContext* currentVMExecContext(); //TODO: should be protected (only usable during exec() calls, intended only for VMFunctions) |
353 |
schoenebeck |
2727 |
|
354 |
schoenebeck |
3733 |
private: |
355 |
|
|
VMParserContext* loadScriptOnePass(const String& s); |
356 |
schoenebeck |
2581 |
protected: |
357 |
schoenebeck |
2879 |
VMEventHandler* m_eventHandler; |
358 |
schoenebeck |
2588 |
ParserContext* m_parserContext; |
359 |
schoenebeck |
2974 |
bool m_autoSuspend; |
360 |
schoenebeck |
3551 |
bool m_acceptExitRes; |
361 |
schoenebeck |
2942 |
class CoreVMFunction_message* m_fnMessage; |
362 |
|
|
class CoreVMFunction_exit* m_fnExit; |
363 |
|
|
class CoreVMFunction_wait* m_fnWait; |
364 |
|
|
class CoreVMFunction_abs* m_fnAbs; |
365 |
|
|
class CoreVMFunction_random* m_fnRandom; |
366 |
|
|
class CoreVMFunction_num_elements* m_fnNumElements; |
367 |
schoenebeck |
2945 |
class CoreVMFunction_inc* m_fnInc; |
368 |
|
|
class CoreVMFunction_dec* m_fnDec; |
369 |
schoenebeck |
3076 |
class CoreVMFunction_in_range* m_fnInRange; |
370 |
schoenebeck |
2965 |
class CoreVMFunction_sh_left* m_fnShLeft; |
371 |
|
|
class CoreVMFunction_sh_right* m_fnShRight; |
372 |
schoenebeck |
3678 |
class CoreVMFunction_msb* m_fnMsb; |
373 |
|
|
class CoreVMFunction_lsb* m_fnLsb; |
374 |
schoenebeck |
2970 |
class CoreVMFunction_min* m_fnMin; |
375 |
|
|
class CoreVMFunction_max* m_fnMax; |
376 |
schoenebeck |
3221 |
class CoreVMFunction_array_equal* m_fnArrayEqual; |
377 |
|
|
class CoreVMFunction_search* m_fnSearch; |
378 |
|
|
class CoreVMFunction_sort* m_fnSort; |
379 |
schoenebeck |
3573 |
class CoreVMFunction_int_to_real* m_fnIntToReal; |
380 |
|
|
class CoreVMFunction_real_to_int* m_fnRealToInt; |
381 |
schoenebeck |
3590 |
class CoreVMFunction_round* m_fnRound; |
382 |
|
|
class CoreVMFunction_ceil* m_fnCeil; |
383 |
|
|
class CoreVMFunction_floor* m_fnFloor; |
384 |
|
|
class CoreVMFunction_sqrt* m_fnSqrt; |
385 |
|
|
class CoreVMFunction_log* m_fnLog; |
386 |
|
|
class CoreVMFunction_log2* m_fnLog2; |
387 |
|
|
class CoreVMFunction_log10* m_fnLog10; |
388 |
|
|
class CoreVMFunction_exp* m_fnExp; |
389 |
|
|
class CoreVMFunction_pow* m_fnPow; |
390 |
|
|
class CoreVMFunction_sin* m_fnSin; |
391 |
|
|
class CoreVMFunction_cos* m_fnCos; |
392 |
|
|
class CoreVMFunction_tan* m_fnTan; |
393 |
|
|
class CoreVMFunction_asin* m_fnAsin; |
394 |
|
|
class CoreVMFunction_acos* m_fnAcos; |
395 |
|
|
class CoreVMFunction_atan* m_fnAtan; |
396 |
schoenebeck |
2942 |
class CoreVMDynVar_NKSP_REAL_TIMER* m_varRealTimer; |
397 |
|
|
class CoreVMDynVar_NKSP_PERF_TIMER* m_varPerfTimer; |
398 |
schoenebeck |
2581 |
}; |
399 |
|
|
|
400 |
|
|
} // namespace LinuxSampler |
401 |
|
|
|
402 |
|
|
#endif // LS_INSTRUMENTSCRIPTVM_H |