Need some help with MIDI bug

I’m currently running out of ideas what my bug could origin from. There is a non-zero chance of it being a JUCE-bug, so I decided to ask for help here.

In this Bitwig screenshot you see 2 tracks. The top one shows 2 notes, or in other words 2 MIDI events, one with noteOff, one with noteOn. below there is bounced audio from that track running through my plugin, which currently uses both noteOn and noteOff values to retrigger the phase of a ramped oscillator and play it back into the audio stream for debugging purposes.

You can observe that it retriggered before the noteOff, which is weird(!), but then also perfectly in time with the noteOn, just as expected.

I first thought Bitwig had a weird MIDI handling and maybe jitters it a bit as it’s being passed to the plugin chain, because the behaviour seemed slightly different when I tested it in Cubase. But other plugins have been found to react accurately to all MIDI messages of my test setup, so then I thought maybe it is a JUCE issue. I upgraded to the latest commit of the develop-branch, but the issue still persists, so it’s either not a JUCE issue, or one no one found yet.

I wanna share with you the code that I use to synthesize this test signal. It’s super simple and unlikely the cause of the trouble, but just in case:

struct NoteOnOffTestSynth
{
	NoteOnOffTestSynth() :
		phasor(),
		env(0.)
	{
		phasor.setFreqHz(1000., 1. / 44100.);
	}

	void operator()(float* const* samples, const MidiBuffer& midi, int numChannels, int numSamples)
	{
		auto s = 0;
		for (const auto it: midi)
		{
			const auto msg = it.getMessage();
			if (msg.isNoteOnOrOff())
			{
				env = 1.;
				phasor.phase = 0.;

				const auto ts = it.samplePosition;
				while (s < ts)
				{
					env *= .999;
					const auto phs = phasor();
					const auto wave = std::cos(phs * math::Tau) * env;
					samples[0][s] = wave;
					++s;
				}
			}
		}
		while (s < numSamples)
		{
			env *= .999;
			const auto phs = phasor();
			const auto wave = std::cos(phs * math::Tau) * env;
			samples[0][s] = wave;
			++s;
		}
		for(auto ch = 1; ch < numChannels; ++ch)
			for (auto s = 0; s < numSamples; ++s)
				samples[ch][s] = samples[0][s];
	}

	Phasor phasor;
	double env;
};

If anyone has an idea how I could find out more details about this problem, let me know. Because if it’s not a JUCE-related problem I’m super running out of ideas right now

some startSample not take into account in juce synth Voice::processBuffer ?

i’m not using any elaborated voicing classes. just that code, which synthesizes impulse-y waves from noteOns and noteOffs. and I wanna know why noteOff messages are given to the plugin like they were negatively delayed with no latency-inducing plugin involved

ok this thread can be closed! i just realized i should have written

env = 1.;
phasor.phase = 0.;

after that while loop. that one was sneaky, but also kinda stupid^^