Logic Pro's Scripter is a super powerful tool for manipulating MIDI, but the docs are... not great. TypeScript can fix that.
There are offical docs for Logic Pro's Scripter. I won't link to them here, not because the link will eventually 404, but because I find them intensely unhelpful. Add this to the fact that authoring scripts in the Scripter editor is downright painful. We can do better.
The goal: A scalable build process for Logic scripts.
- I want to keep my scripts in a repository so I can keep them in version control.
- I want some sort of reassurance that I'll be able to modify my scripts in a year without fear of breaking them.
- Finally, I want to share my scripts with others.
Types help code scale. The TypeScript compiler can tell if you if your code suddenly no longer conforms to the type contracts you put in place. This is ideal for when you're revisiting a script you wrote months/years earlier. As a bonus, Once we have types in place, we can use typedoc to generate API documentation we can use instead of the offical docs.
As for portability, Scripter runs the version of JavaScriptCore on your
system. Since I want to share my scripts with people who may be running older
systems, I want to transpile my scripts as far down as possible. tsc
is
great at this.
Type Definitions
#Because all of the Scripter types are globals, we can declare
them as such:
declare function GetParameter(name: string): number
Similarly, we can define the ambient classes Logic uses for Events
declare class NoteOn {
send(): void
trace(): void
}
Once we have all of our types defined, here's what we can do:
The types described above have been published to npm: https://www.npmjs.com/package/logic-pro-types
Setting Up a Script Library
#This tutorial assumes this directory structure:
.
├── dist
├── package.json
├── src
│ ├── clap.ts
│ └── doubletime.ts
└── tsconfig.json
Create a package.json:
Install typescript
, logic-pro-types
as devDependencies.
npm init -y
npm install typescript logic-pro-types --save-dev
Create a tsconfig.json
with the following conetents:
{
"compilerOptions": {
"outDir": "dist",
"target": "ES5",
"skipLibCheck": true
},
"include": ["src/*.ts"]
}
💡 It's important to target ES5 because Scripter looks specifically for
var
statements for global variables likePluginParameters
.const
andlet
won't work.
tsc
can compile multiple scripts at once, but unfortunately we can't use this
for our scripts. When a TypeScript project contains multiple files, the only way
to avoid global naming conflicts (HandleMIDI
, PluginParameters
) is to make
each of our scripts a module. A module imports or exports something, and when
tsc
builds a module it preserves those imports or exports. When downleveling,
tsc
will also stamp a proprty on exports
for ESM compatibility. Scripter
doesn't know anything about import
/export
or require
/module.exports
, so
we need to make sure nothing of the sort shows up in our output.
The easiest solution is to introduce a build tool. I prefer esbuild for its speed, ease of use, and excellent command line interface.
Install and save esbuild as a dependency with npm install esbuild --save-dev
Next, hange the build script in your package.json
. This command tells esbuild
to build everything in your source directory and put it in dist/
{
"scripts": {
"build": "esbuild src/*.ts --outdir=dist"
}
}
And add the following to your tsconfig.json
. This tells tsc
to assume each
file is a module, which should make your editor integration happy.
{
"compilerOptions": {
"moduleDetection": "force"
}
}
💡 "moduleDetection": "force" tells
tsc
to treat every file as a module without having to import or export anything.
Now simply run npm run build
and copy your scripts from the dist
directory.