Jump to content

Make your own Logic Pro X MIDI Plug-ins in JavaScript!


Recommended Posts

OK, I wrote a script tonight to simulate a guitar strum: guitar_strum.pst. Check it out. Load it with scripter with an instance of Sculpture on the channel. Any guitar preset will do as long as you have 6 note polyphony set. But I like it best with: Sculpture Legacy > Guitars > Steel Strumming Guitar or the guitar.pst Sculpture preset I also attached below. It also sounds great with an EXS steel string acoustic too. But read below and note that EXS currently complains (goes silent) after several strums if you don't force some midi note off events.

 

It's not nearly complete. This is step1, and I plan on adding some degree of guitar-logic so that only guitar chords in particular inversions can actually be played. Right now the plug just plays whatever notes you tell it to, even if the combination is impossible on a 6 string. But eventually, you play an A7 in some standard inversion and the script will voice the output properly. Right now, here's how it works.

 

Keys below C4 are used to define chord tones which become notes in a javascript array. If and when the array contains 3 or more notes, then a separate strumming key can trigger these notes to play (iterating through the array and sending them all in sequence). There are two strumming keys right now. They are C4 and D4. C4 is a downstroke and D4 is an upstroke. The successive strings in the stroke are played with progressively less velocity and of course are delayed by a few milliseconds. All of this is randomized a bit to make things sound more organic.

 

Important: I can't decide yet how I want to trigger midi note off events and clear the array to get ready for a new chord; maybe when I release my left hand . . . ? I'm not sure. So the way it is right now, I listen for CC64 (damper) and trigger all midi notes off and clear my chord array that way also. Sculpture doesn't much care if you ever send it these midi off events. But EXS surely does and it will go silent after a dozen strums if you never hit your damper pedal.

 

I want to:

1) Add parameters for strumming style/ability by using variation in "string" velocity and timing (ie. loose/sloppy strumming)

2) Tie the strumming keys (C4, D4) velocity to the initial velocity of the strum which right now is just hard coded to about 100.

3) Narrow down my chord definition region to some limited space below C4 so that I can free up the low end of the keyboard for single picked bass notes etc.

4) Add other strum keys that cause muted strums via CC changes in strum etc.

5) And of course play proper guitar voicings based on the notes you play (yikes!) - I'll probably start with just open position options first

 

But I think I know how to do all of that now and it's just a matter of carving out a some hours over the next few days.

guitar_strum_1.pst

Guitar.pst

Link to comment
Share on other sites

(...)

 

Has anybody found a way to Set PluginParameters? Let's say i would like a menu Item to display the midi note that I'm playing.

 

(...)

 

Still on that question.

 

If we can set default values, can't we use a variable for it and reset the Plugin Parameters.

 

On page 189 of the fx manual they talk about the reset function.

 

Reset function

Reset() is called when the plugin is reset.

 

How can we reset the plugin? I found nothing in the scripter tutorials about that.

 

Any ideas?

Link to comment
Share on other sites

I'm working on that right now and have had zero luck:

 

