├── .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 | ![Logic Pro Scripter Guide](images/icon.png) 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 | ![Chain MIDI FX](images/chain-fx.png) 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 | ![Instrument Channel](images/instrument-channel.png) 75 | 76 | [2] On the channel strip, click to add a MIDI FX. 77 | 78 | ![Channel Strip](images/channel-strip.png) 79 | 80 | [3] Add "Scripter" as a MIDI FX 81 | 82 | ![MIDI FX Options](images/midifx-options.png) 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 | ![Scripter Plugin Window](images/scripter-plugin-window.png) 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 | ![Scripter Menu](images/scripter-menu.png) 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 | ![Tutorial Scripts](images/tutorial-scripts.png) 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 | ![Factory Scripts](images/factory-scripts.png) 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 | ![Script Editor](images/scripter-dev-window.png) 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 | ![PluginParameters example](images/PluginParameters-example.png) 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 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 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