Jump to content

Sending Scripter MIDI Values on Project Load


DjStiky

Recommended Posts

Hi there,

I have a script I use along with the External Device instrument which has 2 dropdown menus:
First menu allows me to choose a patch name from a list, and it sends MSB,LSB and PC values to midi out so that my synth loads the specific patch.
Second menu allows me to choose which midi channel it affects (1 to 16).

When I load a project that has this, these values do not get sent, however they are remembered on the GUI. They will only get sent if I open the GUI and make a new selection. Is there a way to send existing selection values on project load?

Thanks!

 

Edited by DjStiky
Link to comment
Share on other sites

Hi DjStiky,

This all comes down to getting the ParameterChanged callback function called, which typically gets called:

  • When a plugin parameter is set to a new value
  • When loading a Scripter plug-in setting (i.e: a saved plug-in settings file or ".pst" for example from the plug-in settings menu)
  • When clicking the Run Script button in the Scripter's Editor

However there is a fourth way and that is by calling the UpdatePluginParameters function. I have not found a way to get this function called on project load by setting something up in Scripter alone, but did find a way to get it called by the HandleMIDI function when a specific MIDI message is received. This can be done with the help of bit of MIDI Environment setup. It feels like a workaround but it does work reliably:

  1. Go to Project Settings > MIDI > General and check the Miscellaneous > Send After Loading Project > All fader values box SendAllfadervalues.png.27f80d6ee09a921b93024d3dfffdb1c1.png
  2. Create a Fader object in the MIDI Environment and set it to output a MIDI message of your choice (for example, a CC you know you won't be using for anything else) CCFaderButton.png.39f3e5f86af191a39a1085b936fafc77.png
  3. Cable the fader object to an MIDI Instrument object and set its output port to a IAC bus of your choice (of course the IAC Driver must be enabled in Applications/Utilities/Audio MIDI Setup > MIDI Studio window) ToIACMIDIInstr.png.1d51088dc6c62b2520ba4c0276e1a3a4.png
  4. At this point you need to decide how the Fader object's message will reach Scripter via the IAC bus:
  • Assuming the Software Instrument track where you have the Scripter and the External Instrument plugin has its MIDI In Port set to "All", you could just make sure to leave the track selected whenever you save and close your project.
  • Otherwise you can cable the IAC bus port in the Physical Input object directly to the Software Instrument channel strip object when you have the Scripter and the External Instrument plugin. Besides not having to leave the track selected, the other advantage of doing it this way is that you con route the MIDI message to multiple software Instrument Channel Strip objects where you have Scripter sending Bank Select + Program Changes to external instruments.PhysicalInputtoSIChannelStrips.thumb.png.9eddad39c323a4c641763587ff3a50ef.png

    5. Set up your script to have UpdatePluginParameters called when the MIDI message sent by the fader          object reaches Scripter, for example:

function HandleMIDI(e) {
  // Call UpdatePluginParameters upon receiving a CC 28 message
  if (e instanceof ControlChange && e.number === 28) {
    UpdatePluginParameters();
    // Let non-matching events pass through
  } else {
    e.send();
  }
}

 

A final note: One other way I found when experimenting with this in the past was to first open Logic, then open the project. This sort of works but is very hit-and-miss.

J.

Link to comment
Share on other sites

I have wrestled with this issue a lot also.  To be honest the ParameterChanged function SHOULD be getting called at some point when the project is loaded.  Then it's just a matter of keeping track of whether the script has sent out the midi  yet or not.  If not then send them.  

There is also an undocumented callback function called "Initialize" which you can write to do stuff, but I'm not sure right now whether the Parameters are fully setup before calling that callback function.  Try it.  I don't have access to a mac right now to try anything out...

 

 

 

 

  • Like 2
Link to comment
Share on other sites

alright, just for kicks, i decided to try out chatGPT for the first time today and I tried to ask it to write a LogicPro Scripter script...which guess what....it was able to do.  Mind blown.  Anyway, see the response:

ChatGPTVN.thumb.png.ced9cc355eaa6786298abf38c9f6ae1d.png

So the interesting thing that came out of this, I'm not sure if this actually works, can someone try it?  If so, it's another hidden feature I was not aware of.  Basically the the concept of an "Init' event type, if that is true, than that is one way to send some startup values when the project loads...

Here's the script test for easy copy and paste

var pluginParameters = [
  {name:"Program", type:"lin", minValue:0, maxValue:127, numberOfSteps:127, defaultValue:0},
  {name:"Channel", type:"lin", minValue:1, maxValue:16, numberOfSteps:15, defaultValue:1}
];

function HandleMIDI(event) {
  if (event instanceof Init) {
    // Get the values of the plugin parameters
    var program = GetParameter("Program");
    var channel = GetParameter("Channel") - 1; // Convert to zero-based index

    // Send a program change message on init
    var status = 0xC0 | channel; // Program change status byte
    var data1 = program; // Program number (0-127)
    var data2 = 0; // Not used for program change messages
    var message = [status, data1, data2]; // MIDI message array
    event.sendMIDI(message); // Send the MIDI message
  }
}

 

 

Edited by Dewdman42
  • Wow! 1
Link to comment
Share on other sites

Hey @Dewdman42

 

3 hours ago, Dewdman42 said:

There is also an undocumented callback function called "Initialize" which you can write to do stuff, but I'm not sure right now whether the Parameters are fully setup before calling that callback function.  Try it.

 

Calling UpdatePluginParameters from within that undocumented Initiliaze callback actually does the trick in Logic 10.6.3, so thanks for the tip! Unfortunately however, it does not seem to work in 10.7.7. Something changed.

J.

 

 

 

 

Link to comment
Share on other sites

 

4 hours ago, Dewdman42 said:

So the interesting thing that came out of this, I'm not sure if this actually works, can someone try it?  If so, it's another hidden feature I was not aware of.  Basically the the concept of an "Init' event type, if that is true, than that is one way to send some startup values when the project loads...

 

I tried the ChatGPT code you shared. Unfortunately it was off to a bad start when It made a typo on the PluginParameters' array identifier (it gave you a camel case version) 😅

That typo aside, the code unfortunately does not work:

As you know, HandleMIDI acts upon MIDI events that Scripter receives. I don't see how loading a project would somehow feed this Init object to HandleMIDI for it to check and send that program change message in response to it?

The construction of the program change message is valid JS, but it doesn't look like Scripter can do anything useful with it. 

Finally that sendMIDI method it gave you at the end is also not valid.

Despite all this, I still think ChatGPT is a very useful tool. But I find that many times it just needs to be coerced into giving you what you want.

J.

Edited by Jordi Torres
  • Like 2
Link to comment
Share on other sites

anyway, another way to do this, is to do something more or less like this:

var done = false;

function ProcessMIDI() {
   if(!done) {
       // GetParameters and send over midi
       done = true;
   }
}

I personally dislike polluting ProcessMIDI with this, but anyway, what it comes down to is that Javascript and Scripter have a bootstrapping procedure it goes through.  When you load a project, first it scans the whole script from top to bottom looking for variable names and so forth, then it executes whatever code you have in there at the global level.  At that point, midi doesn't work and the Parameters array is not available yet either.  

After it has fully scanned the script and executed all the javascript at the global level, there are several things that are done, not exactly sure which order:

  1. The Initialize callback is called
  2. The internal structures holding Parameter data is fully and completely setup, which in some cases seems to result in ParameterChanged callback being called.

Finally, after doing that, Scripter goes into a state where it will be calling the various callback functions as they come up.

The dilema is that the Parameters cannot be accessed until step #2 happens first above.  it's unknown whether that happens before or after the Initialize callback.  it definitely does not happen before executing all the global level javascript I mentioned above.  it happens before Scripter goes into the mode of waiting for callbacks..  But initialize is a callback itself...so I would think it would be called after step#2 above, but we don't know.

UpdatePluginParameters I would not expect to work at all until step #2 has occurred, so attempting to call it during Initialize...who knows...not sure why it worked before and not now...but we have limited info about the timing.

In any case ProcessMIDI is definitely not called until everything has been fully bootstrapped, so you can certainly add some code like above and it should basically manage to do what is needed.

Edited by Dewdman42
Link to comment
Share on other sites

Another way worth consideration is like this:

var done = false;
function Idle() {
    if(!done) {
        // GetParamter and send midi
        done = true;
    }
}

I'm not sure right now if you can actually send midi inside the Idle function, quite possibly not.  I don't have a mac here to try it.  The advantage of this would be to avoid ProcessMIDI being called incessantly just to check for a one time sending of program change data.  Idle would get called incessantly, but in theory Scripter is calling Idle much less often and at times where it has some CPU room to breath.

Thing is, ParameterChanged should be getting called during project load, so really you ought to be able to do that above type of thing in there, with even less CPU hit.

var done = false;
function ParameterChanged(num, val) {
    if(!done) {
        // GetParamter and send midi
        done = true;
    }
}

 

Edited by Dewdman42
Link to comment
Share on other sites

Thank you @Dewdman42! this worked but kind of... 🤔

var done = false;

function ProcessMIDI() {
   if(!done) {
       // GetParameters and send over midi
       done = true;
   }
}

However not always. It only works if I have one track with my script. I think it has to do with having multiple tracks with scripter and this script. Doesn't work if I have a different tracks for each of the 16 midi channels, all having this script loaded.

This is what my script currently looks like for one of my categories of patches, and I have a different copy for each category of patches (not sure if I commented the new code correctly), however I wish I could have 1 script that combines all categories and patches for each of the 16 channels. GUI would have lets say 16 "Category" menus (1 for each midi channel), each having a sub menu with patches for that category:

//Create PC, MSB and LSB objects;
var pc = new ProgramChange();
var msb = new ControlChange();
var lsb = new ControlChange();

//Set MIDI CC number to control the Patches menu
var ccNum = 1;

//Author string
var author = "Factory Piano";

//MIDI Channel strings array
var mChannel = [];

//Patches array
var Patches = [
   { patch: "Concert Grand", program: 0, msb: 121, lsb: 13 },
   { patch: "Italian Piano", program: 0, msb: 121, lsb: 16 },
   { patch: "Jazz Piano", program: 0, msb: 121, lsb: 5 },
   { patch: "Italian Jazz P.", program: 0, msb: 121, lsb: 18 },
   { patch: "Live Piano", program: 1, msb: 121, lsb: 6 },
   { patch: "Rock Piano", program: 0, msb: 121, lsb: 8 },
   { patch: "Pop Upright", program: 0, msb: 121, lsb: 14 },
   { patch: "M1 Piano", program: 2, msb: 121, lsb: 2 },
   { patch: "Pop Grand", program: 0, msb: 121, lsb: 12 },
   { patch: "It. Piano", program: 0, msb: 121, lsb: 17 },
   { patch: "Classic Piano", program: 0, msb: 121, lsb: 4 },
   { patch: "Warm Grand", program: 0, msb: 121, lsb: 10 },
   { patch: "Bright Piano", program: 1, msb: 121, lsb: 5 },
   { patch: "It. Grand & Stack", program: 2, msb: 121, lsb: 13 },
   { patch: "G.Piano Stack 1", program: 2, msb: 121, lsb: 8 },
   { patch: "G.Piano Stack 2", program: 2, msb: 121, lsb: 9 },
   { patch: "Honky-Tonk", program: 3, msb: 121, lsb: 2 },
   { patch: "Ragtime Piano", program: 3, msb: 121, lsb: 3 },
   { patch: "E. Grand Piano", program: 2, msb: 121, lsb: 12 },
   { patch: "Grand&MovingPad", program: 0, msb: 121, lsb: 9 },
   { patch: "Grand & Strings", program: 0, msb: 121, lsb: 7 },
   { patch: "Midi Grand & Pad", program: 1, msb: 121, lsb: 4 },
   { patch: "Harpsichord 1", program: 6, msb: 121, lsb: 7 },
   { patch: "Harpsichord 8+4", program: 6, msb: 121, lsb: 8 },
   { patch: "ClassicClav1 DNC", program: 7, msb: 121, lsb: 7 },
   { patch: "ClassicClav2 DNC", program: 7, msb: 121, lsb: 8 },
   { patch: "Piano Layers", program: 2, msb: 121, lsb: 6 },
   { patch: "Grand & FM Stack", program: 2, msb: 121, lsb: 7 },
   { patch: "Piano & Vibes", program: 0, msb: 121, lsb: 6 },
   { patch: "Grand Piano Demo", program: 0, msb: 121, lsb: 11 },
   { patch: "Grand Piano", program: 0, msb: 121, lsb: 3 },
   { patch: "Upright Piano", program: 0, msb: 121, lsb: 15 }
];

//ProcessMIDI Default State (Used for sending values on script or project load)
var done = false;

//Populate MIDI Channels array
for (var i = 0; i < 16; i++) {
   mChannel[i] = String(i + 1);
}

//Create menus and author label
var PluginParameters = [{
   name: "Patches",
   type: "menu",
   valueStrings: [
       "Concert Grand",
       "Italian Piano",
       "Jazz Piano",
       "Italian Jazz P",
       "Live Piano",
       "Rock Piano",
       "Pop Upright",
       "M1 Piano",
       "Pop Grand",
       "It. Piano",
       "Classic Piano",
       "Warm Grand",
       "Bright Piano",
       "It.Grand & Stack",
       "G.Piano Stack 1",
       "G.Piano Stack 2",
       "Honky-Tonk",
       "Ragtime Piano",
       "E. Grand Piano",
       "Grand&MovingPad",
       "Grand & Strings",
       "Midi Grand & Pad",
       "Harpsichord 1",
       "Harpsichord 8+4",
       "ClassicClav1 DNC",
       "ClassicClav2 DNC",
       "Piano Layers",
       "Grand & FM Stack",
       "Piano & Vibes",
       "Grand Piano Demo",
       "Grand Piano",
       "Upright Piano"

   ],
   defaultValue: 0,
   numberOfSteps: 4
}, {
   name: "MIDI Channel",
   type: "menu",
   valueStrings: mChannel,
   defaultValue: 1
}, {
   name: author,
   type: "text"
}];

//Send Program Change and Bank Select MSB/LSB when selecting a patch from the menu
function ParameterChanged(param, value) {

   //MIDI Channel variable
   var midichannel = GetParameter("MIDI Channel") + 1;

   msb.number = 0;
   msb.value = Patches[GetParameter("Patches")].msb;
   msb.channel = midichannel;
   msb.send();

   lsb.number = 32;
   lsb.value = Patches[GetParameter("Patches")].lsb;
   lsb.channel = midichannel;
   lsb.send();

   pc.number = Patches[GetParameter("Patches")].program;
   pc.channel = midichannel;
   pc.send();
}

//Send Program Change and Bank Select MSB/LSB when script or project loads
function ProcessMIDI() {
   if(!done) {
       // GetParameters and send over midi
       done = true;
   }
}

//Let MIDI messages pass through, control menu with a CC
function HandleMIDI(e) {
   if (e instanceof ControlChange && e.number == ccNum) {
       SetParameter(0, e.value);
   }
   e.send();
}

All this because Logic doesn't let us sort banks and programs for MIDI instruments in the order we want. 😅 The "Multi-Instrument" object in the Environment is very limited, with a limited number of patches and not being able to apply different LSB, MSB and PC values per patch in any order.

Edited by DjStiky
Clarification
Link to comment
Share on other sites

You need to fill in the part where I said this:

// GetParameters and send over midi

That's where you need to insert all your logic that sends out the midi.  right now you have that inside ParameterChanged.  Put that code into a sub function and call that sub function both from. ParameterChanged as well as from ProcessMIDI at that point I indicated.

Link to comment
Share on other sites

Hey @Dewdman42,

I gave your suggestion a go but unfortunately it did not do the trick 🙁. I tested it on Logic 10.7.7 + macOS 12.6.4. 

 

@DjStiky, since I was the one who helped you with that script nearly 7 years ago, I feel inclined to provide you with a slightly improved version. Works the same, just eliminated some redundant stuff:

// Create PC and Bank Select (MSB/LSB) objects;
const pc = new ProgramChange();
const bs = new ControlChange();

// Set MIDI CC number to control the Patches menu
const ccNum = 1;

// Author string
const author = 'Factory Piano';

// Patches array
const Patches = [
  { patch: 'Concert Grand', program: 0, msb: 121, lsb: 13 },
  { patch: 'Italian Piano', program: 0, msb: 121, lsb: 16 },
  { patch: 'Jazz Piano', program: 0, msb: 121, lsb: 5 },
  { patch: 'Italian Jazz P.', program: 0, msb: 121, lsb: 18 },
  { patch: 'Live Piano', program: 1, msb: 121, lsb: 6 },
  { patch: 'Rock Piano', program: 0, msb: 121, lsb: 8 },
  { patch: 'Pop Upright', program: 0, msb: 121, lsb: 14 },
  { patch: 'M1 Piano', program: 2, msb: 121, lsb: 2 },
  { patch: 'Pop Grand', program: 0, msb: 121, lsb: 12 },
  { patch: 'It. Piano', program: 0, msb: 121, lsb: 17 },
  { patch: 'Classic Piano', program: 0, msb: 121, lsb: 4 },
  { patch: 'Warm Grand', program: 0, msb: 121, lsb: 10 },
  { patch: 'Bright Piano', program: 1, msb: 121, lsb: 5 },
  { patch: 'It. Grand & Stack', program: 2, msb: 121, lsb: 13 },
  { patch: 'G.Piano Stack 1', program: 2, msb: 121, lsb: 8 },
  { patch: 'G.Piano Stack 2', program: 2, msb: 121, lsb: 9 },
  { patch: 'Honky-Tonk', program: 3, msb: 121, lsb: 2 },
  { patch: 'Ragtime Piano', program: 3, msb: 121, lsb: 3 },
  { patch: 'E. Grand Piano', program: 2, msb: 121, lsb: 12 },
  { patch: 'Grand&MovingPad', program: 0, msb: 121, lsb: 9 },
  { patch: 'Grand & Strings', program: 0, msb: 121, lsb: 7 },
  { patch: 'Midi Grand & Pad', program: 1, msb: 121, lsb: 4 },
  { patch: 'Harpsichord 1', program: 6, msb: 121, lsb: 7 },
  { patch: 'Harpsichord 8+4', program: 6, msb: 121, lsb: 8 },
  { patch: 'ClassicClav1 DNC', program: 7, msb: 121, lsb: 7 },
  { patch: 'ClassicClav2 DNC', program: 7, msb: 121, lsb: 8 },
  { patch: 'Piano Layers', program: 2, msb: 121, lsb: 6 },
  { patch: 'Grand & FM Stack', program: 2, msb: 121, lsb: 7 },
  { patch: 'Piano & Vibes', program: 0, msb: 121, lsb: 6 },
  { patch: 'Grand Piano Demo', program: 0, msb: 121, lsb: 11 },
  { patch: 'Grand Piano', program: 0, msb: 121, lsb: 3 },
  { patch: 'Upright Piano', program: 0, msb: 121, lsb: 15 },
];

// Create menus and author label
var PluginParameters = [{
    name: 'Patches',
    type: 'menu',
    valueStrings: [
      'Concert Grand',
      'Italian Piano',
      'Jazz Piano',
      'Italian Jazz P',
      'Live Piano',
      'Rock Piano',
      'Pop Upright',
      'M1 Piano',
      'Pop Grand',
      'It. Piano',
      'Classic Piano',
      'Warm Grand',
      'Bright Piano',
      'It.Grand & Stack',
      'G.Piano Stack 1',
      'G.Piano Stack 2',
      'Honky-Tonk',
      'Ragtime Piano',
      'E. Grand Piano',
      'Grand&MovingPad',
      'Grand & Strings',
      'Midi Grand & Pad',
      'Harpsichord 1',
      'Harpsichord 8+4',
      'ClassicClav1 DNC',
      'ClassicClav2 DNC',
      'Piano Layers',
      'Grand & FM Stack',
      'Piano & Vibes',
      'Grand Piano Demo',
      'Grand Piano',
      'Upright Piano',
    ],
    defaultValue: 0,
    numberOfSteps: 4,
  },
  {
    name: 'MIDI Channel',
    type: 'linear',
    defaultValue: 1,
    minValue: 1,
    maxValue: 16,
    numberOfSteps: 15,
  },
  {
    name: author,
    type: 'text',
  },
];

// Send Program Change and Bank Select MSB/LSB when selecting a patch from the menu
function ParameterChanged(param, value) {

  // MIDI Channel variable
  const midiChannel = GetParameter('MIDI Channel');
  
  // Bank Select MSB
  bs.number = 0;
  bs.value = Patches[GetParameter('Patches')].msb;
  bs.channel = midiChannel;
  bs.send();

  // Bank Select LSB
  bs.number = 32;
  bs.value = Patches[GetParameter('Patches')].lsb;
  bs.send();

  // Program Change
  pc.number = Patches[GetParameter('Patches')].program;
  pc.channel = midiChannel;
  pc.send();
}

// Let MIDI messages pass through, control menu with a CC
function HandleMIDI(e) {
  if (e instanceof ControlChange && e.number === ccNum) {
    SetParameter(0, e.value);
  } else {
    e.send();
  }
}

J.

Link to comment
Share on other sites

4 hours ago, Jordi Torres said:

@DjStiky, since I was the one who helped you with that script nearly 7 years ago, I feel inclined to provide you with a slightly improved version. Works the same, just eliminated some redundant stuff:

Thanks @Jordi Torres! Your script has been very helpful for my projects, so really appreciate it!

If you ever want to take a crack at further enhancements, let me know. I have an idea that would prevent me from having different copies of this for each category of patches, and prevent having multiple tracks/instances for each midi channel.

In theory, it would work this way:
- 1 instance where the GUI would have 16 sections (1 for each midi channel)
- Each of the 16 section would have 2 dropdown menus. First dropdown would choose "category" of patches, second dropdown would have the patch list for the chosen category.

This way, different Program Change and Bank Select MSB/LSB values would be sent to each of all the 16 midi channels from the same instance, all in 1 scripter plugin. Example:

Channel 1: Piano category and Concert Grand patch
Channel 2: Guitar category and Nylon Guitar patch
and so on...

However, I'm not familiar with coding and not sure how feasible this would be.

Thanks again!

Edited by DjStiky
Link to comment
Share on other sites

5 hours ago, Dewdman42 said:

I’m not looking really sure what you are referring to now, sorry.  

My apologies @Dewdman42, somehow I missed some of the content posted earlier, including the bit where DjStiky mentions your suggestion with ProcessMIDI did do the trick. 

I will over it again later and re-test, sorry for the confusion.

@DjStiky, I will go over what you posted later when I done with work.

J.

Edited by Jordi Torres
  • Like 1
Link to comment
Share on other sites

The Script DJSiky posted should not work either.  i explained above, i made a very simple explanation because I don't have access to a mac right now, but basically in ProcessMIDI you need to do a one time send of the program change data.  And then you need to do the same thing from ParameterChanged whenever someone updates the GUI.  So put that logic into a sub function and then call it from inside ProcessMIDI like I showed, and also inside ParameterChanged.

 

  • Like 1
Link to comment
Share on other sites

5 minutes ago, Dewdman42 said:

but basically in ProcessMIDI you need to do a one time send of the program change data.  And then you need to do the same thing from ParameterChanged whenever someone updates the GUI.  So put that logic into a sub function and then call it from inside ProcessMIDI like I showed, and also inside ParameterChanged.

Ok, this is what I tried last night and it did not work here. I will do more testing later just in case.

Thanks.

J.

Link to comment
Share on other sites

So @Dewdman42,

I re-tested what I tried yesterday, but unfortunately Scripter does not send the MIDI when opening the project (with Logic not already running, which is the most interesting scenario for me).

I tried your ProcessMIDI suggestion, and today also with the Idle function. Again this on Logic 10.7.7 + macOS 12.6.4. 

Here's the ProcessMIDI variant:

// Create PC, Bank Select (MSB/LSB) objects;
const pc = new ProgramChange();
const bs = new ControlChange();

// Set MIDI CC number to control the Patches menu
const ccNum = 1;

// Author string
const author = 'Factory Piano';

// Patches array
const Patches = [
  { patch: 'Concert Grand', program: 0, msb: 121, lsb: 13 },
  { patch: 'Italian Piano', program: 0, msb: 121, lsb: 16 },
  { patch: 'Jazz Piano', program: 0, msb: 121, lsb: 5 },
  { patch: 'Italian Jazz P.', program: 0, msb: 121, lsb: 18 },
  { patch: 'Live Piano', program: 1, msb: 121, lsb: 6 },
  { patch: 'Rock Piano', program: 0, msb: 121, lsb: 8 },
  { patch: 'Pop Upright', program: 0, msb: 121, lsb: 14 },
  { patch: 'M1 Piano', program: 2, msb: 121, lsb: 2 },
  { patch: 'Pop Grand', program: 0, msb: 121, lsb: 12 },
  { patch: 'It. Piano', program: 0, msb: 121, lsb: 17 },
  { patch: 'Classic Piano', program: 0, msb: 121, lsb: 4 },
  { patch: 'Warm Grand', program: 0, msb: 121, lsb: 10 },
  { patch: 'Bright Piano', program: 1, msb: 121, lsb: 5 },
  { patch: 'It. Grand & Stack', program: 2, msb: 121, lsb: 13 },
  { patch: 'G.Piano Stack 1', program: 2, msb: 121, lsb: 8 },
  { patch: 'G.Piano Stack 2', program: 2, msb: 121, lsb: 9 },
  { patch: 'Honky-Tonk', program: 3, msb: 121, lsb: 2 },
  { patch: 'Ragtime Piano', program: 3, msb: 121, lsb: 3 },
  { patch: 'E. Grand Piano', program: 2, msb: 121, lsb: 12 },
  { patch: 'Grand&MovingPad', program: 0, msb: 121, lsb: 9 },
  { patch: 'Grand & Strings', program: 0, msb: 121, lsb: 7 },
  { patch: 'Midi Grand & Pad', program: 1, msb: 121, lsb: 4 },
  { patch: 'Harpsichord 1', program: 6, msb: 121, lsb: 7 },
  { patch: 'Harpsichord 8+4', program: 6, msb: 121, lsb: 8 },
  { patch: 'ClassicClav1 DNC', program: 7, msb: 121, lsb: 7 },
  { patch: 'ClassicClav2 DNC', program: 7, msb: 121, lsb: 8 },
  { patch: 'Piano Layers', program: 2, msb: 121, lsb: 6 },
  { patch: 'Grand & FM Stack', program: 2, msb: 121, lsb: 7 },
  { patch: 'Piano & Vibes', program: 0, msb: 121, lsb: 6 },
  { patch: 'Grand Piano Demo', program: 0, msb: 121, lsb: 11 },
  { patch: 'Grand Piano', program: 0, msb: 121, lsb: 3 },
  { patch: 'Upright Piano', program: 0, msb: 121, lsb: 15 },
];

// Create menus and author label
var PluginParameters = [{
    name: 'Patches',
    type: 'menu',
    valueStrings: [
      'Concert Grand',
      'Italian Piano',
      'Jazz Piano',
      'Italian Jazz P',
      'Live Piano',
      'Rock Piano',
      'Pop Upright',
      'M1 Piano',
      'Pop Grand',
      'It. Piano',
      'Classic Piano',
      'Warm Grand',
      'Bright Piano',
      'It.Grand & Stack',
      'G.Piano Stack 1',
      'G.Piano Stack 2',
      'Honky-Tonk',
      'Ragtime Piano',
      'E. Grand Piano',
      'Grand&MovingPad',
      'Grand & Strings',
      'Midi Grand & Pad',
      'Harpsichord 1',
      'Harpsichord 8+4',
      'ClassicClav1 DNC',
      'ClassicClav2 DNC',
      'Piano Layers',
      'Grand & FM Stack',
      'Piano & Vibes',
      'Grand Piano Demo',
      'Grand Piano',
      'Upright Piano',
    ],
    defaultValue: 0,
    numberOfSteps: 4,
  },
  {
    name: 'MIDI Channel',
    type: 'linear',
    defaultValue: 1,
    minValue: 1,
    maxValue: 16,
    numberOfSteps: 15,
  },
  {
    name: author,
    type: 'text',
  },
];

function getParamsAndSendMidi() {
  // MIDI Channel variable
  const midiChannel = GetParameter('MIDI Channel');
  // Bank Select MSB
  bs.number = 0;
  bs.value = Patches[GetParameter('Patches')].msb;
  bs.channel = midiChannel;
  bs.send();

  // Bank Select LSB
  bs.number = 32;
  bs.value = Patches[GetParameter('Patches')].lsb;
  bs.send();

  // Program Change
  pc.number = Patches[GetParameter('Patches')].program;
  pc.channel = midiChannel;
  pc.send();
}

let done = false;

function ProcessMIDI() {
  if (!done) {
    getParamsAndSendMidi();
    done = true;
  }
}

// Send Program Change and Bank Select MSB/LSB when selecting a patch from the menu
function ParameterChanged(param, value) {
  getParamsAndSendMidi();
}

// Let MIDI messages pass through, control menu with a CC
function HandleMIDI(e) {
  if (e instanceof ControlChange && e.number === ccNum) {
    SetParameter(0, e.value);
  } else {
    e.send();
  }
}

J.

Link to comment
Share on other sites

That's very interesting.  I think this can be solved, but I don't have a mac to try anything with logging to figure out when ProcessMIDI is being called the first time.  ProcessMIDI will be called over and over again, once per process block, as soon as the project is loaded.  Unless the track is disabled initially perhaps or something like that.  I am not sure what may have changed in version 10.7.7 that would somehow case the midi to be eaten and not get sent out the first time ProcessMIDI is called.

Another thing to try, just thinking out loud now, inside the Initialize function, try setting done=false.  

But honestly I am fairly sure that ParameterChanged also gets called when the project is loaded, so really it should not have even been neccessary to use ProcessMIDI, it should have worked from inside ParameterChanged.  The question is why it's not working, somehow the midi is not quite enabled yet or something at the time those functions are getting called in Scripter..something like that.  It would be interesting to put some logging messages into each of those functions to see if its attempting to send the midi, even though you aren't seeing the midi sent.

Edited by Dewdman42
  • Like 1
Link to comment
Share on other sites

On 5/5/2023 at 3:53 AM, DjStiky said:

Thanks @Jordi Torres! Your script has been very helpful for my projects, so really appreciate it!

If you ever want to take a crack at further enhancements, let me know. I have an idea that would prevent me from having different copies of this for each category of patches, and prevent having multiple tracks/instances for each midi channel.

In theory, it would work this way:
- 1 instance where the GUI would have 16 sections (1 for each midi channel)
- Each of the 16 section would have 2 dropdown menus. First dropdown would choose "category" of patches, second dropdown would have the patch list for the chosen category.

This way, different Program Change and Bank Select MSB/LSB values would be sent to each of all the 16 midi channels from the same instance, all in 1 scripter plugin. Example:

Channel 1: Piano category and Concert Grand patch
Channel 2: Guitar category and Nylon Guitar patch
and so on...

@DjStiky,

I think it would be better to create a separate thread for that.

J.

Link to comment
Share on other sites

Another thing worth trying would be to have the program change delayed a little bit.  Try changing the script code to sendAfterMilliseconds or something...give it a bit of time and see if that solves the problem in 10.7.7.  It might take some MS's or it might take a second or two...  anyway its worth a try to see if the midi system just needs to be completely readied before attempting to send midi.

And if that doesn't work, then we could look at a more elaborate work around which would wait until ProcessMIDI has been called a number of times before trying to send out any midi message...again..waiting for logicPro to fully set itself up before trying to send any midi...  something like this:

var done = false;
var elapsed = 0;
var WAIT = 100;

function ProcessMIDI() {
   if(!done) {
       if(elapsed < WAIT) {
            elapsed++;
            return;
       }
       else {
            sendProgramChange();
            done = true;
       }
   }
}

And there are other work arounds I can think of to do something like that inside the ParameterChanged function, which is a better place generally to do this, but I don't have a mac to test it right now and I don't want to speculate too much, let's first see if any of the above two possible work arounds might get around Logic 10.7.7. issue.

 

Link to comment
Share on other sites

  • 7 months later...

I ended up just adding a button to send the current selected values, which clicking only takes an extra second after loading the project 🙂

{
   name: "Send MIDI",
   type: "momentary"
}
// Handle momentary button press
function HandleMIDI(e) {
   if (e instanceof ControlChange && e.number == ccNum) {
      SetParameter(0, e.value);
   } else if (e instanceof NoteOn && e.number == 64 && e.value == 127) {
      // Note: Change the button number (64) to the desired MIDI note number
      SendMIDI();
   }
   e.send();
}
// Send the currently selected MIDI values
function SendMIDI() {
   var midichannel = GetParameter("MIDI Channel") + 1;

   msb.number = 0;
   msb.value = Patches[GetParameter("Patches")].msb;
   msb.channel = midichannel;
   msb.send();

   lsb.number = 32;
   lsb.value = Patches[GetParameter("Patches")].lsb;
   lsb.channel = midichannel;
   lsb.send();

   pc.number = Patches[GetParameter("Patches")].program;
   pc.channel = midichannel;
   pc.send();
}

Thanks for the effort guys, really appreciate it!

 

Edited by DjStiky
  • Like 1
Link to comment
Share on other sites

 Nice to see you're learning scripter!

Try adding this to your script to see if it will automatically send the patch change for you soon after loading the script.

var done = false;
var elapsed = 0;
var WAIT = 100;

function ProcessMIDI() {
   if(!done) {
       if(elapsed < WAIT) {
            elapsed++;
            return;
       }
       else {
            sendMIDI();
            done = true;
       }
   }
}

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...