• moving a Scripter GUI control will not write to automation (simply doesn't work). On the flip side, if you select a Scripter control from the automation menu and manually pencil in some changes to automation, the control will follow those changes. Not sure how useful that is.

 

• I cannot find a way to take an attribute of any kind of MIDI event and use that to "physically" move a GUI control.

Link to comment
Share on other sites

Damn, I just started taking Python lessons. Maybe I should have done Javascript instead. Not knowing anything about Javascript, I don't really understand how this works. What I'm wondering is, how does knowledge of javascript in general help with this? Isn't this specific to Logic, more like it's own language? I'm just curious because I want to learn other languages besides just Python.
Link to comment
Share on other sites

• moving a Scripter GUI control will not write to automation (simply doesn't work). On the flip side, if you select a Scripter control from the automation menu and manually pencil in some changes to automation, the control will follow those changes. Not sure how useful that is.

 

There must be something wrong with your install or preferences or bug with your system. I can record automation from Scripter controls.

Link to comment
Share on other sites

 

• I cannot find a way to take an attribute of any kind of MIDI event and use that to "physically" move a GUI control.

 

Without a SetParameter function, which Apple hasn't given us, I don't see any way of writing variable values BACK to the GUI. Moreover, I'm not sure we would really want to do that with a parameter, per se, since I think that technically, a parameter is by convention something that would be set externally via GUI and not itself alterable via data internal to the script.

 

What we want is some kind of monitor object, a GUI element that just displays whatever we put in it. But we don't have access to that level via the Javascript API. Logic's stock midi plugins provide some nice GUI elements. But these, I'm sure, are written in Obj C and then compiled. Not sure if there is any API documentation on these - like, are they under the AU standard? I haven't looked, but are there packages for these plugins somewhere in a directory as there are "component" packages for Audio Units? . . . Although I doubt it.

Link to comment
Share on other sites

@ cmrick, I figured out what the problem was. Has nothing to do with a bug in my system or a bad install.

 

@ jamie, Javascript is not specific to Logic. Scroll up a few posts and check out the links I provided for Javascript web resources.

Link to comment
Share on other sites

 

Thanks, Shiver. Your mention of a foot control clicked the light bulb for me. I realized there are scads of hardware controllers (Mackie, etc) that can manipulate Logic's transport with MIDI messages. I already have software that can send arbitrary midi to Logic, so now I just have to work out what messages to send to set the cycle locators to the desired start and end positions. If I get it working, I'll share the code so others can use it.

 

If you manage to get that working I'd love to see that. :D

 

I've got it working. 8) I'll post some details and a link to download a tarball in a new topic to be titled "LPMidiTransport : How to roll your own software midi controller"

Link to comment
Share on other sites

 

• I cannot find a way to take an attribute of any kind of MIDI event and use that to "physically" move a GUI control.

 

Without a SetParameter function, which Apple hasn't given us, I don't see any way of writing variable values BACK to the GUI.

 

Yeah... :( I've found that when Logic writes to automation from a plugin, it generates Fader events. And unfortunately, there doesn't seem to be a way to write or read Fader events in the MIDI-Javascript implementation.

 

Moreover, I'm not sure we would really want to do that with a parameter, per se, since I think that technically, a parameter is by convention something that would be set externally via GUI and not itself alterable via data internal to the script.

I can think of 1001 and reasons why it would be great to be able to arbitrarily read values from MIDI events and cause a menu or slider to reflect those values. OK, maybe not 1001 :lol: but I have at least 10 on my short list.

 

What we want is some kind of monitor object, a GUI element that just displays whatever we put in it. But we don't have access to that level via the Javascript API. Logic's stock midi plugins provide some nice GUI elements. But these, I'm sure, are written in Obj C and then compiled. Not sure if there is any API documentation on these - like, are they under the AU standard? I haven't looked, but are there packages for these plugins somewhere in a directory as there are "component" packages for Audio Units? . . . Although I doubt it.

I have no idea myself.

 

To your idea of a monitor object, I can see how that would be useful. But for now, we already have several Scripting plugin-based monitors, and they can be easily set up and/or right out modified to display a given kind of data in the console with var.trace(); or Trace(var); Not elegant, but functional for the time being.

 

No, I've got a serious jonesing for Scripter menus or sliders to reflect values from non-automation-based MIDI data. Wishing on a star for SetParameter... Set Parameter... :D

Link to comment
Share on other sites

What I'm wondering is, how does knowledge of javascript in general help with this? Isn't this specific to Logic, more like it's own language? I'm just curious because I want to learn other languages besides just Python.

Yep, there are all sorts of different javascript implementations out there. Think of it as a subset of the stuff folks use to make web browsers jump through hoops. For example, After Effects "expressions" are js, Apple's own Quartz Composer has a js patch, as well as Max/Msp/Jitter, Processing, etc.

 

http://eloquentjavascript.net

http://www.yuiblog.com/crockford/

http://jsfiddle.net/ctrlfrk/hNaYu/30/

Link to comment
Share on other sites

Does it seem like the scripter plugin is limited to stuff on that one track, or would there be a way to send to other midi instruments? My main use for the environment right now is setting up tracks that feed multiple instruments to stack them up. The ability to do that using Scripter or other midi plugins would be huge.

 

Because that would require updates to the AU spec, an incorporation in a new version of OSX, before applications like Logic can take advantage of it. In short, it needs OSX support first, they can't just do it in a Logic update...

 

Which they could do with OSX 10.9 and include in a future update to LPX…but if that were the case we would have probably heard about the OSX betas having that AU functionality, right? Of course it's a pain to update the AU spec, but that's no excuse to not ever do it.

Link to comment
Share on other sites

MIDI out from AU plugins is indeed now in the AU spec, just isn't supported by Logic yet...

For example, my favorite third party tool, Plogue Bidule, has midi out in its AU, but it doesn't work in Logic. It would be awesome if Logic supported Bidule's midi capabilities. My first inquiry with LPX was to see if anything is different/improved on this front. Not yet...

Link to comment
Share on other sites

Ski, I take back what I thought earlier. It might actually be possible to write a setParameter function if I can find the .js file that has the prototypes in it for the getParameter and other functions. Whether or not I'll ever get this to be reflected in the GUI is another issue but it would be a start to at least be able to set a parameter AFTER the script runtime and then retrieve it with getParameter. That would be a good first step. Right now I can reset a parameter's default value using PluginParameters[0].defaultValue =

But getParameter function isn't grabbing the default value. I don't know what exactly it's grabbing - clearly the "current value" etc. but I've no idea how to refer to it.

 

We have this from a previous post: http:///Applications/Logic Pro X.app/Contents/Frameworks/MADSP.framework/Versions/A/Resources/EventTypes.js

 

and this file includes all of the prototypes for the event object. If we can find a similar one containing getParameter and PluginParameter constructors then I'll try editing that file to add a setParameter method.

Link to comment
Share on other sites

Hi 88,

 

Gotta make a deadline so I'm gonna re-read your post again later. But I wanted to quickly mention something... I dunno if this is going to help at all or not, but I found that you can set the default value for a parameter using a variable. At first I didn't think it was possible but then I found that it was. So my thinking was this... by declaring a global variable at the top of the code, I'd use it to set the defaultValue: attribute for the slider or menu. Then I could dynamically change the value of the variable and thus change the displayed value of the slider. Well, no such luck LOL!

 

Anyway, mo' layda. And glad to be in conversation with you about this! :)

Link to comment
Share on other sites

Just a couple points to add to the discussion here:

 

1) Smart controls can be mapped to the Scripter GUI parameters you create in your script. Smart controls can learn MIDI controller assignments. Thus you can use MIDI to update your Scripter parameters at will. This isn't the same as the ability to access those parameters programmatically from within your script, of course, but it's a nice way to control parameters of your script in real time (and see the changes reflected in the Scripter GUI). I suppose if you then wanted your script to generate MIDI CC to be selectively rerouted back into the channel strip via the IAC bus to update Scripter parameters via Smart Controls, you could do that in the environment. You'll have jitter, but I don't see why it wouldn't otherwise work. Ski?

 

2) Anything you might want to accomplish in the first place by programmatically resetting or updating parameters from within your Script can of course be done by using global variables as stand-ins for your parameters (albeit without graphical feedback in the GUI). You'd instantiate them with the same default values given to your parameters, and you'd write code in the ParameterChanged() function to update them as parameters are updated from the GUI. Then you could refer to these "parameter-mirroring" global variables from anywhere within your script to use their values or alter them however you like--you just wouldn't see the parameters being updated in the GUI (because they aren't updated), but whenever you do change a parameter, the corresponding global variable snaps to that value.

 

