├── images
├── icon.png
├── icon.pptx
├── chain-fx.png
├── channel-strip.png
├── scripter-menu.png
├── factory-scripts.png
├── midifx-options.png
├── instrument-channel.png
├── tutorial-scripts.png
├── scripter-dev-window.png
├── scripter-plugin-window.png
├── PluginParameters-example.png
└── copy.svg
├── scripter
├── processmidi-timing.js
├── trace.js
├── get-timing-info.js
├── console.log.js
└── PluginParameters-example.js
├── .gitignore
├── scripts
└── clipboard.min.js
├── LICENSE
└── README.md
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/icon.png
--------------------------------------------------------------------------------
/images/icon.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/icon.pptx
--------------------------------------------------------------------------------
/images/chain-fx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/chain-fx.png
--------------------------------------------------------------------------------
/images/channel-strip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/channel-strip.png
--------------------------------------------------------------------------------
/images/scripter-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/scripter-menu.png
--------------------------------------------------------------------------------
/images/factory-scripts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/factory-scripts.png
--------------------------------------------------------------------------------
/images/midifx-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/midifx-options.png
--------------------------------------------------------------------------------
/images/instrument-channel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/instrument-channel.png
--------------------------------------------------------------------------------
/images/tutorial-scripts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/tutorial-scripts.png
--------------------------------------------------------------------------------
/images/scripter-dev-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/scripter-dev-window.png
--------------------------------------------------------------------------------
/images/scripter-plugin-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/scripter-plugin-window.png
--------------------------------------------------------------------------------
/images/PluginParameters-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/HEAD/images/PluginParameters-example.png
--------------------------------------------------------------------------------
/scripter/processmidi-timing.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ProcessMIDI example that measures the internal
3 | */
4 |
5 | let lastTime = null;
6 |
7 | function ProcessMIDI() {
8 | const now = new Date().getTime();
9 | if (lastTime) Trace(`${now - lastTime}ms`);
10 | lastTime = now;
11 | }
12 |
13 | /*
14 | * Sample output with sample rate of 44.1kHz and buffer of 1024 samples
15 |
16 | 23ms
17 | 24ms
18 | 23ms
19 | 23ms
20 | 23ms
21 | */
22 |
--------------------------------------------------------------------------------
/scripter/trace.js:
--------------------------------------------------------------------------------
1 | /* Trace() examples */
2 |
3 | // Primitives: string, number, boolean
4 | Trace('Hello World!');
5 | Trace(3.1415);
6 | Trace(true);
7 |
8 | // Objects
9 | const obj = {num: 1.2, str: 'howdy', boolean: true, arr: [5,6,7]};
10 | Trace(obj);
11 | Trace(JSON.stringify(obj));
12 | Trace(JSON.stringify(obj, null, 4));
13 |
14 | // Increase the loop count to force some trimming
15 | for (let i=1; i<=10; i++) Trace("number " + i);
16 |
--------------------------------------------------------------------------------
/scripter/get-timing-info.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ProcessMIDI example that measures the internal
3 | */
4 |
5 | // Required to enable GetTimingInfo()
6 | var NeedsTimingInfo = true;
7 |
8 | function ProcessMIDI() {
9 |
10 | var info = GetTimingInfo(); // get a TimingInfo object from the host
11 |
12 | //if the transport is running
13 | if (info.playing) {
14 | Trace(info.tempo); // print the tempo in the plugin console
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scripter/console.log.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Author: Dewdman42 on https://www.logicprohelp.com/forum
3 | * Link: https://www.logicprohelp.com/forum/viewtopic.php?t=144010#
4 | *
5 | * This snippet of code provides buffered tracing. What that
6 | * means is that all Trace messages will be sent first to a
7 | * buffer and then later sent to the console window when
8 | * Scripter is not busy. The result is that ALL tracing
9 | * messages will be sent out, none will be lost.
10 | */
11 |
12 | const console = {
13 | maxFlush: 20,
14 | b:[],
15 | log: function(msg) {this.b.push(msg)},
16 | flush: function() {
17 | var i=0;
18 | while(i<=this.maxFlush && this.b.length>0) {
19 | Trace(this.b.shift());
20 | i++;
21 | }
22 | }
23 | };
24 | function Idle() {
25 | console.flush();
26 | }
27 |
28 | // Intead of Trace(msg) use this:
29 | console.log("Hello World");
30 |
31 | // This will buffer and print out 20 lines about every quarter second
32 | for (i=0; i<1000; i++)
33 | console.log("number " + i);
34 |
35 | // Instead of event.trace() use this:
36 | console.log(event.toString());
37 |
--------------------------------------------------------------------------------
/images/copy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
46 |
--------------------------------------------------------------------------------
/scripter/PluginParameters-example.js:
--------------------------------------------------------------------------------
1 | var PluginParameters =
2 | [
3 | // Text divider / header
4 | {
5 | name:"------ Example Plugin Parameters ------",
6 | type:"text",
7 | },
8 | // Note Velocity: linear slider with a range of 1 to 127 in 126 steps. Default: 100
9 | {
10 | name:"Note Velocity",
11 | type:"lin",
12 | minValue:1,
13 | maxValue:127,
14 | numberOfSteps:126,
15 | defaultValue:100
16 | },
17 | // Transpose: linear slider with a range of -24 to 24 in 48 steps. Defauly: 0
18 | {
19 | name:"Transpose",
20 | type:'lin',
21 | minValue:-24,
22 | maxValue:24,
23 | numberOfSteps:48,
24 | unit: "semi-tones",
25 | defaultValue: 0
26 | },
27 | // Checkbox that is defaulted to off (de-selected)
28 | {
29 | name:"Enable",
30 | type:"checkbox",
31 | defaultValue: 0
32 | },
33 | // Menu with 2 items is presented as radio buttons
34 | {
35 | name:"Radio",
36 | type:"menu",
37 | valueStrings:["Opt 1", "Opt 2"],
38 | // default is the index in valueStrings array
39 | defaultValue: 1
40 | },
41 | // Menu with 3 or more menu is presented as drop-down menu
42 | {
43 | name:"Range",
44 | type:"menu",
45 | valueStrings:["Low", "Mid", "High"],
46 | // default is the index in valueStrings array - Mid
47 | defaultValue: 1
48 | },
49 | // MIDI Target selector
50 | {
51 | name: "Target",
52 | type: "target",
53 | },
54 | // Momentary trigger button
55 | {
56 | name: "Trigger",
57 | type: "momentary",
58 | }
59 | ];
60 |
61 | function ParameterChanged(param, value) {
62 | // param is index in PluginParameters array
63 | Trace(`"${PluginParameters[param].name}" changed to ${value}`);
64 | }
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac crud
2 | .DS_Store
3 | tmp
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # Diagnostic reports (https://nodejs.org/api/report.html)
14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 | *.lcov
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # TypeScript v1 declaration files
49 | typings/
50 |
51 | # TypeScript cache
52 | *.tsbuildinfo
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variables file
76 | .env
77 | .env.test
78 |
79 | # parcel-bundler cache (https://parceljs.org/)
80 | .cache
81 |
82 | # Next.js build output
83 | .next
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 |
--------------------------------------------------------------------------------
/scripts/clipboard.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * clipboard.js v2.0.8
3 | * https://clipboardjs.com/
4 | *
5 | * Licensed MIT © Zeno Rocha
6 | */
7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={134:function(t,e,n){"use strict";n.d(e,{default:function(){return r}});var e=n(279),i=n.n(e),e=n(370),a=n.n(e),e=n(817),o=n.n(e);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){for(var n=0;n `~/Music/Audio Music Apps/Plug-In Settings/Scripter`
143 |
144 | Logic Pro script directory contains the Factory Script plug-ins that you see in the Scripter menu. Under this directory is the `Tutorial Scripts` directory.
145 |
146 | > `/Applications/Logic Pro X.app/Contents/Resources/Plug-In Settings/Scripter`
147 |
148 |
149 | ## Standard JavaScript Capabilities
150 |
151 | Apple's doc provides limited information on the JavaScript environment provided by Scripter. So, here's a quick summary which points some things that are standard, useful but documented and useful but not available.
152 |
153 | Scripter uses is JavaScript ES6 (EcmaScript 6). That's the same as modern browsers (as of 2021) and more powerful than some of the older JavaScript doc and examples on the web.
154 |
155 | The Scripter editor is a half-decent environment with some syntax checking and error highlighting. Some developers say that they do their development in an emulated Scripter environment with a better editor - sounds good to me. (TODO find an link to such a beast)
156 |
157 | Scripter requires all the code to be in a single script (no `import` or `require` of your favourites libraries and modules).
158 |
159 | Scripter makes most of the standard set of JavaScript features you know and love are available like...
160 |
161 | - `Class`
162 | - `Date`
163 | - `JSON`
164 | - `Math`
165 | - `Number`
166 | - `RegExp`
167 | - plenty more
168 |
169 | ES6 in Scripter gives us lots of nice features compared to older JavaScript variants.
170 |
171 | * Arrow functions as shorthand: `const square = (num) => num * num;`
172 | * `let` and `const` declarations for best practice
173 | * Default parameter values: `const square = (num=1) => num * num;`
174 | * "Rest" parameter (...) allows a function to treat an indefinite number of arguments as an array. `function sum(...args) { code }`
175 | * `for/of` loops: `for (variable of iterable) { ... }`
176 | * JavaScript Classes
177 | * JavaScript Promises (I'm not sure this is any use in Scripter for MIDI because we can't call async functions but feel free to code creatively)
178 | * Array.find() and Array.findIndex()
179 |
180 |
181 | ### Limitations to JavaScript
182 |
183 | There are some features that JS developers take for granted in a browser or node.js that are not available in Logic's Script.
184 |
185 | - file reading or any OS access
186 | - `require` or `import` for neat packaging of your code or re-using existing code libraries
187 | - `setTimeout` for delayed operators - see instead the [Event timing methods](#event-methods) and the [ProcessMIDI()](#processmidievent) callback
188 | - `alert`
189 | - `console` - use `Trace` instead
190 |
191 | ### Limited UI
192 |
193 | You can implementation a range of useful input controls with the [`PluginParameters`](#pluginparameters-object).
194 |
195 | Unfortunately, there is no way to get text input from a user unless your user is prepared to edit a script.
196 |
197 | But then, Scripter is a simpler development environment than for creation of full plug-ins so expect some limitations.
198 |
199 | ### Fragility
200 |
201 | My impression is that Apple has not "hardened" the Scripter environment. I find it crashes easily. That is, a rogue script will instantly crash the Logic Pro app (not just scripter).
202 |
203 | Some easy ways to crash Logic with Scripter...
204 |
205 | * Exceed tight memory limitations
206 | * Exceed tight time limitations
207 | * Print too much with [`Trace()`](#trace)
208 | * Other crashes that I can't diagnose
209 |
210 | To be clear... the time limitations **MAKE SENSE**. Music is time-sensitive and delays of a few milliseconds can affect output quality. (Look below for the `Idle()` that helps with slower tasks.)
211 |
212 | ### Unlike a Browser or Node.js
213 |
214 | Each JavaScript runtime has a context. JS running in a browser has access to windows, DOM and other webby things plus many critical security constraints. Node.js has access to parts of the operating system including the file system, ability to load packages plus a different set of critical security constraints.
215 |
216 | Scripter is a more compact and limited environment than either the browser or node.js.
217 |
218 |
219 | ## Global Variables
220 |
221 | Name | Description
222 | --- | ---
223 | `NeedsTimingInfo` | Boolean. Set to `true` to enable the `GetTimingInfo()` function for access to current [`TimingInfo`](#timinginfo-object) properties
224 | `ResetParameterDefaults` | Boolean. Sets UI controls to default values. NOTE: documentation on this is limited and I can't figure out how to use it.
225 | [`MIDI`](#midi-object) | Object. Provides a set of utility functions for working with MIDI objects.
226 |
227 |
228 | ## Global Functions
229 |
230 | The following are the Scripter functions that integrate into the Logic Pro MIDI environment.
231 |
232 | Feature | Description
233 | --- | ---
234 | [`HandleMIDI(event)`](#handlemidievent) | Called with each MIDI event on the channel that is received by the plug-in
235 | [`ProcessMIDI(event)`](#processmidievent) | Called periodically for regular tasks like sequencing and tempo-based effects
236 | [`ParameterChanged(paramNum, value)`](#pluginparameters-object) | Called after any parameter change by the user
237 | [`GetParameter(string)`](#xx) | Returns a given parameter’s current value
238 | [`UpdatePluginParameters()`](#xx) | dynamically updates the user interface
239 | [`GetParameter(param-name)`](#xx) | retrieves the current console value of a parameter
240 | [`SetParameter(param-name, value)`](#xx) | sets the console value of a parameter
241 | [`GetTimingInfo()`](#xx) | Retrieves a `TimingInfo` object, which contains timing information that describes the state of the host transport and the current musical tempo and meter
242 | [`Trace(obj)`](#xx) | Prints `obj` to the console. Only a single parameter is supported
243 | [`Reset()`](#xx) | Called when (a) bypass the Scripter plug-in, or (b) transport is started. No parameters
244 | [`Idle()`](#xx) | Called during idle times when it won't get in the way of HandleMIDI() and ProcessMIDI(). Usually a few times per second. TODO - expand
245 |
246 |
247 | ### `HandleMIDI(event)`
248 |
249 | This function is called for every MIDI event that is received by the plug-in on the channel strip. The parameter is an [`Event`](#event) object (described below in detail) but can be any of:
250 |
251 | * Note On
252 | * Note Off
253 | * Control Change
254 | * Program Change
255 | * Poly Pressure
256 | * Channel Pressure
257 | * Pitch Bend
258 | * Target Event
259 |
260 | `HandleMIDI()` allows the processing MIDI events:
261 |
262 | * Print them for debug
263 | * Modify the event (e.g. change pitch or velocity)
264 | * Ignore events
265 | * Add events (e.g. arpeggiate, add chords)
266 |
267 | It is optional to implement this function. If omitted, Logic will pass through the event unmodified.
268 |
269 | Examples:
270 |
271 | ```
272 | // Pass MIDI events through the plug-in with modification
273 | function HandleMIDI(event) {
274 | event.send();
275 | }
276 | ```
277 |
278 | ```
279 | // Pass MIDI events through the plug-in with modification
280 | function HandleMIDI(event) {
281 | event.send();
282 | event.trace();
283 | }
284 |
285 | /*
286 | Sample Output
287 | [NoteOn channel:1 pitch:44 [G#1] velocity:91]
288 | [NoteOff channel:1 pitch:44 [G#1] velocity:64]
289 | [NoteOn channel:1 pitch:44 [G#1] velocity:91]
290 | [NoteOn channel:1 pitch:36 [C1] velocity:91]
291 | */
292 | ```
293 |
294 | Note: `event.trace()` is an alternative to `Trace(event)`;
295 |
296 |
297 | ### `ProcessMIDI(event)`
298 |
299 | `ProcessMIDI()` enables execution of regular / periodic tasks. Examples include:
300 |
301 | * writing a sequencer
302 | * injecting time-based automation effects
303 | * any tempo-related effects
304 |
305 | `ProcessMIDI()` is called once per “process block”. Process blocks are related to the audio setup. NOTE: it is not affected by the tempo or MIDI content of your project.
306 |
307 | Specifically, the block is the duration of an audio output buffer which is determined by the Audio settings of Logic Pro plus the Project. That is, the block size (in samples) divided by the sample rate (in Hz).
308 |
309 | ```
310 | blockDuration = blockLength / sampleRate
311 |
312 | e.g.
313 | blockLength = 512 samples
314 | sampleRate = 44,100Hz (44.1kHz)
315 | blockDuration => 0.01161sec = 11.61msec
316 | ```
317 |
318 | The effect is that (a) increasing the sample rate decreases the block length and (b) increasing the block size (in samples) increase the block length.
319 |
320 | Sample Rate (Hz) | Buffer Size (Bytes) | Block size (msec) | Block Freq (Hz)
321 | --- | --- | --- | ---
322 | 44,100Hz | 32 | 0.73ms | 1378.125Hz
323 | 44,100Hz | 64 | 1.45ms | 689.0625Hz
324 | 44,100Hz | 128 | 2.9ms | 344.5313Hz
325 | 44,100Hz | 256 | 5.8ms | 172.2656Hz
326 | 44,100Hz | 512 | 11.61ms | 86.1328Hz
327 | 44,100Hz | 1024 | 23.22ms | 43.0664Hz
328 | 48,000Hz | 32 | 0.67ms | 1500Hz
329 | 48,000Hz | 64 | 1.33ms | 750Hz
330 | 48,000Hz | 128 | 2.67ms | 375Hz
331 | 48,000Hz | 256 | 5.33ms | 187.5Hz
332 | 48,000Hz | 512 | 10.67ms | 93.75Hz
333 | 48,000Hz | 1024 | 21.33ms | 46.875Hz
334 | 88,200Hz | 32 | 0.36ms | 2756.25Hz
335 | 88,200Hz | 64 | 0.73ms | 1378.125Hz
336 | 88,200Hz | 128 | 1.45ms | 689.0625Hz
337 | 88,200Hz | 256 | 2.9ms | 344.5313Hz
338 | 88,200Hz | 512 | 5.8ms | 172.2656Hz
339 | 88,200Hz | 1024 | 11.61ms | 86.1328Hz
340 | 96,000Hz | 32 | 0.33ms | 3000Hz
341 | 96,000Hz | 64 | 0.67ms | 1500Hz
342 | 96,000Hz | 128 | 1.33ms | 750Hz
343 | 96,000Hz | 256 | 2.67ms | 375Hz
344 | 96,000Hz | 512 | 5.33ms | 187.5Hz
345 | 96,000Hz | 1024 | 10.67ms | 93.75Hz
346 | 176,400Hz | 32 | 0.18ms | 5512.5Hz
347 | 176,400Hz | 64 | 0.36ms | 2756.25Hz
348 | 176,400Hz | 128 | 0.73ms | 1378.125Hz
349 | 176,400Hz | 256 | 1.45ms | 689.0625Hz
350 | 176,400Hz | 512 | 2.9ms | 344.5313Hz
351 | 176,400Hz | 1024 | 5.8ms | 172.2656Hz
352 | 192,000Hz | 32 | 0.17ms | 6000Hz
353 | 192,000Hz | 64 | 0.33ms | 3000Hz
354 | 192,000Hz | 128 | 0.67ms | 1500Hz
355 | 192,000Hz | 256 | 1.33ms | 750Hz
356 | 192,000Hz | 512 | 2.67ms | 375Hz
357 | 192,000Hz | 1024 | 5.33ms | 187.5Hz
358 |
359 |
360 | `ProcessMIDI()` is not required.
361 |
362 | `ProcessMIDI()` has no arguments.
363 |
364 | `ProcessMIDI()` is often used in combination with the [`TimingInfo`](#timinginfo-object) object which provides timing information from Logic.
365 |
366 | Caution: if you print much information on each call to `ProcessMIDI()` it can cause [trimming](#trimming-on-trace) and you won't see all the messages.
367 |
368 |
369 | #### Find your block duration
370 |
371 | This example allows you to determine the interval between calls to `ProcessMIDI()`. See [`scripts/processmidi-timing.js`](scripts/processmidi-timing.js)
372 |
373 | ```
374 | /*
375 | * ProcessMIDI example that measures the internal
376 | */
377 |
378 | let lastTime = null;
379 |
380 | function ProcessMIDI() {
381 | const now = new Date().getTime();
382 | if (lastTime) Trace(`${now - lastTime}ms`);
383 | lastTime = now;
384 | }
385 |
386 | /*
387 | * Sample output with sample rate of 44.1kHz and buffer of 1024 samples
388 |
389 | 23ms
390 | 24ms
391 | 23ms
392 | 23ms
393 | 23ms
394 | */
395 | ```
396 |
397 |
398 | ### `Trace(obj)`
399 |
400 | Scripter doesn't have the `console` that you find in browsers or node. Instead it offers `Trace(obj)` to write `obj` to the Scripter console.
401 |
402 | This is useful for status and debug. Most Logic users of your plugin won't ever see the console so don't put important stuff there.
403 |
404 | Some things to note:
405 |
406 | 1. Only a single parameter is accepted
407 | 2. Create your own string if you want to write multiple parameters
408 | 3. `JSON.stringify()` is available and useful
409 | 4. `Trace()` without a parameter does nothing. Use `Trace("\n")` for a blank line.
410 |
411 | #### Limitations of `Trace()`
412 |
413 | [Trimming](#trimming) may prevent display of some of your messages. More detail [below](#trimming).
414 |
415 | The maximum string length appears to 1020 characters (as of Logic Pro 10.15.2). Longer strings generate an error but processing continues: `Error: Trace() failed. Try a shorter string.`
416 |
417 | Sometimes it appears that to much `Trace()` can cause Logic Pro to crash.
418 |
419 | #### `Trace()` for Javascript types
420 |
421 | All the standard JS types can be passed to `Trace()`. See [`scripts/trace.js`](scripts/trace.js)
422 |
423 | ```
424 | /* Trace() examples */
425 |
426 | // Primitives: string, number, boolean
427 | Trace('Hello World!');
428 |
429 | // Objects
430 | const obj = {num: 1.2, str: 'howdy', boolean: true, arr: [5,6,7]};
431 | Trace(obj);
432 | Trace(JSON.stringify(obj));
433 | Trace(JSON.stringify(obj, null, 4));
434 |
435 | // Force some trimming
436 | for (let i=1; i<=1000; i++) Trace("number " + i);
437 | ```
438 |
439 | Output:
440 |
441 | ```
442 | Hello World!
443 | number 1
444 | number 2
445 | number 3
446 | [object Object]
447 | {"num":1.2,"str":"howdy","boolean":true,"arr":[5,6,7]}
448 | {
449 | "num": 1.2,
450 | "str": "howdy",
451 | "boolean": true,
452 | "arr": [
453 | 5,
454 | 6,
455 | 7
456 | ]
457 | }
458 | >
459 | ```
460 |
461 | #### Trimming on Trace
462 |
463 | Logic Pro is a time-sensitive environment. If Scripter gets too many trace requests it will trim them for performance reasons -- to avoid latency.
464 |
465 | You will be annoyed.
466 |
467 | ```
468 | ...console bandwidth exceeded, thinning some traces...
469 | NoteOn
470 | ...console bandwidth exceeded, thinning some traces...
471 | ```
472 |
473 | `Dewdman42` posted a [handy work-around](https://www.logicprohelp.com/forum/viewtopic.php?t=144010) that defers the console work to the [`Idle()`](#idle) function. Conveniently, it's named `console.log` so that you can feel at home.
474 |
475 | See [`scripts/console.log.js`](scripts/console.log.js)
476 |
477 | TODO: it would be good to allow printing of multiple arguments as in console.log.
478 |
479 | ```
480 | var console = {
481 | maxFlush: 20,
482 | b:[],
483 | log: function(msg) {this.b.push(msg)},
484 | flush: function() {
485 | var i=0;
486 | while(i<=this.maxFlush && this.b.length>0) {
487 | Trace(this.b.shift());
488 | i++;
489 | }
490 | }
491 | };
492 |
493 | function Idle() {
494 | console.flush();
495 | }
496 |
497 | // Intead of Trace(msg) use this:
498 | console.log("Hello World");
499 |
500 | // Instead of event.trace() use this:
501 | console.log(event.toString());
502 | ```
503 |
504 |
505 |
506 | ### `Idle()`
507 |
508 | This feature is not documented by Apple but used in it's examples.
509 |
510 | Logic Pro makes music and that's time-sensitive. Small delays introduced by your scripts could result in latency in the music creation and that's bad.
511 |
512 | So Logic offers the `Idle()` function for housekeeping duties that might take a little longer like...
513 |
514 | * printing to the console. e.g. [`console.log()`](#trimming-on-trace)
515 | * updating the GUI
516 |
517 | Your `Idle()` function is called approximately every quarter second. (I measured it at 0.265sec on my MacBook Pro but have no idea if that will vary between Logic updates or across hardware).
518 |
519 |
520 | ## Scripter Objects
521 |
522 | ### `Event` Object
523 |
524 | Read Apple's documentation on the [Event Object](https://support.apple.com/en-au/guide/logicpro/lgce0d0efc5a/10.6.2/mac/10.15.7)
525 |
526 | #### Event Types
527 |
528 | The `Event` object is a prototype for the various standard MIDI event types. All the following sub-classes share common properties and [Event Methods](#event-methods). However, each `Event` type has properties that are specific to it's MIDI definition.
529 |
530 | Class | Description
531 | --- | ---
532 | `Note` | Parent class of `NoteOn` and `NoteOff`
533 | `NoteOn` | Represents a note on event
534 | `NoteOff` | Represents a note off event
535 | `PolyPressure` | A polyphonic aftertouch event
536 | `ControlChange` | A MIDI control change event
537 | `ProgramChange` | Represents a MIDI program change event
538 | `ChannelPressure` | Represents a MIDI channel pressure event
539 | `PitchBend` | Represents a MIDI pitch bend event
540 |
541 |
542 | #### Event Methods
543 |
544 | Generally, a MIDI plugin in Script will receive each MIDI event, treat it in some way, then "send" it onwards. Sending means passing on the MIDI event to the next step in the channel. That might be another MIDI FX plug-in or the Instrument.
545 |
546 | Event Method | Description
547 | --- | ---
548 | `Event.send()` | Send the event immediately
549 | `Event.sendAfterMilliseconds(delay-in-msec)` | Send the event after a specific delay (can be an integer or a floating point number)
550 | `Event.sendAtBeat(beat)` | Send the event at a specific beat in the host timeline. The beat is a floating-point number.
551 | `Event.sendAfterBeats(number beat)` | Send the event after a delay measure in beats.
552 | `Event.trace()` | Print the event to the console using the `Trace()` object
553 | `Event.toString()` | Returns selected information about the event as a string.
554 | `Event.toarticulationID(integer number)` | Sets the articulation ID from 0–254
555 | `Event.channel(number)` | Set MIDI channel 1 to 16
556 | `Event.beatPos` | Retrieves the event’s exact beat position
557 |
558 |
559 | #### Creating an `Event`
560 |
561 | Each type of `Event` has a constructor. Currently, Scripter does not accept parameters on the constructor. So the pattern is to create a default event then set all required properties.
562 |
563 | This example creates then sends `ControlChange` event.
564 |
565 | ```
566 | {
567 | var cc = new ControlChange;
568 |
569 | // set controller numer 1 = modulation
570 | cc.number = 1;
571 | // set the ControlChange value
572 | cc.value = 42;
573 |
574 | // Send it!
575 | cc.send();
576 |
577 | // Print it to the console
578 | cc.trace();
579 | }
580 |
581 | /*
582 | * Expected output...
583 |
584 | [ControlChange channel:1 number:1 [Modulation] value:42]
585 | */
586 |
587 | ```
588 |
589 | #### Clone an Event
590 |
591 | Clone an existing event by passing that event as the constructor parameter.
592 |
593 | ```
594 | const newNoteOn = new NoteOn(existingNoteOn);
595 | ```
596 |
597 | The cloned `Event` can then be modified.
598 |
599 | ```
600 | {
601 | const cc1 = new ControlChange;
602 | cc1.number = 1;
603 | cc1.value = 42;
604 |
605 | const cc2 = new ControlChange(cc1);
606 | cc2.value++;
607 |
608 | cc1.trace();
609 | cc2.trace();
610 | }
611 |
612 | /*
613 | * Expected output
614 |
615 | [ControlChange channel:1 number:1 [Modulation] value:42]
616 | [ControlChange channel:1 number:1 [Modulation] value:43]
617 | */
618 | ```
619 |
620 | #### Modifying an Event
621 |
622 | The properties of any event can be modified before the `send()`.
623 |
624 | This example transposes the pitch of every `Note` event (`NoteOn` and `NoteOff`) and passes through any other events without modification.
625 |
626 | ```
627 | function HandleMIDI(event) {
628 | if (event instanceof Note) {
629 | event.pitch += 12;
630 | event.send();
631 | } else {
632 | event.send();
633 | }
634 | }
635 | ```
636 |
637 | TODO...
638 |
639 | Here's a JSON dump of the object contents...
640 | ```
641 | function HandleMIDI(event) {
642 | if (event instanceof NoteOn) {
643 | Trace(JSON.stringify(event, null, 4));
644 | }
645 | }
646 |
647 | /*
648 | * Example output
649 |
650 | {
651 | "detune":0,
652 | "pitch":36,
653 | "velocity":91,
654 | "status":144,
655 | "isRealtime":false,
656 | "data1":36,
657 | "data3":0,
658 | "data2":91,
659 | "channel":1,
660 | "port":1,
661 | "articulationID":0,
662 | "beatPos":0
663 | }
664 | */
665 | ```
666 |
667 | #### `NoteOn` Event
668 |
669 | Has all the default [`Event` methods](#event-methods) plus the following properties:
670 |
671 | Property | Description
672 | --- | ---
673 | channel | [MIDI](#midi-object) value from 1 to 16
674 | pitch | [MIDI](#midi-object) value from 0 to 127
675 | velocity | [MIDI](#midi-object) value from 0 to 127. A velocity value of 0 is interpreted as a note off event, not a note on.
676 | articulationID | ??
677 | inStartFrame | ??
678 | isRealtime | ??
679 |
680 | #### `NoteOff` Event
681 |
682 | Has all the default [`Event` methods](#event-methods) plus the following properties:
683 |
684 | Property | Description
685 | --- | ---
686 | channel | [MIDI](#midi-object) value from 1 to 16
687 | pitch | [MIDI](#midi-object) value from 0 to 127
688 | velocity | [MIDI](#midi-object) value from 0 to 127
689 | articulationID | ??
690 | inStartFrame | ??
691 | isRealtime | ??
692 |
693 |
694 | #### `PolyPressure` Event
695 |
696 | A polyphonic aftertouch event. It has all the default [`Event` methods](#event-methods) plus the following properties:
697 |
698 | Property | Description
699 | --- | ---
700 | channel | [MIDI](#midi-object) value from 1 to 16
701 | pitch | [MIDI](#midi-object) value from 0 to 127
702 | value | [MIDI](#midi-object) value from 0 to 127 providing the pressure level
703 | inStartFrame | ??
704 | isRealtime | ??
705 |
706 |
707 | #### `ControlChange` Event
708 |
709 | A MIDI control change event. It has all the default [`Event` methods](#event-methods) plus the following properties:
710 |
711 | Property | Description
712 | --- | ---
713 | channel | [MIDI](#midi-object) value from 1 to 16
714 | number | [MIDI](#midi-object) Controller value from 0 to 127. TODO
715 | value | [MIDI](#midi-object) Controller value from 0 to 127. TODO
716 | inStartFrame | ??
717 | isRealtime | ??
718 |
719 |
720 | #### `ProgramChange` Events
721 |
722 | Represents a MIDI program change event. It has all the default [`Event` methods](#event-methods) plus the following properties:
723 |
724 | Property | Description
725 | --- | ---
726 | channel | [MIDI](#midi-object) value from 1 to 16
727 | number | [MIDI](#midi-object) Program change number from 0 to 127.
728 | inStartFrame | ??
729 | isRealtime | ??
730 |
731 |
732 | #### `ChannelPressure` Event
733 |
734 | Represents a MIDI channel pressure event for after-touch. It has all the default [`Event` methods](#event-methods) plus the following properties:
735 |
736 | Property | Description
737 | --- | ---
738 | channel | [MIDI](#midi-object) value from 1 to 16
739 | value | [MIDI](#midi-object) channel pressure value from 0 to 127.
740 | inStartFrame | ??
741 | isRealtime | ??
742 |
743 |
744 | #### `PitchBend` Event
745 |
746 | Represents a MIDI pitch bend event with a 14-bit integer value in the range -8192 to +8191. 0 is the center value. It has all the default [`Event` methods](#event-methods) plus the following properties:
747 |
748 | Property | Description
749 | --- | ---
750 | channel | [MIDI](#midi-object) value from 1 to 16
751 | value | [MIDI](#midi-object) channel pressure value from -8192 to 8191
752 | inStartFrame | ??
753 | isRealtime | ??
754 |
755 |
756 | #### `TargetEvent` Events
757 |
758 | Represents a user-defined MIDI ControlChannel message or can control plug-in parameters that a user selects through a `target` interface from the [`PluginParameters` object](#pluginparameters-object).
759 |
760 | In this example:
761 | * Include a `PluginParameters` of type `target` called "Select Target".
762 | * The default value of the drop-down is "Off". This has a value of `-1`.
763 | * The `ParameterChanged()` function is a debug utility to print the details of any change of the target made byt the user through the Plugin control.
e.g. `"Select Target" set to "Foot Control" [value=4]`
764 | * The `MIDI.ccName()` utility converts the MIDI value (0-127) to a human-readable format. e.g. `4` is "Foot Control"
765 |
766 | XXX
767 |
768 | ```
769 | var PluginParameters = [
770 | {
771 | name: "Select Target",
772 | type: "target"
773 | }
774 | ];
775 |
776 | // Pretty-print each time the user changes a parameter (debug)
777 | function ParameterChanged(paramNum, value) {
778 | const paramName = PluginParameters[paramNum].name;
779 | const targetName = MIDI.ccName(value)
780 | Trace(`"${paramName}" set to "${targetName}" [value=${value}]`)
781 | }
782 | ```
783 |
784 | "Select Target" set to "Foot Control" [value=4]
785 |
786 |
787 |
788 | You can create user-definable MIDI CC messages, or you can control plug-in parameters.
789 | TargetEvent reads the parameter to be modified from a menu where the user can select a destination MIDI CC.
790 | Alternately, you can use the Learn Plug-In Parameter feature to assign any plug-in parameter inserted after (below) Scripter in the same channel strip.
791 | The chosen destination is saved with the plug-in setting.
792 |
793 |
794 |
795 | TargetEvent.value(float): Sets the target value.
796 |
797 | TODO
798 |
799 | - TargetEvent.target(string): Create user definable MIDI CC messages or control plug-in parameters.
800 | - TargetEvent.value(float): Sets the target value.
801 |
802 | #### `Event` - Under the Covers
803 |
804 | There is more information available about events than is presented via the documented Scripter API. Use this script to display all properties of an event generated by Logic Pro. The additional properties can be manipulated.
805 |
806 | ```
807 | function HandleMIDI(event) {
808 | event.send();
809 |
810 | event.trace();
811 | Trace(JSON.stringify(event, null, 4))
812 | }
813 |
814 | /*
815 | * Output from a NoteOn event
816 |
817 | [NoteOn channel:1 pitch:44 [G#1] velocity:91]
818 | {
819 | "detune": 0,
820 | "pitch": 44,
821 | "velocity": 91,
822 | "status": 144,
823 | "isRealtime": false,
824 | "data1": 44,
825 | "data3": 0,
826 | "data2": 91,
827 | "channel": 1,
828 | "port": 1,
829 | "articulationID": 0,
830 | "beatPos": 0
831 | }
832 | */
833 | ```
834 |
835 |
836 | ### `TimingInfo` Object
837 |
838 | Ref: Apple's doc for [JavaScript TimingInfo object](https://support.apple.com/en-au/guide/logicpro/lgcee186be46/mac)
839 |
840 | `TimingInfo` returns the current timing information of Logic Pro plus the current musical tempo and meter. Important:
841 |
842 | * `NeedsTimingInfo` global must be set true to enable `GetTimingInfo()`
843 | * `GetTimingInfo()` return the current `TimingInfo` Object
844 | * Often called during the [`ProcessMIDI()`](#processmidievent) function
845 | * Can also be called from the [`HandleMIDI()`](#handlemidievent) function
846 | * But does not appear to work in [`Idle()`](#idle)
847 |
848 | ```
849 | // required for the function GetTimingInfo()
850 | var NeedsTimingInfo = true;
851 |
852 | // Retrieve and print the TimingInfo
853 | function ProcessMIDI() {
854 | const info = GetTimingInfo();
855 | Trace(JSON.stringify(info, null, 4));
856 | }
857 |
858 | /*
859 | * Example Output
860 | {
861 | "playing": true,
862 | "blockStartBeat": 2.93541949590048,
863 | "blockEndBeat": 2.95863944689432,
864 | "blockLength": 0.023219950993848215,
865 | "tempo": 60,
866 | "meterNumerator": 4
867 | "meterDenominator": 4,
868 | "cycling": false,
869 | "leftCycleBeat": 1,
870 | "rightCycleBeat": 5,
871 | }
872 | */
873 | ```
874 |
875 | The object properties explained...
876 |
877 | TimingInfo Property | Type | Description
878 | --- | --- | ---
879 | `playing` | boolean | Value is true when Logic Pro is playing. Switches between true and false when playback starts and stops/pauses
880 | `blockStartBeat` | float | The beat position at the start of the process block
881 | `blockEndBeat` | float | The beat position at the end of this process block
882 | `blockLength` | float | Length of the process block in beats (always equals `blockEndBeat-blockStartBeat`)
883 | `tempo` | float | Tempo in beats-per-minute
884 | `meterNumerator` | integer | The host meter numerator. e.g. '3' in '3/4' time
885 | `meterDemoninator` | integer | The host meter denominator. e.g. '4' in '3/4' time
886 | `cycling` | boolean | Indicates whether the host transport is cycling
887 | `leftCycleBeat` | float | The beat position at the start of the cycle range
888 | `rightCycleBeat` | float | Beat position at the end of the cycle range
889 |
890 | #### `TimingInfo` for tempo
891 |
892 | `tempo`, `meterNumerator`, and `meterDemoninator` represent the information at the moment of the call to `GetTimingInfo()`. If your project has changing tempo or time signature then these values will change.
893 |
894 | #### `TimingInfo` and the Cycle
895 |
896 | The cycle area is used to repeatedly play a particular part of a project. In Logic, it is shown by the yellow-ish stripe above the ruler.
897 |
898 | `cycling` is true if the cycle is on / enabled.
899 |
900 | `leftCycleBeat` and `rightCycleBeat`:
901 | * provide the start and end of points of the cycle
902 | * they are measured in `beats` (e.g. 5/4 time signature has 5 beats per bar)
903 | * they are floats
904 | * the start of the first bar is `1`
905 | * they are available irrespective of whether the cycle is currently enabled or disabled
906 | * they can change dynamically if the user adjusts the cycle
907 | * changing the values does not affect the Logic settings
908 | * they are not affected by the tempo, block size, or sample rate
909 |
910 | Examples:
911 |
912 | Time Signature | Cycle Area | `leftCycleBeat` | `rightCycleBeat` | `right - left`
913 | --- | --- | --- | --- | ---
914 | 4/4 | First bar (first 4 beats of project) | 1 | 5 | 4 beats
915 | 4/4 | First eighth note of bar 1 | 1 | 1.5 | 0.5 beats
916 | 4/4 | 1.5 bars = start of bar 3 to middle of bar 4 | 9 | 15 | 6 beats
917 | 4/4 | 8 bars = start of bar 9 to end of bar 16 | 33 | 65 | 32 beats
918 | 12/8 | First bar | 1 | 13 | 12 beats
919 | 5/4 | First bar (first 5 beats of project) | 1 | 6 | 5 beats
920 | 5/4 | First 10 bars (start of bar 1 to end of bar 10) | 1 | 51 | 50 beats
921 |
922 | #### Project with changing time signatures
923 |
924 | Things are a more complex when a project contains changes in time signatures.
925 |
926 | What I expected...
927 |
928 | * Cycle area selects the first 3 bars of a project
929 | * Bar 1 is in 4/4 (4 beats)
930 | * Bar 2 is in 12/8 (12 beats)
931 | * Bar 3 is in 7/8 (7 beats)
932 | * `leftCycleBeat` is `1`
933 | * `rightCycleBeat` is `24`
934 | * total beats selected is `24-1` = `23` beats = 4 + 12 + 7
935 |
936 | What I saw...
937 |
938 | Similar to above but the beats changed according to the playhead position. When it was in a region of X/4 vs. X/8 vs. X/16 then beats changed by a factor of 2 (without changing the beats per bar). It's not clear if this is a Logic Pro feature or bug.
939 |
940 |
941 | #### `TimingInfo` for blocks
942 |
943 | The block information relates to the underlying audio system as explained for the [`ProcessMIDI()`](#processmidievent) function. It is determined by the Project's sample rate (e.g. 44.1kHz or 96kHz) plus the audio buffer size (e.g. 512 bytes). Those 2 properties are not available to the It is NOT determined by the tempo, beats, bars or other MIDI or musical data.
944 |
945 | Block data is not that easy to work with because the block duration (in seconds) is not explicitly known (though you can make good guesses using standard sample rates and buffer sizes and the block size calculation shown in [`ProcessMIDI()`](#processmidievent))
946 |
947 | Block '1.0' is indexed to the start of the project.
948 |
949 | ### `PluginParameters` Object
950 |
951 | `PluginParameters` is used to define the graphical interface for your plug-in. The script sets the value to array of objects that define each parameter to display to the user.
952 |
953 | This example shows a 3-value drop-down menu called "Intensity". The default is "1" which is "Mid" (because it is index-1 in the `valueStrings` array).
954 |
955 | ```
956 | var PluginParameters = [
957 | {
958 | name: "Intensity",
959 | type: "menu",
960 | valueStrings: ["Low", "Mid", "High"],
961 | defaultValue: 1
962 | },
963 | { param2 },
964 | ...
965 | ]
966 | ```
967 |
968 | The following is combination of documented and undocumented parameter settings.
969 |
970 | `type`: defines the type parameter widget
971 | - “lin”: Creates a linear slider
972 | - “log”: Creates a logarithmic slider
973 | - “momentary”: Creates a momentary button (one-shot trigger)
974 | - “menu”: Creates a menu. Also needs the `valueStrings` property that is an array of strings to show in the menu.
975 | - "checkbox": a true/false selector
976 | - “target”: Allows user to select a MIDI destination
977 |
978 | `defaultValue`: the integer or float number to set as default value. Default is 0.0.
979 |
980 | `minValue`: integer or float setting the minimum slider value. default is 0.0
981 |
982 | `maxValue`: integer or float setting the maximum slider value. default is 0.0
983 |
984 | `numberOfSteps`: an integer number to define the number of steps of a slider
985 |
986 | `unit`: a string to present a unit description in the plug-in controls. If no value is typed, the default behavior is to display no unit.
987 |
988 | `text`: Type text to create a divider/header in the plug-in UI.
989 |
990 |
991 | #### `PluginParameters` Functions
992 |
993 | Global functions for handling parameters...
994 |
995 | Function | Description
996 | --- | ---
997 | `ParameterChanged(paramNum, value)` | Called after any parameter change by the user. paramNum is the parameter index in `PluginParameters`
998 | `GetParameter(param-name)` | retrieves the current console value of a parameter
999 | `SetParameter(param-name, value)` | sets the console value of a parameter
1000 | `UpdatePluginParameters()` | dynamically updates the user interface
1001 |
1002 |
1003 | #### `PluginParameters` Example
1004 |
1005 | 
1006 |
1007 | ```
1008 | var PluginParameters =
1009 | [
1010 | // Text divider / header
1011 | {
1012 | name:"------ Example Plugin Parameters ------",
1013 | type:"text",
1014 | },
1015 | // Note Velocity: linear slider with a range of 1 to 127 in 126 steps. Default: 100
1016 | {
1017 | name:"Note Velocity",
1018 | type:"lin",
1019 | minValue:1,
1020 | maxValue:127,
1021 | numberOfSteps:126,
1022 | defaultValue:100
1023 | },
1024 | // Transpose: linear slider with a range of -24 to 24 in 48 steps. Defauly: 0
1025 | {
1026 | name:"Transpose",
1027 | type:'lin',
1028 | minValue:-24,
1029 | maxValue:24,
1030 | numberOfSteps:48,
1031 | unit: "semi-tones",
1032 | defaultValue: 0
1033 | },
1034 | // Checkbox that is defaulted to off (de-selected)
1035 | {
1036 | name:"Enable",
1037 | type:"checkbox",
1038 | defaultValue: 0
1039 | },
1040 | // Menu with 2 items is presented as radio buttons
1041 | {
1042 | name:"Radio",
1043 | type:"menu",
1044 | valueStrings:["Opt 1", "Opt 2"],
1045 | // default is the index in valueStrings array
1046 | defaultValue: 1
1047 | },
1048 | // Menu with 3 or more menu is presented as drop-down menu
1049 | {
1050 | name:"Range",
1051 | type:"menu",
1052 | valueStrings:["Low", "Mid", "High"],
1053 | // default is the index in valueStrings array - Mid
1054 | defaultValue: 1
1055 | },
1056 | // MIDI Target selector
1057 | {
1058 | name: "Target",
1059 | type: "target",
1060 | },
1061 | // Momentary trigger button
1062 | {
1063 | name: "Trigger",
1064 | type: "momentary",
1065 | }
1066 | ];
1067 |
1068 | function ParameterChanged(param, value) {
1069 | // param is index in PluginParameters array
1070 | Trace(`"${PluginParameters[param].name}" changed to ${value}`);
1071 | }
1072 | ```
1073 |
1074 |
1075 | #### Unicode
1076 |
1077 | [Unicode](https://home.unicode.org/) characters can be used in your parameters - many but not all.
1078 |
1079 | Only a handful of Unicode's [musical symbols](https://unicode-table.com/en/blocks/musical-symbols/) worked in Logic 10.6.2 (it would be handy to have more).
1080 |
1081 | ```
1082 | {
1083 | name:"𝄆 Codas 𝄇",
1084 | name:"Cleffs 𝄞 𝄢",
1085 | }
1086 | ```
1087 |
1088 |
1089 | I had more luck with [Emojis](https://unicode-table.com/en/sets/top-emoji/) to spice up parameters.
1090 |
1091 | ```
1092 | {
1093 | name:"Smiley 😁",
1094 | name:"Clap 🙌",
1095 | }
1096 | ```
1097 |
1098 |
1099 | #### `GetParameter()` Function
1100 |
1101 | TODO - expand and add example
1102 | Returns a given parameter’s current value
1103 |
1104 | #### `UpdatePluginParameters()` Function
1105 |
1106 | TODO - expand and add example
1107 | dynamically updates the user interface
1108 |
1109 | #### `GetParameter()` Function
1110 |
1111 | TODO - expand and add example
1112 | retrieves the current console value of a parameter
1113 |
1114 | #### `SetParameter()` Function
1115 |
1116 | TODO - expand and add example
1117 | sets the console value of a parameter
1118 |
1119 |
1120 | ### `MIDI` Object
1121 |
1122 | The `MIDI` object is a global provided by Scripter with utility functions for working with MIDI objects.
1123 |
1124 | In Logic Pro 10.15.X, the MIDI object is defined here...
1125 |
1126 | ```
1127 | /Applications/Logic Pro X.app/Contents/Frameworks/MADSP.framework/Versions/A/Resources/MIDIClass.js
1128 | ```
1129 |
1130 | `MIDI` Function | Description
1131 | --- | ---
1132 | `ccName(num)` | Convert a MIDI control channel number to it's name (string). e.g. `MIDI.ccName(2)` returns `"Breath"`
1133 | `allNotesOff()` | What it says :-)
1134 | `normalizeStatus(num)` | Ensures that a MIDI status is an integer in the "normal" range of 128 to 239 (inclusive). Default return is 128. Example: `MIDI.normalizeStatus(213)` returns 213 but `MIDI.normalizeStatus(248)` returns 239. See the [Expanded MIDI 1.0 Messages List (Status Bytes)](https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes)
1135 | `normalizeChannel(channel)` | Normalises the channel to be an integer from 1 to 16. Default is 1. e.g. `MIDI.normalizeChannel(3)` returns 3
1136 | `normalizeData(num)` | Normalises the data value to be an integer from 0 to 127. Default is 1. e.g. `MIDI.normalizeData(48)` returns 48
1137 | `noteNumber(str)` | Converts a note name string to the MIDI number. e.g. `MIDI.noteNumber("C#1")` returns 37. Returns null if the parameter is not a valid note number.
1138 | `noteName(num)` | Converts a MIDI note number to the name (string). e.g. `MIDI.noteName(37)` returns `"C#1"`. Returns null if the parameter is not a valid note number
1139 |
1140 | The `MIDI` object also presents the following internal utilities. It's recommend that you use the corresponding public functions above.
1141 |
1142 |
1143 | `MIDI` Property | Description
1144 | --- | ---
1145 | `_noteNames` | Use `noteName()`. Array of 128 MIDI notes names. `[C-2,C#-2,D-2,...,F8,F#8,G8]` with `MIDI._noteNames[37] = "C#1"`
1146 | `_makeNoteNames` | Utility that creates the `_noteNames` array
1147 | `_ccNames` | Array of 128 MIDI Control Change names. `['Bank MSB', 'Modulation','Breath', ... 'Poly Mode On']`
1148 | `_sendEventOnAllChannels` | Use the public functions
1149 |
1150 |
1151 |
1152 | ## Appendix 1 - Native Functions
1153 |
1154 | These functions are implemented natively by Scripter and available for JavaScript. You could call them directly, but it's preferrable to use the public JavaScript functions documented above.
1155 |
1156 | This is not a complete list.
1157 |
1158 | - `SendMIDIEventNow` - used by `event.send()`
1159 | - `SendMIDIEventAfterMilliseconds` - used by `event.sendAfterMilliseconds()`
1160 | - `SendMIDIEventAtBeat` - used by `event.sendAtBeat()`
1161 | - `SendMIDIEventAfterBeats` - used by `event.sendAfterBeats()`
1162 |
1163 |
1164 | ## Appendix 2 - Real Time??
1165 |
1166 |
1167 | From @Dewdman42 on https://www.logicprohelp.com/forum/viewtopic.php?t=132827&start=20
1168 |
1169 | ProcessMIDI() gets called as javascript code to execute ahead of time. LPX allows plugins and itself to operate on a buffer full of data in whatever way they need to; ahead of time. Midi events are not actually "sent" when you call event.send(). They are "scheduled". There is this period of time where LPX and plugins are operating on audio data in the buffer and basically churning it and modifying the audio, taking into account midi events that are scheduled there to have software instruments use those midi events to modify the audio buffer, etc.. Finally when its time for that buffer to be played, then the buffer, along with any midi events that need to be sent externally, will be sent out the hardware interfaces. ProcessMIDI() is how you can schedule midi events to be processed during that audio buffer process block. How far ahead of time will this javascript get called to schedule the midi events? We don't know.
1170 |
1171 | ## Appendix 3 - TODO
1172 |
1173 | This is a work-in-progress. In addition to TODO notes through this document...
1174 |
1175 | 1. Resolve: Fader Event appears to be mythical (and sometimes confused online with a Fader PluginParameter). Confirm and clean up
1176 | 2. If Fader Event doesn't exist, then how to create a MIDI fader event
1177 | 3. Get proper documentation for TargetEvent
1178 | 4. Does old EventTypes.js need a mention (it's not visible in Logic Pro X 10.6+)
1179 | 5. Write up `beatPos`
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1193 |
--------------------------------------------------------------------------------