Midi sequence playback

So I am new potential user here, just playing around with 14 day trial. I whipped together a quick little test of midi sequence playback in GPScript…just a proof of concept…want to see if I am on the wrong track or anyone has done anything already…or tips about GPScript that might apply…

Here is simple little example, this just sends two NoteOn’s on beat one and two just to see how to schedule notes for playback, but theoretically, it could be expanded to hundreds or thousands of events, actually what I would ultimately do is write a separate program outside of GigaPerformer that basically will generate the script from a midi file.

Theoretically this will work fine as this demonstration shows…

But a couple questions

  1. Is there any reason I couldn’t call ScheduleMidiEvent and queue up 1000 events during Initialization and expect them to playback? Any limit to any many midi events can be queued up ahead of time?

  2. If the playback is stopped and restarted, is the internal queue also reset?

  3. Looks like currently GPScript does not have any kind of struct to hold related data in an array of structs or something like that… Am I missing anything?

There are some further optimizations that can be done, but just quick proof of concept it more or less works to schedule midi events, then its just matter of doing that once per beat (unless it can be done just a single time before hitting PLAY.

var iRig : MidiInBlock

var a : MidiMessage[5]
var t : Double[5]
var b : Integer[5]

var idx : Integer

Initialization

  b[0]=0;t[0]=0;a[0] := MakeNoteMessageEx(60,100,1)
  b[1]=0;t[1]=0;a[1] := MakeNoteMessageEx(62,100,1)
  b[2]=0;t[2]=0;a[2] := MakeNoteMessageEx(64,100,1)
  b[3]=3;t[3]=0;a[3] := MakeNoteMessageEx(66,100,1)
  b[4]=4;t[4]=0;a[4] := MakeNoteMessageEx(68,100,1)
    
  idx := 0
  
End

On BeatChanged(barNumber : Integer, BeatNumber : Integer, tick : Integer)

   if( BeatNumber >= b[idx] ) Then
       while b[idx] <= BeatNumber Do
           ScheduleMidiEvent(iRig, a[idx], t[idx])
           idx := idx + 1
       End
   End

End

Going to the other extreme, the Ableton Link feature allows you to use even the Lite version of Live easily with GP ( e.g. communicating with virtual audio or midi ports). Obviously depends on what you’re trying to achieve, but works well and very simple.

I do not know how big this queue can be, but arrarys are limited and therefore it is not possible
the initialize 1000 array elements.

For your example (no delay used) this would be better:

PlayNote(<p : MidiInBlock>, <noteNumber : integer>, <velocity : integer>, 
<channel : integer>, <startTimeMS : integer>, <durationMS : integer>)

what is the limit of GPscript arrays?

I will look at PlayNote, but that will be harder for what I want to do, because I intend to write (its halfway done already) an external program that can convert a midifie into a GPScript. Midifiles, have NoteOn and NoteOff events as they come, the notion of trying to figure out the note durations would make it more complicated.

I understand.

I am using Ableton Live do to such things or ToonTrack EZPlayer.

Yea I have other options too. right now I am trying to see what GPScript can do before I commit to buying the product. I have 11 days left on the trial. I think I can make a general script work and that is preferable since it can be completely controlled by widgets in GP. Could probably even handle tempo changes.

I have done this kind of thing already with LogicPro/Mainstage Scripter.

what is the max limit of arrays?

I think 128

oh man. ok.

GP Script was designed to let us experiment with concepts that could later be converted into built-in functionality, and although it does continue to evolve, it is not intended to be a general purpose programming language.

I don’t know what it is you’re trying to do but it sounds like you might want to consider a tool like Max in conjunction with Gig Performer. Gig Performer has a deep OSC implementation originally designed specifically to support this use case as I was originally using Max by itself for live performance before we built Gig Performer and I wanted to leverage the transformations I had implemented there rather than having to start from scratch.

For example, there is an abstraction called “GigPerformerSynthRx” which essentially have inputs that look similar to the Max [midiformat] object, e.g. you can send in numbers (or a list in the case of a note with velocity) but instead of producing MIDI, they get converted to OSC messages and sent to Gig Performer. In this first image, I’m holding the mouse over the port that accepts Aftertouch messages.

screenshot_1104

Here’s another example that I used to use to perform Comfortably Numb with a band. As you can see, there are abstractions to support such things as keyboard layering, velocity handling and so forth. It’s a very powerful way to leverage the midi processing engine of Max with Gig Performer’s rackspace and plugin management. It’s easy to use Max’s [seq] object as well to pull in MIDI events that can be sent directly to Gig Performer.

Although not perfect (because it was intended at the time for a very specific usage … clocking via fingers :slight_smile: ) there is a MidiSequence object in GP Script along with a collection of functions for stepping through it. It can load standard MIDI files.

I use it to step through by pressing notes on a keyboard but the OnBeat callback could probably be used as well.

1 Like

I will look at that thanks! I didn’t wanna ask because I remembering reading some API comments that said “do not even ask!”. :grinning:

Is there any limit to the size of that structure? For example, I have an example midfile here which is just a typical rock song, all tracks, with CC’s, etc…its over 13,000 midi events. Clearly it would be a PITA to try to rig up something with 128 sized arrays.

I am still evaluating GP at this point, I’m a script nerd so this is something I’m paying attention to. I have done a lot of scripting with MainStage/LogicPro and it has some nice benefits with javascript, but it does not have nearly as comprehensive of an API as you have done for GPScript. So kudos there.

Also the Scripter in MainStage cannot really access any widget and transport controls, etc… Its not even remotely possible to have a script control tempo for example, in MainStage. But I think a GPScript could do that, if I am not mistaken, well it would probably not be hard to add that to GPScript if its not there already, whereas with MainStage, good luck even getting an email to a developer much less getting them to do that.

Its possible I may not buy GP this time, I think its a fabulous product, but I don’t have a pressing need for live performing right now I am mainly just exploring it and the price is a little high for me with my needs…later on however if I start gigging again I will definitely consider buying it. I was only looking right now because PA has an incredible sale right now and I have coupon on top of that that expires tonight…so the price for their version is less then half the price as direct…so that is a low enough price to maybe just get it and play around with it, but what I don’t like is that they are not on top of the bug fixes dot dot releases. So maybe I will wait after all… but we shall see. I intend to use up my 11 days of trial playing around with GPScript though and I will post here whatever I come up with.

alright so question about MidiSequence_GetCurrentEvents. That returns an array of events, at the current playhead position? Its not clear exactly what would be returned to me, but I don’t think that will work for sequence playback because the OnBeat callback gets called once per beat…and basically GetCurrentEvents would need to return all events that start within the span of time from this beat to the next beat. And then we could call the schedule function with the ms delay set appropriately for each one, to schedule one beat’s worth of events.

I do love that this MidiSequence can read a midi file, which would completely eliminate my need to use an external program to convert a midi file to GPScript. But…I still don’t see how I could fashion a GPScript with it to simply playback the sequence from start to finish when you hit play on the transport of GP.

Realistically, I mainly need to manage smaller midi sequences…like a dozen or so Program changes per song…so just doing it the hard way with arrays would probably work. But I’d prefer a more generalized solution that can just open a midi file and play it back when I hit play on the transport, or from a widget. That way it will be easier to program each song by simply using a midi sequencer to program the sequence and keeping the GPScript more generic.

Yeah, sometimes stuff gets installed in there so we can experiment — as I might have mentioned earlier, one of the reasons we developed GP Script was so that we could try out ideas that might later make their way directly into Gig Performer for use by musicians without needing scripting. We never intended it to have a high profile.

However, we were extremely surprised that so many users started using GP Script to do things way beyond what we had intended and so as GP Script continues to evolve, many more functions and objects are starting to show up.

The MidiSequence object was developed for a very specific purpose, i.e. to allow certain chunks of MIDI to be played during a song using (typically) two fingers triggering two keys to “step” through a sequence. It’s not really experimental any more so we should just remove those warnings.

To do what you need however, we will have to add another function in that will execute the equivalent of “GetCurrentEvents” but based on how much time has passed.

With that in place, either through the On Timer callback or through one of the function generators, it would be possible to actually play a sequence.

No - you can pretty much load an arbitrary sized SMF file with format 0 or format 1 (multitrack) into it.

Sure - but arrays in GP Script were never intended for such things. They were really just intended to hold a few numbers (representing a chord, for example) or a few widgets (for iteration purposes).
Having fixed-size arrays is much more efficient from a memory allocation perspective, which is important in a real-time environment, particularly since they can come and go on the fly, perhaps every time a MIDI event is produced. We probably could come up with a decent mechanism to allow arbitrary sized global arrays but to be honest it just wasn’t a high priority.

We looked hard at just embedding some other language but I have some serious philosophical objections to languages like Javascript or Lua and I really wanted a more seamless integration, e.g. being able to declare a variable with the name of a widget and just using it rather than have to bind strings to variables at runtime, etc. I also wanted to have a clear separation between functions and callbacks.

Sure — SetBPM

Indeed :slight_smile:

If you’re going to make a new function…it should be able to obtain a little bit after the current playhead, so that a collection of midi events can be “scheduled” ahead of time. One beat should cover it. Most people shouldn’t have more than 128 events within a beat…hehe but never know… Or just provide an option to specify to the function the start and end time of the inquiry in either beats or ticks or both. Midifiles are by tick…with a PPQN value to derive a fractional beat from…as I’m sure you know.

But… the problem is that your MidiMessage objects don’t have a timestamp…so that actually won’t work either.

Regarding arrays I hear you. It can easily be possible to make RT thread problems if not careful. In what I was going to do, I was going to create the array elements during Initialization, so it wouldn’t matter if it does a lot of malloc during that. But you’re right if someone tried to do that during a RT callback, it could be problematic.

Dynamic Arrays are also limited to 128? Just curious.

What I do like about javascript in particular, is that I have quite a bit of programming power with JSON data structures and powerful array handling, etc… But you are definitely correct, the script-coder needs to know what to avoid during RT callbacks…

oh also, I guess this current MidiSequence paradigm might be mallocing stuff to return an array of notes per beat…which is basically something I would try to find another way to avoid that.

If you instead of an option to request playback from the MidiSequence object, then the array doesn’t have to be allocated and returned.

That two finger performance mode sounds really cool too though!