├── .gitignore
├── LICENSE
├── README.md
├── images
├── PluginParameters-example.png
├── chain-fx.png
├── channel-strip.png
├── copy.svg
├── factory-scripts.png
├── icon.png
├── icon.pptx
├── instrument-channel.png
├── midifx-options.png
├── og-logic-pro-scripter.svg
├── scripter-dev-window.png
├── scripter-menu.png
├── scripter-plugin-window.png
└── tutorial-scripts.png
├── scripter
├── PluginParameters-example.js
├── console.log.js
├── get-timing-info.js
├── processmidi-timing.js
└── trace.js
└── scripts
└── clipboard.min.js
/.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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Logic Pro Scripter Guide
4 | description: This guide is intended for developers who want to use their dev knowledge to do good stuff with Scripter for MIDI in Logic Pro. It assumes some knowledge of both Logic Pro and JavaScript. I maintain it because I found Apple’s Scripter documentation both incomplete and somewhat difficult to use.
5 | icon: images/icon.png
6 | image: /projects/logic-pro-scripter/images/og-logic-pro-scripter.svg
7 | gitrepo: https://github.com/musios-app/logic-pro-scripter
8 | tags: logic-pro-x javascript scripting guide
9 | ---
10 |
11 | 
12 |
13 | View the latest [Logic Pro Scripter Guide](https://andrewjhunt.github.io/logic-pro-scripter/)
14 |
15 | This guide is intended for developers who want to use their dev knowledge to do good stuff with Scripter for MIDI in Logic Pro. It assumes some knowledge of both Logic Pro and JavaScript. I maintain it because I found [Apple's Scripter documentation](https://support.apple.com/en-au/guide/logicpro/lgce728c68f6/mac) both incomplete and somewhat difficult to use.
16 |
17 | ## Contents
18 |
19 | * TOC
20 | {:toc}
21 |
22 | ## Background
23 |
24 | The Scripter plug-in provides an interface between JavaScript code and the MIDI data of a Logic Pro channel. It allows users to create scripts that:
25 |
26 | * Generate MIDI: like notes for chords, arpeggios. Or controls for modulation and effects.
27 | * Transform MIDI: transpose notes, modify timing and rhythm
28 | * Inject automation
29 | * Control anything that MIDI can
30 |
31 | Combining multiple MIDI FX plugins can be particularly powerful:
32 |
33 | * Scripter can be chained with other MIDI FX plugins
34 | * Multiple Scripters can be chained in together
35 |
36 | For example, this chain combines 5 MIDI FX That
37 |
38 | 1. Convert single notes to Chords with the Chord Trigger plugin
39 | 2. Use the MIDI Arpeggiator to play those chords as argeggios
40 | 3. Use Transposer to change pitch
41 | 4. Use a custom Scripter to play delayed echo notes
42 | 5. Use a custom Scripter to duplicate quieter notes up an octave
43 |
44 | Even more power can be achieved by managing MIDI flows with the MIDI Environment.
45 |
46 | 
47 |
48 | ### Doc Status
49 |
50 | This documentation are updated for
51 | * Logic Pro 10.6.2 (May 2021)
52 |
53 | The scripts and tests were run on
54 | * MacOS Catalina 10.15.7
55 | * MacBook Pro (13-inch, 2020) with 2 GHz Quad-Core Intel Core i5 and 16GB memory
56 |
57 | ### Apple's Scripter Documentation
58 |
59 | Can you start with Apple's doc for the [Logic Pro Scripter MIDI plug-in](https://support.apple.com/en-au/guide/logicpro/lgce728c68f6/mac) covering all the core topics you'll need. Integrated into that documentation is a set of scripts that you'll find packaged with Logic Pro Scripter under it's Factory / Tutorial Scripts. The Factory scripts also reveal a lot about Scripter that's not written in the documentation.
60 |
61 |
62 | ### Other Guides and Resources
63 |
64 | [An Introduction to Scripting in Logic X by Will Walker](https://music.tutsplus.com/tutorials/an-introduction-to-scripting-in-logic-x--cms-23920)
65 |
66 | [Collection of LogicScripts by JBramauer](https://github.com/JBramauer/LogicScripts)
67 |
68 | ### Getting Started
69 |
70 | Script is a MIDI plug-in. Here's the quick steps to get scripter going (but there's much better doc elsewhere).
71 |
72 | [1] In your Logic project, add a new channel and select "Software Instrument" which looks something like...
73 |
74 | 
75 |
76 | [2] On the channel strip, click to add a MIDI FX.
77 |
78 | 
79 |
80 | [3] Add "Scripter" as a MIDI FX
81 |
82 | 
83 |
84 | [5] Scripter presents as a standard Logic plug-in with a control panel.
85 |
86 | 1. Menu items described below
87 | 2. Panel for "Global Parmaeters" which are populated from the [`PluginParameters` object](#pluginparameters-object)
88 | 3. "Open Script in Editor" button to open the [Script Editor window](#script-editor-window)
89 |
90 | 
91 |
92 | The Scripter menu (shown above as "Factory Default") provides
93 |
94 | 1. Standard plug-in options like Undo, Redo and Next/Previous plugin
95 | 2. A set of file comments for loading, savings and other management of your Scripts
96 | 3. A list of the existing saved user scripts (see [Files and Directories](#files-and-directories))
97 | 4. Factory menu for Apple's standard scripts and Tutorial scripts
98 |
99 | 
100 |
101 |
102 | ### Apple's Tutorial Scripts
103 |
104 | Scripter comes with a set of Tutorial Scripts that show the basic functions of Script in action. These are a good to get started.
105 |
106 | 
107 |
108 | ### Apple's Factory Scripts
109 |
110 | Logic Pro is bundled a set of Factory scripts that show the power of Scripter to do very useful MIDI treatment.
111 |
112 | These scripts are also very useful for understanding how to create your own scripts. To review the Scripter code, select them from the Scripter plug-in menu and click "Open Script in Editor".
113 |
114 | 
115 |
116 |
117 | #### Script Editor Window
118 |
119 | The Script Editor window is opened by click "Open Script in Editor" on the main plugin window.
120 |
121 | The Script Editor has two panels:
122 |
123 | 1. Top panel contains JavaScript with useful capabilities like syntax highlighting.
124 | 2. The lower panel is the Console which shows the output of the script. It's much like the console in any web browser.
125 |
126 | A new Scripter will contain a basic script that (a) prints each MIDI event to the console with `event.trace()` (which prints a summary of the event using `Trace()`), and (b) passes through the event with `event.send()`.
127 |
128 | The basic script development process is:
129 | 1. Load and/or Edit a script
130 | 2. Click the "Run Script" button at the top of Script Editor to activate your script (or get a bug report)
131 | 3. Monitor the script in the console. Often you'll need to start playing your track for the most interesting stuff.
132 |
133 | 
134 |
135 |
136 | ### Files and Directories
137 |
138 | Scripter plug-in files are stored as `.pst` files which are Apple Logic plugin settings files. These files contain your scripts, settings and other info (I'm not sure what else Logic puts in these proprietary binary files).
139 |
140 | User script directory. This is the default directory when you Load and Save scripts from the plug-in window.
141 |
142 | > `~/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 |
--------------------------------------------------------------------------------
/images/PluginParameters-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/PluginParameters-example.png
--------------------------------------------------------------------------------
/images/chain-fx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/chain-fx.png
--------------------------------------------------------------------------------
/images/channel-strip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/channel-strip.png
--------------------------------------------------------------------------------
/images/copy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
46 |
--------------------------------------------------------------------------------
/images/factory-scripts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/factory-scripts.png
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/icon.png
--------------------------------------------------------------------------------
/images/icon.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/icon.pptx
--------------------------------------------------------------------------------
/images/instrument-channel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/instrument-channel.png
--------------------------------------------------------------------------------
/images/midifx-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/midifx-options.png
--------------------------------------------------------------------------------
/images/scripter-dev-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/scripter-dev-window.png
--------------------------------------------------------------------------------
/images/scripter-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/scripter-menu.png
--------------------------------------------------------------------------------
/images/scripter-plugin-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/scripter-plugin-window.png
--------------------------------------------------------------------------------
/images/tutorial-scripts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musios-app/logic-pro-scripter/9b270708603bb2b879bef50741aa925e6c49ab5c/images/tutorial-scripts.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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