What's new

Logic Scripter—multiport track delay compensation?

the dropbox link isn't working...

using delay -500ms is working perfectly for me. Rendered audio is exactly 500ms earlier then the midi event.

it seems to work both with and without the auto compensation version (which I don't think you should be using anyway). I'm rather surprised that it came out the same both ways.
 
the dropbox link isn't working...

using delay -500ms is working perfectly for me. Rendered audio is exactly 500ms earlier then the midi event.

it seems to work both with and without the auto compensation version (which I don't think you should be using anyway). I'm rather surprised that it came out the same both ways.
I’ll look at the Dropbox link in the morning and make sure it’s working.

To clarify: You’re getting the -500 ms delay to work on the midi track itself? Not an instrument track or the audio track?
 
i don't think the track delay parameter it's broken at all. it works fine and as expected here. one thing worth noting that might be confusing: you can have a delay in ticks and one in milliseconds at the same time.

This is very interesting and thanks for pointing this out, which is probably kmaster's problem. I did find this only has both delays in effect if the ticks one is the currently selected one. If the ms delay is the currently selected one, then only the ms delay will be used. Definitely a bug. everyone report this. It should be consistent in any case, preferably just the visible one active at once.
 
Yes I used midi track, it connects to environment object, which is cabled to mixer strip, hosting EXS24 (which by default plays a sine wave sound). The output of the mixer strip goes to BUS1, which is used as the input for a new audio track which I record to.... Its rendered exactly 500ms early. See my last post quoting BabylonWaves comment from earlier in the thread though, that is probably why you were seeing confusing results.
 
And to repeat myself, I don't think you should be using the Auto Compensation mode, that is designed to compensate for your sound card when using external hardware synths, etc.
 
Yes I used midi track, it connects to environment object, which is cabled to mixer strip, hosting EXS24 (which by default plays a sine wave sound). The output of the mixer strip goes to BUS1, which is used as the input for a new audio track which I record to.... Its rendered exactly 500ms early. See my last post quoting BabylonWaves comment from earlier in the thread though, that is probably why you were seeing confusing results.
That's not what I'm getting at all.


(There are two projects in here. The one we're interested in is Test-project-MIDI-Delay-200815.)

In the Notes of the Project, I added what I should expect to find.

In the Notes of the Print Tracks, I added screenshots and explanations of what my settings were per print.

Screenset 1 shows the Notes, the audio file/Track view, the Arrange window, the Track inspector, and the relevant Environment page.

--

The results were these:
1. No delay offset = dead on the grid
2. -99 Ticks = -99 Ticks
3. -500.0 ms = dead on the grid
4. Auto = impossible (option greyed out)
5. -99 Ticks, but -500.0 ms also selected = -99 Ticks
6. -500.0 ms, but -99 Ticks also selected = dead on the grid


Does this project work differently on your system than it does here?
 
Very strange. It worked perfectly for me last night. Today I loaded your project and it didn't work properly. I couldn't see any reason why not. I tried to create a new one from scratch and now I can't get a new one to work properly either. No idea what I did differently last night that made it work properly. If I can figure that out I'll let you know.

its possible I accidentally had the track delay for the mixer channel set to -500ms in addition to the track delay for the midi track, which by the way works perfectly now too, but on the midi track it seems to be ignored.

@babylonwaves, do you have any further ideas about it? You said you had it working?

More in a minute...but anyway, yea...as of right now, I can't get midi track through environment to respect negative track delay either.
 
Another thing to keep in mind regarding VePro.AU3 is that its not possible to configure the AU3 midi port in the midi instrument environment object either, only channel. The "Port" attribute that you see on old school midi tracks is not the AU3 midi port, its in reference to external midi device ports... Its confusing the way they are named, but anyway just wanted to point that out.

That means that in order to use old school midi tracks with more than 16 tracks in one VePro instance in AU3, you have to do some squirley stuff in the environment in order to route the other ports. All doable, but its not totally straightforward, one of the templates I shared earlier is setup like that if you want to see how its done.
 
Stay tuned shortly for a Scripter script you might be able to use instead of banging your head against the wall with the old school midi tracks.
 
I found more bugs related to track delay in LogicPro...i guess this is what you said at the very beginning and I can confirm its still a problem. When you create multiple tracks going to a single multi timbral instrument, the same way NewTracksWizard does it, they each have their own track delay setting, but they are not honored and its totally not clear which one is the one that is honored, it appears to be the last one that was last created...or something strange like that...basically...its not reliable and I would not use it.

This brings us full circle now. I think the best way to handle this kind of situation is by using Scripter in conjunction with ExpertSleepers, LatencyFixer plugin.

LatencyFixer download here: https://www.expert-sleepers.co.uk/legacy_downloads.html

Instructions
  1. Create Instrument Track with VePro.AU3 on it

  2. Add Scripter to that channel and add the script shown at the bottom of this post.

  3. You will need to edit the script to set the known latency (in ms) for each port/channel. For example, the following edit would set port1, chan1 to 50ms latency; port1-chan2 to 30ms latency, etc. After editing the script, make sure to press the RunScript button.

    JavaScript:
    var latencyByChannel = [
     [ 50, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 1
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 2
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 3
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 4
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 5
    .
    .
  4. Look on the Scripter GUI and you will see a Lookahead value reported, which should be the largest latency you are configuring across all 768 possible midi port/channels.

    lookahead.jpg


  5. Add LatencyFixer to an Audio FX plugin slot on the same channel as VePro.AU3 inst. Configure it to the same value reported as Lookahead. You will have to convert from ms to seconds in this case (divide by 1000).

  6. To add more tracks, use New Track with Next Channel command in LogicPro. More information about building large VePro AU3 templates can be found here: https://www.logicprohelp.com/forum/viewtopic.php?f=9&t=143416
JavaScript:
/*************************************************************
 * Latency Correction By Port/Channel
 * v1.02
 *
 * Specify the KNOWN latency for each port/channel in the array
 * below.  Use in combination with Expert Sleepers LatencyFixer
 * to create enough lookehead to handle the largest reported
 * latency here.
 *
 * Only NoteOn events will be sent early, all others will remain
 * exactly on time as they are in the region.
 *************************************************************/

var latencyByChannel = [
 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 1
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 2
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 3
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 4
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 5
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 6
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 7
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 8
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 9
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 10
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 11
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 12
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 13
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 14
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 15
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 16
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 17
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 18
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 19
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 20
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 21
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 22
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 23
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 24
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 25
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 26
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 27
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 28
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 29
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 30
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 31
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 32
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 33
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 34
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 35
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 36
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 37
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 38
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 39
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 40
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 41
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 42
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 43
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 44
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 45
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 46
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 47
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 48
];

/************************************************************
 * Scan Array to find largest latency and report as lookahead
 ************************************************************/
var lookahead = 0;
for(let port=1;port<=48;port++) {
    for(let chan=1;chan<=16;chan++) {
        if(latencyByChannel[port-1][chan-1] > lookahead) {
            lookahead = latencyByChannel[port-1][chan-1];
        }
    }
}

/******************************************
 * return boolean indicating NoteOn events
 ******************************************/
Event.prototype.isNoteOn = function() {
    return false;
};
NoteOn.prototype.isNoteOn = function() {
   if(this.velocity <= 0) return false;
   else return true;
};

/******************************************
 *HandleMIDI
 ******************************************/
function HandleMIDI(event) {
    if(event.port == undefined || event.port == 0) {
        event.port = 1;
    }
    
    let latency = latencyByChannel[event.port-1][event.channel-1];
    let delay = lookahead - latency;
    
    // Only send NoteOn events early, all others delay for entire
    // lookahead amount.
    
    if(event.isNoteOn()) {
        event.sendAfterMilliseconds(delay);
    }
    else {
        event.sendAfterMilliseconds(lookahead);
    }
}

/***************
 * GUI
 ***************/
 
var PluginParameters = [];
PluginParameters.push({
    type: "text",
    name: "Lookahead = " + lookahead + " ms"
});
That's it. Should work as intended....
 
Last edited:
In the meantime, if I get any clarity on how to make the track delay work properly with either old school midi tracks or new school multi-timbral tracks...I will let you know, but so far...that appears to be pretty buggy in LogicPro when dealing with multi-timbral instruments. :emoji_angry:
 
oh also, if you still want to use old school midi tracks for other reasons such as the mute/solo buttons, etc..you still can with this approach. Just make sure not to use the track delay for setting negative delay. Always use Expert Sleepers to avoid any LogicPro bug confusion.. Let me know if you want some guidance setting up a project with old school midi tracks. Using that approach you can actually have all 768 tracks into one VePro instance!
 
Thinking about this topic of multi-timbral sample latency correction, including per-articulation; a bit over night and I want to make some additional comments and observations about the issue and some of the common solutions(none 100% perfect).

Goal

So the goal for this conversation is how to set things up so that you can quantize notes to the grid and not have to nudge them early to compensate for slow attack samples, or poorly constructed sample instruments with late sample start times.

Solutions
  1. Main solution usually offered is to use negative track delay for each track.

  2. Secondary solution is to use something like LatencyFixer to induce PDC into starting notes early.

  3. In LogicPro, Scripter can be used to move midi event times later on a case by case basis, which when combined with the above two approach can possibly get around bugs in LogicPro and/pro provide finer grained control over the correction.

In thinking this through, I find that there are pros and cons of each approach. All three approaches have some potential downsides, some are easier then others to setup, I will comment on each one now.

Negative Track Delay

On the surface this is the easiest solution, and the solution most often cited. If the DAW supports this, then you can determine how latent the sample instrument is (without reporting plugin latency), and just use a negative track delay setting to correct for it.

Here are the problems I see with this approach:
  1. In the case of LogicPro, we have identified in this thread some buggy behavior with multi-timbral instruments and perhaps with midi tracks where it simply doesn't work as expected.

  2. Having all midi events start early is not really what you want either. As it turns out, NoteOff events should specifically NOT start early. The problem we are correcting for is the sound of the note not starting immediately, but when you release the key, it will end immediately. So actually, you need NoteOn events to be started early and NoteOff events to be executed exactly on time as expected.

  3. Other kinds of midi events also need to not be early. For example CC, PitchBend, Aftertouch and probably ProgramChange events need to be exactly on the timing you expect them to be, not early. So the negative track delay feature would cause all of them to be too early also. The real problem is only NoteOn events are late and need to be made earlier, all other midi events need to remain exactly on time.

Expert Sleepers LatencyFixer Plugin

This free plugin can be used to report latency to the host. Since we can basically determine that a particular sample instrument is introducing some start time latency but not actually reporting that to the host, the use of LatencyFixer can simply report that latency, then PluginDelayCompensation (PDC) will do the rest. Right?

Yes that is actually easy solution, no more LogicPro bugs related to track delay setting. Additionally, this plugin can theoretically be used inside VePro mixer itself, so that you can report the latency of each instrument in the VePro mixer and VePro will align all the audio there before bringing it all back to LogicPro. Hm...that is interesting.

Still some problems...
  1. Similar as the negative track delay, this will cause all other midi events, including NoteOff to be too early. The real problem we need to solve is making only NoteOn events start early. All other events need to be exactly on time.

  2. Because you can only put one instance of LatencyFixer on the instrument channel, you get only one global PDC correction for the whole multi-timbral instrument (entire VePro Instance). At least with negative track delay, if it actually worked right, you can time align each source track independently. Unfortunately it doesn't work right anyway. (however, as noted, you can theoretically use LatencyFixer inside the VePro mixer in order to align each instrument with different amounts of correction.)

LogicPro Scripter

A custom Scripter solution seems like perhaps the best way to deal with this issue, since we can delay different midi events by different amounts. Also it potentially provides a way to have a look at articulationID and provide different amounts of latency correction per articulation! Right?

There are problems with this approach too.
  1. While scripter can easily make sure that NoteOn events are early and NoteOff events are left on time, there are other problems now because legato instruments often require fairly precise overlapping of NoteOff to the next NoteOn in order to establish legato and play the legato transition. So when you have Scripter monkeying around with NoteOn and NoteOff timing differently from each other, this may or may not cause problems with certain legato instruments.

  2. As noted earlier, CC and other event types need to not be early, they typically need to be exactly on time where expected. But what if the CC or PitchBend event specifically needs to be set before actually sending the NoteOn to the instrument for some reason? This could particularly be an issue if you are using CC as an articulation switcher, for example. In that case the CC event needs to be sent early also just like NoteOn. See the dilemma? How do you know when to send these other kinds of midi events early or not? Maybe there is a clever solution, but I haven't thought of it yet. You could simply avoid using CC articulation switches at all cost, which might get rid of this problem well enough, however.

  3. When attempting to use different latency per articulationID, then it becomes even more complicated. Different amounts of latency correction per articulation, but the keyswitches all need to in front of the notes, including if the switches are CC...and meanwhile non-switch midi events need to be exactly on the time they are supposed to and don't forget about legato transition issues, especially now we're dealing with potentially different timing correction per articulation.
Summary

So... bottom line is that this problem can be a bit complicated and so far I don't see a simple solution that really takes care of it quite perfectly. All of these problems are made more complicated when using VePro, PLAY or Kontakt in multi-timbral modes. You can get kind of close to the right answer and maybe good enough depending on your project, but still there are some interesting little details to be aware about related to LogicPro bugs, non-note midi events needing to be on correct timing and potential legato transition problems.
 
Last edited:
Why fix that when you can add some features borrowed from Ableton Live instead ...?
Horses for courses but I am loving the new Step Sequencer for my electro stuff. Might be interesting to use for ostinati too.

I do wish they'd fix the PDC bug with track automation though.
 
I am not talking for or against any features added to Logic, Cubase or any DAW, I just find it sad that almost all of them seem to keep adding stuff before fixing things. Regarding the features added: sure! That is up to personal preference! I just picked that example, because IMO Logic tries to integrate that concept of Ableton Live into their own. Some people might like that, but the more a software tries to be everything, the more it may lead to bugs, problems and an overwhelming number of features.

I could say the same thing about Cubase and many of their recent additions like pitch correction? I would have preferred, had they just implemented ARA for the market leader Melodyne! They did that later, of course, maybe due to many people asking for it. But it shows again, the DAW developers should focus more on what their target audience wants instead of trying to do things, other programs are better at or great plugins exist to buy for.
 
I found more bugs related to track delay in LogicPro...i guess this is what you said at the very beginning and I can confirm its still a problem. When you create multiple tracks going to a single multi timbral instrument, the same way NewTracksWizard does it, they each have their own track delay setting, but they are not honored and its totally not clear which one is the one that is honored, it appears to be the last one that was last created...or something strange like that...basically...its not reliable and I would not use it.

This brings us full circle now. I think the best way to handle this kind of situation is by using Scripter in conjunction with ExpertSleepers, LatencyFixer plugin.

LatencyFixer download here: https://www.expert-sleepers.co.uk/legacy_downloads.html

Instructions
  1. Create Instrument Track with VePro.AU3 on it

  2. Add Scripter to that channel and add the script shown at the bottom of this post.

  3. You will need to edit the script to set the known latency (in ms) for each port/channel. For example, the following edit would set port1, chan1 to 50ms latency; port1-chan2 to 30ms latency, etc. After editing the script, make sure to press the RunScript button.

    JavaScript:
    var latencyByChannel = [
    [ 50, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 1
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 2
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 3
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 4
    ,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 5
    .
    .
  4. Look on the Scripter GUI and you will see a Lookahead value reported, which should be the largest latency you are configuring across all 768 possible midi port/channels.

    lookahead.jpg


  5. Add LatencyFixer to an Audio FX plugin slot on the same channel as VePro.AU3 inst. Configure it to the same value reported as Lookahead. You will have to convert from ms to seconds in this case (divide by 1000).

  6. To add more tracks, use New Track with Next Channel command in LogicPro. More information about building large VePro AU3 templates can be found here: https://www.logicprohelp.com/forum/viewtopic.php?f=9&t=143416
JavaScript:
/*************************************************************
* Latency Correction By Port/Channel
* v1.02
*
* Specify the KNOWN latency for each port/channel in the array
* below.  Use in combination with Expert Sleepers LatencyFixer
* to create enough lookehead to handle the largest reported
* latency here.
*
* Only NoteOn events will be sent early, all others will remain
* exactly on time as they are in the region.
*************************************************************/

var latencyByChannel = [
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 1
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 2
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 3
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 4
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 5
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 6
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 7
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 8
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 9
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 10
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 11
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 12
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 13
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 14
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 15
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 16
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 17
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 18
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 19
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 20
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 21
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 22
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 23
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 24
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 25
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 26
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 27
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 28
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 29
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 30
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 31
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 32
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 33
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 34
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 35
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 36
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 37
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 38
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 39
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 40
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 41
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 42
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 43
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 44
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 45
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 46
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 47
,[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // port 48
];

/************************************************************
* Scan Array to find largest latency and report as lookahead
************************************************************/
var lookahead = 0;
for(let port=1;port<=48;port++) {
    for(let chan=1;chan<=16;chan++) {
        if(latencyByChannel[port-1][chan-1] > lookahead) {
            lookahead = latencyByChannel[port-1][chan-1];
        }
    }
}

/******************************************
* return boolean indicating NoteOn events
******************************************/
Event.prototype.isNoteOn = function() {
    return false;
};
NoteOn.prototype.isNoteOn = function() {
   if(this.velocity <= 0) return false;
   else return true;
};

/******************************************
*HandleMIDI
******************************************/
function HandleMIDI(event) {
    if(event.port == undefined || event.port == 0) {
        event.port = 1;
    }
  
    let latency = latencyByChannel[event.port-1][event.channel-1];
    let delay = lookahead - latency;
  
    // Only send NoteOn events early, all others delay for entire
    // lookahead amount.
  
    if(event.isNoteOn()) {
        event.sendAfterMilliseconds(delay);
    }
    else {
        event.sendAfterMilliseconds(lookahead);
    }
}

/***************
* GUI
***************/

var PluginParameters = [];
PluginParameters.push({
    type: "text",
    name: "Lookahead = " + lookahead + " ms"
});
That's it. Should work as intended....

Admittedly I am a little fuzzy on the exact order of MIDI flow between the Scripter and Articulation Sets, so this might not work...but along these lines, why couldn't we tie to the value of an unused CC (say, 111, or whatever works) as a MIDI negative delay value, and in the Scripter set the ms multiplier and the MIDI handling functionality? That way we don't have to worry about declaring a huge unlabelled array of various values...?

So, for instance, Library A has staccatos on Channel 2 that sound 60ms behind the noteOn and Library B has staccatos with Keyswitch B-2 that sound 80ms behind the noteOn. In the Scripter, we could say ms multiplier = 4, so it's looking 508ms ahead, then whenever Library A sends Articulation Set A sends channel 2, CC111 val 15 or Library B sends Articulation Set B sends B-2, CC111 val 20, each staccato would be heard at the correct negative delay.

This could even get more granular with MSB/LSB values, but for now...would this even work?
 
Last edited:
Great minds think alike...

I have another script laying around here that does almost exactly what you just described. I never shared it because I felt it might be too complicated for most people to want to mess with it. Also I have since moved on to trying to make a more all-encompassing script that does "everything" all in one script...but its not done and its considerably more complicated. But anyway, I prototyped something that does exactly what you just described so that you can embed the CC into the articulation set, etc..just like you described. I can't remember now if I ran into some little problem or if it was 100% working, I will have to go find it and get back to you. I think it was handling both the MSB/LSB possibility (using two CC's) as well as a multiplier, but the MSB/LSB idea was more difficult I think because of the way Articulation Sets work..

Like one thing, Articulation Set's will not send a key switch repeatedly if it believes it has already been sent and hasn't been trumped by a different one. its not always clear when it will send the CC or not.. So I think I may have run into an issue where that uncertainty made it unreliable. But I can't remember now. I will dig up whatever I had and send your way to try it out.

Sidenote

the overriding problem with these Scripter solutions is the lack-of-GUI. Simple scripts are fine, but when you get into more complicated ones, and particularly with VePro involved, you could have many tracks funneling through one single Script on its way to VePro...so then you need a GUI that has dozens or hundreds of data points to edit...completely not feasible with Scripter's GUI features.

The alternative is to send JSON data to the script, like I did in the above script as the big-ass array. Imagine one that is even bigger with a lot more data points. Then you use an external GUI editor to edit that structure. That opens up the possibility to having much more control and a really nice GUI, but the script itself would have an arcane JSON data structure to edit until such time a GUI actually exists. :-(

The CC approach here, allows the Articulation Aet itself to be that GUI...for this particular thing. But it also relies on the articulation set actually sending all those switches, in exactly the right order, etc. For example, with this approach the delay CC has to be the first key switch.....because the other key switches also need to be handled with the same negative track delay as the note itself. That's part of why I didn't share it publicly..too complicated to explain all the precise rules that people would need to follow in configuring their articulation set to make it all work. Too many ways to get it wrong and create frustration and chaos on the internet about it.

Anyway let me see if I can find it, I'll PM it for you to try out
 
Top Bottom