No doubt this is not news to you guys, but I thought I'd put it out there!

Link to comment
Share on other sites

All great stuff ombudsman, thanks!

 

I totally see what you mean about creating a "foldback" situation for getting a GUI parameter to update (or rather, simply change). Great idea, though for what I have in mind it could get a little unwieldy. OK, here's a simple example of what I'd like to be able to accomplish...

 

What I'd love to do is create a Script that would read existing program change messages in a track. Within the script I'd have a slider or menu that would display the program numbers as they appear on a given synth. Let's take my NordLead2 as an example. The first bank's program numbers are displayed from 1 - 99 on its LED display. These correspond to MIDI program change values 0 - 98. A routine in the Script would create a slider or menu that displays the (MIDI program change numbers + 1).

 

The idea here is to have the plugin window with Link enabled so that when I select the channel hosting the Nord (via the External I/O), the Scripter GUI will automatically appear and show me a readout of Nord program numbers updating in real time.

 

Of course, doing the math is the easy part. ;)

Link to comment
Share on other sites

In the LPX efx manuel they talk about the Scripter API (application programming interface).

An API that is provided by Logic and that you can use via JavaScript.

The Scripter functions are surly not part of usual JavaScript.

 

If there is a way the setParameters we will be able to store settings and things like a Launchpad Scripter could be realized.

I mean we can do it now, but without the possibility to store settings using the parameters it wouldn't be very handy.

 

A starting point would be if we know how to use the Reset function - Reset() is called when the plugin is reset.

The manual indicates that API allows us to reset the plugin. But how?

 

And the point beej talked about, the AU Midi limitations... i cant understand why they did not refine the AU plugin format and realized all the Midi FX as AUs. I think there is an routing issue and they don't know how to handle it and i think the missing option to record the midi data of the Midi FX output tells about it.

