A technical support community for Apple Logic Pro users.

 
User avatar
Dewdman42
Topic Author
Posts: 3307
Joined: Tue Sep 09, 2014 3:01 pm
Location: Salt Lake City, UT

TIP: Scripter Event extensions

Sun May 02, 2021 2:45 pm

Scripter includes an object class that you may be familiar with, for representing midi events. There is a set of classes, and they are hierarchical.

ScripterEventHierarchy.jpg
ScripterEventHierarchy.jpg (30.82 KiB) Viewed 1778 times


What this means is that there is a top level base class called Event. Then there are more specific object classes which inherit behavior from that base class, while providing specific properties and methods for each of their own cases. In the above digram you can see that NoteOn and NoteOff inherit from an intermediary class called Note. Note inherits from Event. All other midi events inherit from the base class Event.

Alright..that sounds like programmer technical mumbo jumbo...what can you do with that info?

Note that when HandleMIDI is called it is passed an Event object, typically like this:

function HandleMIDI(event) {
   event.send();
}


But which type of event is that? what will its properties be? For example, Note's have pitch and velocity, ControlChange have number and value. We don't know initially inside HandleMIDI which type it is to even start looking. So far HandleMIDI only knows that its one of the Event objects...which specific one, we have to find out.

The way many people check is by using the instanceof keyword in javascript to see if an event matches certain type. Like this:

function HandleMIDI(event) {
    if(event instanceof NoteOn) {
        // do something with it now we know its a NoteOn
    }
}


Alright..that's fine and pretty simple so far... But...

In some cases scripter code can get messy with a lot of nested if/then statements to do this action or that action depending on what type of object we are looking at.

Consider this mundane simple example:

function HandleMIDI(event) {
    if(event instanceof NoteOn) {
        Trace(`NoteOn = ${MIDI.noteName(event.pitch)}`);
        event.send();
    }
    else if(event instanceof ControlChange) {
        if(event.number == 1) {
            Trace(`CC1 = ${event.value}`);
            event.send();
        }
       else {
           event.send();
       }
    }
    else {
        event.send();
    }
}


Extending Events with Methods

It is possible to add methods and properties to any of the Event object classes, or to all of them at once (by adding to the Event class), by using javascript prototype approach.

You can google around to get more depth on the topic, but this is done by using the prototype keyword in Javascript. Here is an example where I will replicate the above behavior using this approach:

Event.prototype.handler = function() {
    this.send();
};

NoteOn.prototype.handler = function() {
    Trace(`NoteOn = ${MIDI.noteName(this.pitch)}`);
    this.send();   
};

ControlChange.prototype.handler = function() {
    if(this.number == 1) {
        Trace(`CC1 = ${this.value}`);
    }
    this.send();
};

function HandleMIDI(event) {
    event.handler();
}


So in the above example we define a method added to Event objects called handler, but we have defined three seperate versions of that method. One for NoteOn, one for ControlChange, and one for everything else. The Event version is the version that everything else will use, while NoteOn and ControlChange each have their own version of the handler method.

Note the use of the keyword this. Within an object prototype, this refers to the object itself. Google is your friend if that still isn't clear.

Also note that when prototype methods are defined this way there is a semi-colon after the final brace, unlike normal function definitions.

Each version of this method does different actions. One nice thing about this approach is that the actions for each event type are isolated into those per-event-type methods, which makes it more clear to see how each event type is being handled, but also note that the instanceof keyword and the previously complicated if/then spiderwebs are gone?!?!

Then notice how HandleMIDI is very simple now? You simply call event.handler(), and Scripter will automatically call the correct version of the handler method depending on which specific Event type it is. It happens automatically. No need to use Instanceof anything...

function HandleMIDI(event) {
    event.handler();
}


Extend Events with Properties

Another thing that can be very useful is to stash values into an event object so that as the event object is passed around your code, it will take these associated values piggybacked into it.

That is actually quite easy, no need to prototype, just add any property you want at any time to any object in javascript and the value will ride around on the event object:

function HandleMIDI(event) {
    event.now = Date.now();
    doSomething(event);
}

function doSomething(event) {
    Trace(`timestamp = ${event.now}`);
}


Its also possible stash a sort of global property in each Event class, at a global level..so its one shared variable between all objects of each type. For example, let's say I want to stash a reusable key-switching NoteOn object; as a property, into the NoteOn class so that it can be reused in a more global way by all future NoteOn objects. Here's an example that may fry your brain, but try it.

Event.prototype.mySend = function() {
    this.send();
};

NoteOn.prototype.ks = new NoteOn;
NoteOn.prototype.ks.pitch = MIDI.noteNumber("C0");

NoteOn.prototype.mySend = function() {
    this.ks.channel = this.channel;
    this.ks.velocity = 100;
    this.ks.send();
    this.ks.velocity = 0;
    this.ks.send();
   
    this.send();
};

function HandleMIDI(event) {
    event.mySend();
}


Brain fried? Its meant to demonstrate how things work more than anything. In the above example, all NoteOn's will have a C0 keyswitch inserted, all other events will pass through. Note how simple the HandleMIDI function is? This combines some of the per-object method selection mentioned above, but note that I have stashed a global ks object that is available for reuse to all NoteOn objects. Its global, so ks doesn't have to be recreated every time. All NoteOn's will be sharing a pointer to the same ks object.

Alright that's enough for now. I use some of the above tricks all the time. I find that it can clean up my HandleMIDI function and make it much easier to understand rather then a spider web of object instanceof checking, etc. It consolidates the specific handling for each event object type, inside its own prototype function.

Just something to consider....
OSX 10.15 (Catalina) on OpenCore - Logic Pro 10.6.1, VePro7, Mainstage3
5,1 MacPro 3.46ghz x 12 96gb ram
 
User avatar
Dewdman42
Topic Author
Posts: 3307
Joined: Tue Sep 09, 2014 3:01 pm
Location: Salt Lake City, UT

Re: TIP: Scripter Event extensions

Sun May 02, 2021 3:05 pm

Here is another example of something I use all the time, particularly when I need to detect NoteOff. One issue that a lot of people miss is that NoteOff can be represented two different ways in LogicPro. A NoteOn with velocity=0 is also considered a NoteOff! So normally you'd have to use logic like this to truly check for NoteOff:

    if(event instanceof NoteOff || (event instanceof NoteOn && event.velocity< 1)) {
        // handle NoteOff
    }


Kind of annoying to have to do all that checking every time you are needing to handle NoteOff. But I do this a lot:

Wouldn't it be nice to have something like this:

    if(event.isNoteOff()) {
        // do NoteOff stuff
    }


Here is how you can get that, the prototype way:

Event.prototype.isNoteOff = function() {
    return false;
};
NoteOn.prototype.isNoteOff = function() {
   if(this.velocity < 1) return true;
   else return false;
};
NoteOff.prototype.isNoteOff = function() {
    return true;
};

// more simple HandleMIDI
function HandleMIDI(event) {
    if(event.isNoteOff() == true) {
        // handle NoteOff
    }
}


That's a lot more code its true, but it makes HandleMIDI easier to read and understand...and of course if you have to check for NoteOff in numerous places then it pays for itself. Anyway, Just copy and paste the prototype type stuff to the script, then the rest of the code can use the simple isNoteOff method to detect it in a complete way.

I would be curious to hear about other situations people may find any of these Event prototyping approaches useful.
OSX 10.15 (Catalina) on OpenCore - Logic Pro 10.6.1, VePro7, Mainstage3
5,1 MacPro 3.46ghz x 12 96gb ram