High latency: notes lingering on stop


#1

Hi there.

I’m just getting my feet wet with Juce. While I’ve found most features to be incredibly useful, nicely documented and really quite intuitive, there’s one issue I can’t crack yet. Any help would be hugely appreciated.

I’m working on a MIDI plugin with high latency. Let’s call it 100000 samples. Everything works fine during regular processing but when playback stops, the plugin keeps working on the remaining queue for another 100000 samples, resulting in extra unwanted notes for a while. However, I’d like everything to cease immediately. I’ve tried using prepareToPlay and releaseResources to coordinate various triggers to stop the processing, empty the queue and/or reduce latency to 0, but due to the timing of these function calls they don’t seem to make any difference at all.

Any suggestions? Thanks in advance. :relaxed:


#2

we can’t help you without some code to look at.


#3

Did you actually make the plugin report its latency? If you do then the host will delay everything else by this amount to keep it all in sync.

At 100,000 samples the entire system will become ridiculously unplayable, but the timing should be correct.


#4

Thanks so much for the replies Jules and Matkat. I’ll put some code up shortly.

Yes, the plugin reports its latency and the output is indeed perfectly synchronised when delaying the input by the same amount. In this situation I’m not at all worried about playability because this particular mode is for playback only. It does require an extremely high latency (usually more like 500,000 samples) to work properly.

The only problem I’m having is that when I hit stop, it takes that many samples to settle down again, so there’s an incredibly long tail of unwanted events from the stopping point until the queue is processed and empty again, 10 seconds or so later. Instead of this, I’d like to flush the buffers immediately once the stop message has been received. Is there any way to do this?

My current strategy is to set a flag in releaseResources to tell processBlock to do various combinations of:

  • stop processing any further queue items
  • delete all existing queue items
  • set the latency to 0

I’ve even tried doing the same sorts of things in prepareToPlay. Admittedly quite heavy-handed, but still nothing ever seems to do what I expect when I expect it. Of course, I wouldn’t be surprised if I’m doing it all wrong.

Any thoughts?


#5

Here’s the basic code in question (that aims to do precisely nothing in this instance):

void ScoutAudioProcessor::releaseResources() {
  stopped = true;
}

void ScoutAudioProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& incomingMidi) {
	if (!outgoingMidi.isEmpty()) outgoingMidi.clear();

	if (stopped) {
		if (!queue.isEmpty()) queue.clear();
		stopped = false;
	}

	if (!incomingMidi.isEmpty()) {
		queue.addEvents(incomingMidi, 0, getBlockSize(), getLatencySamples() + timeCounter);
	}

	if (!queue.isEmpty()) {
		outgoingMidi.addEvents(queue, timeCounter, getBlockSize(), -timeCounter);
		if (!outgoingMidi.isEmpty()) queue.clear(timeCounter, getBlockSize());
	}

	incomingMidi.swapWith(outgoingMidi);

	timeCounter += getBlockSize();
}

bool stopped, MidiBuffers queue and outgoingMidi and int timeCounter are declared and initialised to sensible defaults in the header. setLatencySamples(500000) is called in the constructor.


#6

Well of course everything your plugin does will be delayed - it’d be impossible to do anything else without a time-machine!


#7

Haha well I’ve had it working fine on other plugin platforms, so that’s obviously a slight over-generalisation. :wink:

Sorry if I’ve been unclear. Not everything is delayed. The plugin just saves events up to release them at the right time (obviously with suitable transformations applied to them in a real-world scenario). But the incoming event receiver in processBlock should still be right up-to-date, shouldn’t it?

Are the transport start/stop messages also delayed by this amount before they get to processBlock? I can’t think why they should be. EDIT: hmmm… unless that’s a limitation of VSTs under latency, of course. That’s a bit of a deal-breaker.


#8

I’m a little confused by what you’re asking then…

releaseResources is called when it’s time to completely reset your plugin’s state, so of course you should clear stuff like this when that happens. You don’t need a ‘stopped’ flag, just clear your queues in releaseResources and/or prepareToPlay and it’ll achieve the same thing.


#9

It’s debatable about whether the host should put a delay on the play state it gives you, or just the time values. Technically it should delay the state, because otherwise things could get out of sync.


#10

Sorry Jules, I think I must be doing some fairly eccentric things with my plugins, because nobody ever seems to get exactly what I’m on about. :smile:

And thank you, of course you’re right about that boolean. I had changed the structure around a few times, trying different angles on the problem, but your suggestion is the most straightforward approach, although none of it makes any difference anyway so it’s all redundant code.

At least for now it’s all working rather well aside from the latency jetlag on stop. It’s a vaguely annoying cosmetic detail I was hoping to eliminate early in the piece. I’ll test out different VST host settings tomorrow and see how I go with those.

Thanks!


#11

Oh that’s very interesting. I’d imagine making it an option would be a good compromise.

Food for thought! Thanks for all your helpful posts!


#12

I doubt if any hosts would want to bother adding an option like that TBH… Which host are you actually testing with?


#13

I’m using Reaper mostly.


#14

Well, silly me, it turns out I was using the wrong tool for the job. :smile:

As a potentially helpful note to others (or me again in a few weeks’ time), when I checked the value of isPlaying() from AudioPlayHead::CurrentPositionInfo to detect transport start/stop everything worked perfectly. Yay!