Andrew Hunt
View the latest Logic Pro Scripter Guide
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.
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:
Combining multiple MIDI FX plugins can be particularly powerful:
For example, this chain combines 5 MIDI FX That
Even more power can be achieved by managing MIDI flows with the MIDI Environment.
This documentation are updated for
The scripts and tests were run on
Can you start with Apple’s doc for the Logic Pro Scripter MIDI plug-in 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.
An Introduction to Scripting in Logic X by Will Walker
Collection of LogicScripts by JBramauer
Script is a MIDI plug-in. Here’s the quick steps to get scripter going (but there’s much better doc elsewhere).
[1] In your Logic project, add a new channel and select “Software Instrument” which looks something like…
[2] On the channel strip, click to add a MIDI FX.
[3] Add “Scripter” as a MIDI FX
[5] Scripter presents as a standard Logic plug-in with a control panel.
PluginParameters
objectThe Scripter menu (shown above as “Factory Default”) provides
Scripter comes with a set of Tutorial Scripts that show the basic functions of Script in action. These are a good to get started.
Logic Pro is bundled a set of Factory scripts that show the power of Scripter to do very useful MIDI treatment.
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”.
The Script Editor window is opened by click “Open Script in Editor” on the main plugin window.
The Script Editor has two panels:
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()
.
The basic script development process is:
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).
User script directory. This is the default directory when you Load and Save scripts from the plug-in window.
~/Music/Audio Music Apps/Plug-In Settings/Scripter
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.
/Applications/Logic Pro X.app/Contents/Resources/Plug-In Settings/Scripter
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.
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.
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)
Scripter requires all the code to be in a single script (no import
or require
of your favourites libraries and modules).
Scripter makes most of the standard set of JavaScript features you know and love are available like…
Class
Date
JSON
Math
Number
RegExp
ES6 in Scripter gives us lots of nice features compared to older JavaScript variants.
const square = (num) => num * num;
let
and const
declarations for best practiceconst square = (num=1) => num * num;
function sum(...args) { code }
for/of
loops: for (variable of iterable) { ... }
There are some features that JS developers take for granted in a browser or node.js that are not available in Logic’s Script.
require
or import
for neat packaging of your code or re-using existing code librariessetTimeout
for delayed operators - see instead the Event timing methods and the ProcessMIDI() callbackalert
console
- use Trace
insteadYou can implementation a range of useful input controls with the PluginParameters
.
Unfortunately, there is no way to get text input from a user unless your user is prepared to edit a script.
But then, Scripter is a simpler development environment than for creation of full plug-ins so expect some limitations.
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).
Some easy ways to crash Logic with Scripter…
Trace()
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.)
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.
Scripter is a more compact and limited environment than either the browser or node.js.
Name | Description |
---|---|
NeedsTimingInfo |
Boolean. Set to true to enable the GetTimingInfo() function for access to current TimingInfo properties |
ResetParameterDefaults |
Boolean. Sets UI controls to default values. NOTE: documentation on this is limited and I can’t figure out how to use it. |
MIDI |
Object. Provides a set of utility functions for working with MIDI objects. |
The following are the Scripter functions that integrate into the Logic Pro MIDI environment.
Feature | Description |
---|---|
HandleMIDI(event) |
Called with each MIDI event on the channel that is received by the plug-in |
ProcessMIDI(event) |
Called periodically for regular tasks like sequencing and tempo-based effects |
ParameterChanged(paramNum, value) |
Called after any parameter change by the user |
GetParameter(string) |
Returns a given parameter’s current value |
UpdatePluginParameters() |
dynamically updates the user interface |
GetParameter(param-name) |
retrieves the current console value of a parameter |
SetParameter(param-name, value) |
sets the console value of a parameter |
GetTimingInfo() |
Retrieves a TimingInfo object, which contains timing information that describes the state of the host transport and the current musical tempo and meter |
Trace(obj) |
Prints obj to the console. Only a single parameter is supported |
Reset() |
Called when (a) bypass the Scripter plug-in, or (b) transport is started. No parameters |
Idle() |
Called during idle times when it won’t get in the way of HandleMIDI() and ProcessMIDI(). Usually a few times per second. TODO - expand |
HandleMIDI(event)
This function is called for every MIDI event that is received by the plug-in on the channel strip. The parameter is an Event
object (described below in detail) but can be any of:
HandleMIDI()
allows the processing MIDI events:
It is optional to implement this function. If omitted, Logic will pass through the event unmodified.
Examples:
// Pass MIDI events through the plug-in with modification
function HandleMIDI(event) {
event.send();
}
// Pass MIDI events through the plug-in with modification
function HandleMIDI(event) {
event.send();
event.trace();
}
/*
Sample Output
[NoteOn channel:1 pitch:44 [G#1] velocity:91]
[NoteOff channel:1 pitch:44 [G#1] velocity:64]
[NoteOn channel:1 pitch:44 [G#1] velocity:91]
[NoteOn channel:1 pitch:36 [C1] velocity:91]
*/
Note: event.trace()
is an alternative to Trace(event)
;
ProcessMIDI(event)
ProcessMIDI()
enables execution of regular / periodic tasks. Examples include:
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.
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).
blockDuration = blockLength / sampleRate
e.g.
blockLength = 512 samples
sampleRate = 44,100Hz (44.1kHz)
blockDuration => 0.01161sec = 11.61msec
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.
Sample Rate (Hz) | Buffer Size (Bytes) | Block size (msec) | Block Freq (Hz) |
---|---|---|---|
44,100Hz | 32 | 0.73ms | 1378.125Hz |
44,100Hz | 64 | 1.45ms | 689.0625Hz |
44,100Hz | 128 | 2.9ms | 344.5313Hz |
44,100Hz | 256 | 5.8ms | 172.2656Hz |
44,100Hz | 512 | 11.61ms | 86.1328Hz |
44,100Hz | 1024 | 23.22ms | 43.0664Hz |
48,000Hz | 32 | 0.67ms | 1500Hz |
48,000Hz | 64 | 1.33ms | 750Hz |
48,000Hz | 128 | 2.67ms | 375Hz |
48,000Hz | 256 | 5.33ms | 187.5Hz |
48,000Hz | 512 | 10.67ms | 93.75Hz |
48,000Hz | 1024 | 21.33ms | 46.875Hz |
88,200Hz | 32 | 0.36ms | 2756.25Hz |
88,200Hz | 64 | 0.73ms | 1378.125Hz |
88,200Hz | 128 | 1.45ms | 689.0625Hz |
88,200Hz | 256 | 2.9ms | 344.5313Hz |
88,200Hz | 512 | 5.8ms | 172.2656Hz |
88,200Hz | 1024 | 11.61ms | 86.1328Hz |
96,000Hz | 32 | 0.33ms | 3000Hz |
96,000Hz | 64 | 0.67ms | 1500Hz |
96,000Hz | 128 | 1.33ms | 750Hz |
96,000Hz | 256 | 2.67ms | 375Hz |
96,000Hz | 512 | 5.33ms | 187.5Hz |
96,000Hz | 1024 | 10.67ms | 93.75Hz |
176,400Hz | 32 | 0.18ms | 5512.5Hz |
176,400Hz | 64 | 0.36ms | 2756.25Hz |
176,400Hz | 128 | 0.73ms | 1378.125Hz |
176,400Hz | 256 | 1.45ms | 689.0625Hz |
176,400Hz | 512 | 2.9ms | 344.5313Hz |
176,400Hz | 1024 | 5.8ms | 172.2656Hz |
192,000Hz | 32 | 0.17ms | 6000Hz |
192,000Hz | 64 | 0.33ms | 3000Hz |
192,000Hz | 128 | 0.67ms | 1500Hz |
192,000Hz | 256 | 1.33ms | 750Hz |
192,000Hz | 512 | 2.67ms | 375Hz |
192,000Hz | 1024 | 5.33ms | 187.5Hz |
ProcessMIDI()
is not required.
ProcessMIDI()
has no arguments.
ProcessMIDI()
is often used in combination with the TimingInfo
object which provides timing information from Logic.
Caution: if you print much information on each call to ProcessMIDI()
it can cause trimming and you won’t see all the messages.
This example allows you to determine the interval between calls to ProcessMIDI()
. See scripts/processmidi-timing.js
/*
* ProcessMIDI example that measures the internal
*/
let lastTime = null;
function ProcessMIDI() {
const now = new Date().getTime();
if (lastTime) Trace(`${now - lastTime}ms`);
lastTime = now;
}
/*
* Sample output with sample rate of 44.1kHz and buffer of 1024 samples
23ms
24ms
23ms
23ms
23ms
*/
Trace(obj)
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.
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.
Some things to note:
JSON.stringify()
is available and usefulTrace()
without a parameter does nothing. Use Trace("\n")
for a blank line.Trace()
Trimming may prevent display of some of your messages. More detail below.
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.
Sometimes it appears that to much Trace()
can cause Logic Pro to crash.
Trace()
for Javascript typesAll the standard JS types can be passed to Trace()
. See scripts/trace.js
/* Trace() examples */
// Primitives: string, number, boolean
Trace('Hello World!');
// Objects
const obj = {num: 1.2, str: 'howdy', boolean: true, arr: [5,6,7]};
Trace(obj);
Trace(JSON.stringify(obj));
Trace(JSON.stringify(obj, null, 4));
// Force some trimming
for (let i=1; i<=1000; i++) Trace("number " + i);
Output:
Hello World!
number 1
number 2
number 3
[object Object]
{"num":1.2,"str":"howdy","boolean":true,"arr":[5,6,7]}
{
"num": 1.2,
"str": "howdy",
"boolean": true,
"arr": [
5,
6,
7
]
}
>
Logic Pro is a time-sensitive environment. If Scripter gets too many trace requests it will trim them for performance reasons – to avoid latency.
You will be annoyed.
...console bandwidth exceeded, thinning some traces...
NoteOn
...console bandwidth exceeded, thinning some traces...
Dewdman42
posted a handy work-around that defers the console work to the Idle()
function. Conveniently, it’s named console.log
so that you can feel at home.
TODO: it would be good to allow printing of multiple arguments as in console.log.
var console = {
maxFlush: 20,
b:[],
log: function(msg) {this.b.push(msg)},
flush: function() {
var i=0;
while(i<=this.maxFlush && this.b.length>0) {
Trace(this.b.shift());
i++;
}
}
};
function Idle() {
console.flush();
}
// Intead of Trace(msg) use this:
console.log("Hello World");
// Instead of event.trace() use this:
console.log(event.toString());
Idle()
This feature is not documented by Apple but used in it’s examples.
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.
So Logic offers the Idle()
function for housekeeping duties that might take a little longer like…
console.log()
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).
Event
ObjectRead Apple’s documentation on the Event Object
The Event
object is a prototype for the various standard MIDI event types. All the following sub-classes share common properties and Event Methods. However, each Event
type has properties that are specific to it’s MIDI definition.
Class | Description |
---|---|
Note |
Parent class of NoteOn and NoteOff |
NoteOn |
Represents a note on event |
NoteOff |
Represents a note off event |
PolyPressure |
A polyphonic aftertouch event |
ControlChange |
A MIDI control change event |
ProgramChange |
Represents a MIDI program change event |
ChannelPressure |
Represents a MIDI channel pressure event |
PitchBend |
Represents a MIDI pitch bend event |
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.
Event Method | Description |
---|---|
Event.send() |
Send the event immediately |
Event.sendAfterMilliseconds(delay-in-msec) |
Send the event after a specific delay (can be an integer or a floating point number) |
Event.sendAtBeat(beat) |
Send the event at a specific beat in the host timeline. The beat is a floating-point number. |
Event.sendAfterBeats(number beat) |
Send the event after a delay measure in beats. |
Event.trace() |
Print the event to the console using the Trace() object |
Event.toString() |
Returns selected information about the event as a string. |
Event.toarticulationID(integer number) |
Sets the articulation ID from 0–254 |
Event.channel(number) |
Set MIDI channel 1 to 16 |
Event.beatPos |
Retrieves the event’s exact beat position |
Event
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.
This example creates then sends ControlChange
event.
{
var cc = new ControlChange;
// set controller numer 1 = modulation
cc.number = 1;
// set the ControlChange value
cc.value = 42;
// Send it!
cc.send();
// Print it to the console
cc.trace();
}
/*
* Expected output...
[ControlChange channel:1 number:1 [Modulation] value:42]
*/
Clone an existing event by passing that event as the constructor parameter.
const newNoteOn = new NoteOn(existingNoteOn);
The cloned Event
can then be modified.
{
const cc1 = new ControlChange;
cc1.number = 1;
cc1.value = 42;
const cc2 = new ControlChange(cc1);
cc2.value++;
cc1.trace();
cc2.trace();
}
/*
* Expected output
[ControlChange channel:1 number:1 [Modulation] value:42]
[ControlChange channel:1 number:1 [Modulation] value:43]
*/
The properties of any event can be modified before the send()
.
This example transposes the pitch of every Note
event (NoteOn
and NoteOff
) and passes through any other events without modification.
function HandleMIDI(event) {
if (event instanceof Note) {
event.pitch += 12;
event.send();
} else {
event.send();
}
}
TODO…
Here’s a JSON dump of the object contents…
function HandleMIDI(event) {
if (event instanceof NoteOn) {
Trace(JSON.stringify(event, null, 4));
}
}
/*
* Example output
{
"detune":0,
"pitch":36,
"velocity":91,
"status":144,
"isRealtime":false,
"data1":36,
"data3":0,
"data2":91,
"channel":1,
"port":1,
"articulationID":0,
"beatPos":0
}
*/
NoteOn
EventHas all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
pitch | MIDI value from 0 to 127 |
velocity | MIDI value from 0 to 127. A velocity value of 0 is interpreted as a note off event, not a note on. |
articulationID | ?? |
inStartFrame | ?? |
isRealtime | ?? |
NoteOff
EventHas all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
pitch | MIDI value from 0 to 127 |
velocity | MIDI value from 0 to 127 |
articulationID | ?? |
inStartFrame | ?? |
isRealtime | ?? |
PolyPressure
EventA polyphonic aftertouch event. It has all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
pitch | MIDI value from 0 to 127 |
value | MIDI value from 0 to 127 providing the pressure level |
inStartFrame | ?? |
isRealtime | ?? |
ControlChange
EventA MIDI control change event. It has all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
number | MIDI Controller value from 0 to 127. TODO |
value | MIDI Controller value from 0 to 127. TODO |
inStartFrame | ?? |
isRealtime | ?? |
ProgramChange
EventsRepresents a MIDI program change event. It has all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
number | MIDI Program change number from 0 to 127. |
inStartFrame | ?? |
isRealtime | ?? |
ChannelPressure
EventRepresents a MIDI channel pressure event for after-touch. It has all the default Event
methods plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
value | MIDI channel pressure value from 0 to 127. |
inStartFrame | ?? |
isRealtime | ?? |
PitchBend
EventRepresents 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 plus the following properties:
Property | Description |
---|---|
channel | MIDI value from 1 to 16 |
value | MIDI channel pressure value from -8192 to 8191 |
inStartFrame | ?? |
isRealtime | ?? |
TargetEvent
EventsRepresents a user-defined MIDI ControlChannel message or can control plug-in parameters that a user selects through a target
interface from the PluginParameters
object.
In this example:
PluginParameters
of type target
called “Select Target”.-1
.ParameterChanged()
function is a debug utility to print the details of any change of the target made byt the user through the Plugin control. "Select Target" set to "Foot Control" [value=4]
MIDI.ccName()
utility converts the MIDI value (0-127) to a human-readable format. e.g. 4
is “Foot Control”XXX
var PluginParameters = [
{
name: "Select Target",
type: "target"
}
];
// Pretty-print each time the user changes a parameter (debug)
function ParameterChanged(paramNum, value) {
const paramName = PluginParameters[paramNum].name;
const targetName = MIDI.ccName(value)
Trace(`"${paramName}" set to "${targetName}" [value=${value}]`)
}
“Select Target” set to “Foot Control” [value=4]
You can create user-definable MIDI CC messages, or you can control plug-in parameters. TargetEvent reads the parameter to be modified from a menu where the user can select a destination MIDI CC. 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. The chosen destination is saved with the plug-in setting.
TargetEvent.value(float): Sets the target value.
TODO
Event
- Under the CoversThere 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.
function HandleMIDI(event) {
event.send();
event.trace();
Trace(JSON.stringify(event, null, 4))
}
/*
* Output from a NoteOn event
[NoteOn channel:1 pitch:44 [G#1] velocity:91]
{
"detune": 0,
"pitch": 44,
"velocity": 91,
"status": 144,
"isRealtime": false,
"data1": 44,
"data3": 0,
"data2": 91,
"channel": 1,
"port": 1,
"articulationID": 0,
"beatPos": 0
}
*/
TimingInfo
ObjectRef: Apple’s doc for JavaScript TimingInfo object
TimingInfo
returns the current timing information of Logic Pro plus the current musical tempo and meter. Important:
NeedsTimingInfo
global must be set true to enable GetTimingInfo()
GetTimingInfo()
return the current TimingInfo
ObjectProcessMIDI()
functionHandleMIDI()
functionIdle()
// required for the function GetTimingInfo()
var NeedsTimingInfo = true;
// Retrieve and print the TimingInfo
function ProcessMIDI() {
const info = GetTimingInfo();
Trace(JSON.stringify(info, null, 4));
}
/*
* Example Output
{
"playing": true,
"blockStartBeat": 2.93541949590048,
"blockEndBeat": 2.95863944689432,
"blockLength": 0.023219950993848215,
"tempo": 60,
"meterNumerator": 4
"meterDenominator": 4,
"cycling": false,
"leftCycleBeat": 1,
"rightCycleBeat": 5,
}
*/
The object properties explained…
TimingInfo Property | Type | Description |
---|---|---|
playing |
boolean | Value is true when Logic Pro is playing. Switches between true and false when playback starts and stops/pauses |
blockStartBeat |
float | The beat position at the start of the process block |
blockEndBeat |
float | The beat position at the end of this process block |
blockLength |
float | Length of the process block in beats (always equals blockEndBeat-blockStartBeat ) |
tempo |
float | Tempo in beats-per-minute |
meterNumerator |
integer | The host meter numerator. e.g. ‘3’ in ‘3/4’ time |
meterDemoninator |
integer | The host meter denominator. e.g. ‘4’ in ‘3/4’ time |
cycling |
boolean | Indicates whether the host transport is cycling |
leftCycleBeat |
float | The beat position at the start of the cycle range |
rightCycleBeat |
float | Beat position at the end of the cycle range |
TimingInfo
for tempotempo
, 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.
TimingInfo
and the CycleThe 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.
cycling
is true if the cycle is on / enabled.
leftCycleBeat
and rightCycleBeat
:
beats
(e.g. 5/4 time signature has 5 beats per bar)1
Examples:
Time Signature | Cycle Area | leftCycleBeat |
rightCycleBeat |
right - left |
---|---|---|---|---|
4/4 | First bar (first 4 beats of project) | 1 | 5 | 4 beats |
4/4 | First eighth note of bar 1 | 1 | 1.5 | 0.5 beats |
4/4 | 1.5 bars = start of bar 3 to middle of bar 4 | 9 | 15 | 6 beats |
4/4 | 8 bars = start of bar 9 to end of bar 16 | 33 | 65 | 32 beats |
12/8 | First bar | 1 | 13 | 12 beats |
5/4 | First bar (first 5 beats of project) | 1 | 6 | 5 beats |
5/4 | First 10 bars (start of bar 1 to end of bar 10) | 1 | 51 | 50 beats |
Things are a more complex when a project contains changes in time signatures.
What I expected…
leftCycleBeat
is 1
rightCycleBeat
is 24
24-1
= 23
beats = 4 + 12 + 7What I saw…
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.
TimingInfo
for blocksThe block information relates to the underlying audio system as explained for the ProcessMIDI()
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.
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()
)
Block ‘1.0’ is indexed to the start of the project.
PluginParameters
ObjectPluginParameters
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.
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).
var PluginParameters = [
{
name: "Intensity",
type: "menu",
valueStrings: ["Low", "Mid", "High"],
defaultValue: 1
},
{ param2 },
...
]
The following is combination of documented and undocumented parameter settings.
type
: defines the type parameter widget
valueStrings
property that is an array of strings to show in the menu.defaultValue
: the integer or float number to set as default value. Default is 0.0.
minValue
: integer or float setting the minimum slider value. default is 0.0
maxValue
: integer or float setting the maximum slider value. default is 0.0
numberOfSteps
: an integer number to define the number of steps of a slider
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.
text
: Type text to create a divider/header in the plug-in UI.
PluginParameters
FunctionsGlobal functions for handling parameters…
Function | Description |
---|---|
ParameterChanged(paramNum, value) |
Called after any parameter change by the user. paramNum is the parameter index in PluginParameters |
GetParameter(param-name) |
retrieves the current console value of a parameter |
SetParameter(param-name, value) |
sets the console value of a parameter |
UpdatePluginParameters() |
dynamically updates the user interface |
PluginParameters
Examplevar PluginParameters =
[
// Text divider / header
{
name:"------ Example Plugin Parameters ------",
type:"text",
},
// Note Velocity: linear slider with a range of 1 to 127 in 126 steps. Default: 100
{
name:"Note Velocity",
type:"lin",
minValue:1,
maxValue:127,
numberOfSteps:126,
defaultValue:100
},
// Transpose: linear slider with a range of -24 to 24 in 48 steps. Defauly: 0
{
name:"Transpose",
type:'lin',
minValue:-24,
maxValue:24,
numberOfSteps:48,
unit: "semi-tones",
defaultValue: 0
},
// Checkbox that is defaulted to off (de-selected)
{
name:"Enable",
type:"checkbox",
defaultValue: 0
},
// Menu with 2 items is presented as radio buttons
{
name:"Radio",
type:"menu",
valueStrings:["Opt 1", "Opt 2"],
// default is the index in valueStrings array
defaultValue: 1
},
// Menu with 3 or more menu is presented as drop-down menu
{
name:"Range",
type:"menu",
valueStrings:["Low", "Mid", "High"],
// default is the index in valueStrings array - Mid
defaultValue: 1
},
// MIDI Target selector
{
name: "Target",
type: "target",
},
// Momentary trigger button
{
name: "Trigger",
type: "momentary",
}
];
function ParameterChanged(param, value) {
// param is index in PluginParameters array
Trace(`"${PluginParameters[param].name}" changed to ${value}`);
}
Unicode characters can be used in your parameters - many but not all.
Only a handful of Unicode’s musical symbols worked in Logic 10.6.2 (it would be handy to have more).
{
name:"𝄆 Codas 𝄇",
name:"Cleffs 𝄞 𝄢",
}
I had more luck with Emojis to spice up parameters.
{
name:"Smiley 😁",
name:"Clap 🙌",
}
GetParameter()
FunctionTODO - expand and add example Returns a given parameter’s current value
UpdatePluginParameters()
FunctionTODO - expand and add example dynamically updates the user interface
GetParameter()
FunctionTODO - expand and add example retrieves the current console value of a parameter
SetParameter()
FunctionTODO - expand and add example sets the console value of a parameter
MIDI
ObjectThe MIDI
object is a global provided by Scripter with utility functions for working with MIDI objects.
In Logic Pro 10.15.X, the MIDI object is defined here…
/Applications/Logic Pro X.app/Contents/Frameworks/MADSP.framework/Versions/A/Resources/MIDIClass.js
MIDI Function |
Description |
---|---|
ccName(num) |
Convert a MIDI control channel number to it’s name (string). e.g. MIDI.ccName(2) returns "Breath" |
allNotesOff() |
What it says :-) |
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) |
normalizeChannel(channel) |
Normalises the channel to be an integer from 1 to 16. Default is 1. e.g. MIDI.normalizeChannel(3) returns 3 |
normalizeData(num) |
Normalises the data value to be an integer from 0 to 127. Default is 1. e.g. MIDI.normalizeData(48) returns 48 |
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. |
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 |
The MIDI
object also presents the following internal utilities. It’s recommend that you use the corresponding public functions above.
MIDI Property |
Description |
---|---|
_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" |
_makeNoteNames |
Utility that creates the _noteNames array |
_ccNames |
Array of 128 MIDI Control Change names. ['Bank MSB', 'Modulation','Breath', ... 'Poly Mode On'] |
_sendEventOnAllChannels |
Use the public functions |
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.
This is not a complete list.
SendMIDIEventNow
- used by event.send()
SendMIDIEventAfterMilliseconds
- used by event.sendAfterMilliseconds()
SendMIDIEventAtBeat
- used by event.sendAtBeat()
SendMIDIEventAfterBeats
- used by event.sendAfterBeats()
From @Dewdman42 on https://www.logicprohelp.com/forum/viewtopic.php?t=132827&start=20
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.
This is a work-in-progress. In addition to TODO notes through this document…
beatPos