Link to comment
Share on other sites

In the LPX efx manuel they talk about the Scripter API (application programming interface).

An API that is provided by Logic and that you can use via JavaScript.

The Scripter functions are surly not part of usual JavaScript.

 

Basically, they will have incorporated a JS interpreter into Logic, and extended it with the Logic/MIDI-specific functions required to interface with Logic.

 

And the point beej talked about, the AU Midi limitations... i cant understand why they did not refine the AU plugin format and realized all the Midi FX as AUs.

 

There might be technical reasons, or practical ones (or both). Anyway, they've made an implementation decision for whatever reasons they had and that's what we have.

 

I think there is an routing issue and they don't know how to handle it and i think the missing option to record the midi data of the Midi FX output tells about it.

 

You can think what you like but without any evidence it's rather meaningless! :)

Link to comment
Share on other sites

Evidence seems to be relative... if one of the major midi sequencers creates midi FX that can't be recorded by the sequencer, this might be evidence enough for some. :lol:

 

We got the environment with midi process possibilities which must lead into the sequencer input if you want to record it.

After the sequencer input they gave us now the Midi FX and if you want to record it's output... well... it must pass the sequencer input too. Wow.....

 

beej... thats what some would call a midi routing issue. :wink:

Link to comment
Share on other sites

So I've looked extensively on my machine and I can't find any other relevant javascript files that define getParameter or the PluginParameters associative array. I'm going to conclude at this point that these functions have no prototypes within javascript. Since scripter is a plug that basically provides a javascript interpreter WITHIN a larger architecture, getParameter and plugin parameters are probably not stored within javascript as such functionality is hard coded into the javascript interpreter - not surprising since parameters are essentially GUI elements.

 

It's annoying to me that I can do things like:

 

Trace(PluginParameters[0].defaultValue);
PluginParameters[0].defaultValue=0.75;
Trace(PluginParameters[0].defaultValue);

 

and see the change in that default value. But yet I cannot access the real current value of the parameter which, once it is passed from javascript to the larger framework, is not accessible. I think Ombudsman is right on with this and that we have to be happily content (for now) to just mirror our parameters with variables internal to the script and make anything that needs to be directly affected by incoming midi NOT a parameter but another variable. But too bad we can't see changes in the GUI.

Link to comment
Share on other sites

Thanks for doing all that snooping and sharing your thoughts.

 

I agree, it's annoying.

 

Strangeness abounds... when I put a Scripter in MIDI FX slot #1 and create (say) a slider in the script, it generates Fader #19 messages on channel 2. It's easy to see that because if you move the slider and write to automation, those are the events that get recorded. Now...

 

If you create a fader in the environment, program it to output Fader #19/channel 2 messages and cable it to the channel strip, moving the fader doesn't cause likewise movement in the Scripter's fader.

 

Then, try the same as the above but this time run the fader into a Transformer (set to Automation Splitter mode). Connect the transformer to the channel strip. No dice there either.

 

The only thing I've been able to do (as provided for already, and no mystery) is update the value of variables in the script based on changes in the position of a control.

 

The only thing I can think of is to re-write variables (that determine the default position of controls) and then somehow causing the plugin to reset. But I don't know how to call the reset function, or if that would even work.

Link to comment
Share on other sites

Yeah, I wish the documentation explained Reset() more thoroughly. It says the function "is called when the plugin is reset." Clearly this is not the same as to say "call the function in your code to reset the plugin." Well, okay, but... what "resets" the plugin? Clicking the "Run Script" button in the editor? Apparently not (see result below).

 

For what it's worth, you certainly can define and call Reset() in your script, but it doesn't appear to do anything other than whatever code you put in your Reset() function.

 

Example:

function HandleMIDI(event) {
   if (event instanceof NoteOn) {
       event.trace();
       Reset();
       Foo();
   }
}

function Reset() {
   Trace("I don't do anything special.");
}

function Foo() {
   Trace("Bar.");
}

 

Console output after clicking "Run Script" and playing C3:

***Creating a new MIDI engine with script***

Evaluating MIDI-processing script...
Script evaluated successfully!
[NoteOn channel:1 pitch:60 [C3] velocity:95]
I don't do anything special.
Bar.
>

 

Notably, when you define Reset() in your code, it's highlighted orange in the editor--just like HandleMIDI(), and unlike Foo(). That means it is indeed a special function in the API, though its utility remains a mystery for now.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...