MIDI Player/Transport bugs & info requests
-
I am working on a midi project, I'd like to get some info on non-documented methods.
I'm also reporting some bugs that will be pushed as git issues.DOC INFO:
What are these functions supposed to do exactly and how to use them properly?-
setEnabledGrid
andMidiPlayer.setSyncToMasterClock
since they are related -
sendGridOnNextCallback
. I might not fully understand what the grid is finally... -
startInternalClock
andstopInternalClock
. Something seems to happen but I can't understand what they are doing. Also it seems I have strange bugs like the impossibility to stop a loop once it is playing (in fact it stops but plays again without asking at next beat or so...) (probable conflict with syncMode, at least in my head...)
BUGS
-
Next midi player loop misses the last beat and de-sycn from tempo a bit more at each occurence if Loop Enabled. Similarly it strangely misses the first beat if Loop is disabled, but since it shouldn't even loop…
-
...Loop is Always activated no matter the button state (but still, something happens seeing the bug above)
-
Engine.getPlayHead()
throws an empty object. It might not be populated and related to this thread -> source-change-request. And why a playHead thing is part of theEngine
class?
SIDE QUESTIONS
-
How to properly sync a loop with DAW? I mean I can get it to play, and resync using a beatChanged callback. But this means it doesn't necessarily resync on first beat of next bar. Or should I just make a flag and use the isNewBar property?
Or should I compute the DAW bar pos (or get it fromEngine.getPlayHead
) and feed it to the midi player as a 0-1 based playback pos? -
Is MidiPlayer.setGlobalPlaybackRatio() a good method for playing at different tempo (DAW, local, file...). I can get it to work but not sure it is safe enough since the DAW sends it's tempo and here I just compute a ratio of my own to make the playing right. Is there no direct way of setting the playback tempo instead?
-
-
How to properly sync a loop with DAW?
Sync the MIDI player to the HISE master clock and then use the external sync mode, then you don't need to do anything else for DAW syncing.
-
@Christoph-Hart And when I want to play at a different tempo than the DAW one? Is the playback ratio the solution?
-
No then I would switch the HISE clock to internal and change the BPM (I think the default Engine.setHostBpm() should work there).
-
@Christoph-Hart Thanks! Trying...
-
@ustk There's a lot of complexity (the good kind) in the API here. I've been a little overwhelmed myself, and have some questions – but take a look at the code examples in the docs and in the forum. It's helpful that there's a sequencer right there in the interface designer, and you can test stuff out without exporting to a plugin.
One use of the grid stuff is for things you want synched to a clock, but in different units than the bpm - like an LFO or delay.
I've encountered the first two issues you described, but (at least for me), I'm not convinced yet that it's not simply my own ignorance of how to use these things. But it's good to know I'm not alone - we can probably figure a bunch of this stuff out together.
In terms of your getPlayHead(), see my post about it from the other day - it sounds (see what I did there) like everything you're trying to do can be done without it - but if you want it, simply replicate what I posted, and recompile HISE. But the TransportHandler is easier and more efficient if it does what you need — premade callbacks are dope. I'd suggest focusing on that as @Christoph-Hart indicated.
If you're still having problems, post a description of exactly what you're trying to do, and I'll try to help.
-
@Christoph-Hart said in MIDI Player/Transport bugs & info requests:
No then I would switch the HISE clock to internal and change the BPM (I think the default Engine.setHostBpm() should work there).
The Hise clock, it it the same as changing the gridsync to prefer internal?
Cause that's what I'm doing and the Engine.setHostBpm() is not changing anything in that case? -
So two things I narrowed down a wee bit:
-
The loop end seems to happen after the last note-off event, meaning that if you have only one note, let say the first beat for a quarter duration, it's looping back to the beginning at the second beat instead of lasting 3 more beats of silence.
-
A side effect of this is that the NbBars property in the midi file metadata depends on this duration, no matter it is containing 4 beats. (So even if you round this information you never know what you get since a 4/4 bar can be rounded to 0 if you have only one short note...)
Side question:
I have seen 4/4 measures having a last note off exceeding the bar duration, leading to a bar length (NbBars) > 1
What happens to that note-off after looping? -
-
@Christoph-Hart Alright, so the early loop end issue seems to come from the fact the EndOfTrack event isn't read...
It should also be used for the NbBars calculation -
@Christoph-Hart What about the loop that is always enabled, and the buggy looping point? (not playing the first/last beat depending on loop on/off)
-
@ustk Can you upload the MIDI file that doesn't read the end of track?
There are three checks for getting the end time as you can see here:
double HiseMidiSequence::getLengthInQuarters() const { SimpleReadWriteLock::ScopedReadLock sl(swapLock); if (artificialLengthInQuarters != -1.0) return artificialLengthInQuarters; if (signature.numBars != 0.0) return signature.getNumQuarters(); if (auto currentSequence = sequences.getFirst()) return currentSequence->getEndTime() / (double)TicksPerQuarter; return 0.0; }
So it first checks if the user has set an artificial length to override the actual sequence length, then it uses the NumBars property if it's defined (which I think I am doing all the time so I don't see this issue) and as last resort it checks the timestamp of the last event in the sequence (which is a JUCE method and should also find the end of track event and not only the last note off).
-
@Christoph-Hart Yeah I've searched a lot around that end of track event to see if it was read by Hise but I wasn't able to go further.
I am speaking about the last note off, but I either made a mistake or something because I'm not able to reproduce this.
I remember now, the last note OFF is another issue. It is utilised to output the track length while it should be the end of track event...What I am able to confirm 100% of the time is that it misses either the last portion of the last bar or the start of the first bar depending on the loop on/off state...
I tried multiple files from @gorangrooves library to my own quick export from Ableton, but same issue...
I wanted to see if it was a End Of Track problem, I don't think they can all miss that event but I haven't found a tool that can dissect what's really insideIt's strange nobody noticed such an obvious issue till now so I wonder if it's not my built that has a problem
if the user has set an artificial length to override the actual sequence length
Not my case
then it uses the NumBars property if it's defined
How do you do this? Shouldn't it be automatically exported with the MIDI file from the DAW?
as last resort it checks the timestamp of the last event in the sequence (which is a JUCE method and should also find the end of track event and not only the last note off).
That's what I thought so either I'm not lucky and the files don't contains an end of track event, or there's a bug here...
Could someone send me a simple 4/4 file to check if it works on my end, please?
-
@ustk Alright I've taken a look at this and it appears that the MidiFiles contain no information about the actual end of file so it uses the last note off by default. Rounding this up to the full bar is a simple job with scripting (and I think I did this in all my projects without thinking too much about it :)
const var MIDIPlayer1 = Synth.getMidiPlayer("MIDI Player1"); MIDIPlayer1.setSequenceCallback(function(mp) { var s = mp.getTimeSignature(); if(isDefined(s)) { s.NumBars = Math.ceil(s.NumBars); mp.setTimeSignature(s); } });
If you're using the sequence callback already, pass in a broadcaster instead of the function directly, then make sure that this is the first function the broadcaster calls.
Now the
LoopEnabled
button is indeed broken (and the fact that if you disable the loop, it keeps on looping but misses the first beat is some regression issue (I just didn't use the MIDI Player without loop enabled at all). -
@Christoph-Hart said in MIDI Player/Transport bugs & info requests:
Now the
LoopEnabled
button is indeed brokenIs this related to the broken loop enabled button in the sample editor too?
-
@d-healey no, but loop enable states seem to become a general issue in HISE :)
-
@Christoph-Hart Thanks Christoph that's awesome!
A strange thing the files don't contain an EndOfTrack meta-message since it is stated by the midi normalisation and they are exports from different DAWs... I couldn't find a tool to show me what's aside from the on/off notes and controllers. Thinking about it, it would be a nice addition for a Hise custom panel or just an API that throws a meta content object. I'll see if I can make something interesting since JUCE reads the EndOfTrack and probably other kinds of meta...
I'm not using the setSequenceCallback in the project so I guess I don't need a broadcaster, right?
I imagine it's for a priority reason and I don't know what it should be linked to anyway... MidiPlayer maybe? -
@ustk you can pass in a broadcaster instead of a function into any callback slot and then it will send a message to all its listeners (think of it as a distribution tool for events).
-
@Christoph-Hart The code you provided works if we assume
NumBars
is below the length we need. But then if a note off exceeds the required bar length thenceil
will add an empty bar... That is the test I have made previously for getting the right sequence length.But I assume there's no other way to get the length if the EndOfTrack is missing, so the loop creator has to make sure no noteOff exceeds the last bar, am I right, Christoph?
-
@ustk if there is no end of track marker (and actually I don't see anywhere in the JUCE code where that would be parsed), then the length of the MIDI file is the position of the last note off, that's basically the definition of the length :)
If you know how long you want it to be, you can just set it to the fix value, or you can use Math.round() if you want to round down values, but then you would also have to iterate over the sequence and make sure that no timestamp lies beyond that.
so the loop creator has to make sure no noteOff exceeds the last bar, am I right, Christoph?
Yes this would be the most sane approach.
-
@Christoph-Hart yeah everything seems quite logical, it's just weird the length isn't something plainly written but something that has to be calculated.