ruppel.io / Type Definitions for Logic Pro's Scripter


Type Definitions for Logic Pro's Scripter

Last edited May 13, 2024

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.

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:

Autocomplete
Autocomplete for all Event types
Type defs for parameters
Type definitions for plugin parameters

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 like PluginParameters. const and let 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.

#