Get Current BarsBeatsPosition

Dear Steinberg Team,

Is there a way to extract the current position of the project cursor in terms of Bars and Beats (as presented to the user).
Currently I can only get in terms of quarter notes, but when changing time signature along the track, I cannot extract from the quarter notes alone the current bar.

(Or something similar to AAX’s GetBarBeatPosition().)

Kind Regards,
Erez

1 Like

Hi,
what’s your use case for this ?

Cheers,
Arne

I need to know the current bar when a user changes the playback cursor to a specific time.
My plugin needs to be synced with the bars and if the time signature changes along the session, I cannot extract the bar from the given info(current quarter note + current time signature).

Any help would be appreciated.

Thanks,
Erez

We’ve always wanted this, too. Our graph can show the bar/beat, but if they don’t start “recording” into our graph at time 0, then any time sig/tempo changes prior to that start are lost.

For sample accuracy, it would be most helpful if we could get the sample position of the next bar/beat event with each buffer we process. As it is (even leaving aside time sig/tempo changes), we’d have to check the q-note for every sample position in the buffer in order to find out when exactly it changes.

Hi
Did you check this member of ProcessContext:

TQuarterNotes barPositionMusic; ///< last bar start position, in quarter notes

from it you can compute where are the bar positions around the current position.
is it what you mean?

Yeah, I forgot we do use that now, when hosts make it available (which some don’t). Still a little complex, having to compute backwards into our graph from the current buffer start to when the most recent bar/beat occurred. And the code assumes that there hasn’t been a time sig or tempo change since the last bar. If it helps, here’s the code we use:

if (data.processContext != NULL)
	{
		//
		Steinberg::uint32	state	= data.processContext->state;
		if (	isSamplePosKnown &&
				((state & ProcessContext::kPlaying) != 0) &&
				((state & ProcessContext::kProjectTimeMusicValid) != 0) &&
				((state & ProcessContext::kBarPositionValid) != 0) &&
				((state & ProcessContext::kTempoValid) != 0) &&
				((state & ProcessContext::kTimeSigValid) != 0) )
		{
			// Compute last bar#, and fraction of a bar since then
			float		beatsPerQNote			= (float)(data.processContext->timeSigDenominator) / 4.0;
			float		barsPerQNote			= beatsPerQNote / data.processContext->timeSigNumerator;
			float		lastBarNumberFloat	= data.processContext->barPositionMusic * barsPerQNote; //barPositionMusic is in qNotes!
			float		barNumberIntPart		= floor( lastBarNumberFloat ); // -2.3 becomes -3.0, as we want!
			int32		lastBarNumber			= (int32)barNumberIntPart;

			/* TODO: what if this is not zero?
			float		barNumberFraction		= (lastBarNumberFloat >= 0) ? 
														lastBarNumberFloat - barNumberIntPart :	// if >= 0
														barNumberIntPart - lastBarNumberFloat;		// if < 0
			*/

			// Compute last beat# in current bar, and fraction of a beat since then
			float		qNotesBackToLastBar	= data.processContext->projectTimeMusic - data.processContext->barPositionMusic;
			float		currentBeatFloat		= qNotesBackToLastBar * beatsPerQNote;
			float		currentBeatIntPart	= floor( currentBeatFloat ); // always >= 0
			float		currentBeatFraction	= currentBeatFloat - currentBeatIntPart;
			
			Steinberg::uint16	lastBeatNumber			= (Steinberg::uint16)currentBeatIntPart;
			
			// Increment bar and beat, since calculations is 0-based, but numbers are 1-based!
			lastBarNumber++;
			lastBeatNumber++;
			// Are we at a new bar or beat?
			if ((lastBarNumber != lastRecordedBarNumber) || (lastBeatNumber != lastRecordedBeatInBar))
			{
				// Yes; Record the new bar/beat numbers
				lastRecordedBarNumber	= lastBarNumber;
				lastRecordedBeatInBar	= lastBeatNumber;
				
				// Now, we have the most recent bar# and the most recent beat#, 
				// and the fraction of a beat since the last beat.
				// We need to determine how long ago the last beat occurred, in samples.
				// To do that, we need to determine the samples per beat,
				// which is the samples per second (sample rate) divided by the beats per second.
				//	The beats per second equals the quarter notes per second times the beats per quarter note.
				//	The quarter notes per second equals the tempo divided by 60, and
				//	the beats per quarter note equals time signature denominator divided by 4. So...
				float		qNotesPerSecond		= data.processContext->tempo / 60.0;
				float		beatsPerSecond			= qNotesPerSecond * beatsPerQNote;
				float		samplesPerBeat			= data.processContext->sampleRate / beatsPerSecond;
				//
				float		samplesSinceLastBeat	= samplesPerBeat * currentBeatFraction;
				Steinberg::uint16	samplesSinceBeatInt	= (Steinberg::uint16)samplesSinceLastBeat;
				long		sampleAtBeat			= samplePos - samplesSinceBeatInt;
				//	Now we have the sample position of the last beat, which we use to mark it on our sample-based graph

Thanks Yvan and Howard.

Like Howard mentioned, this is only partial information.
In addition to the provided information we also need the exact bar number.

We need this information for cases our synced period is above 1 bar (e.g 2 bars period).

Hi,
So i wonder is there any progress with this issue?

It seems like the information we get is the same as is sent as smpte. A context should never have to be tracked, that is the hole point of it. A pointer to next and previous context’s should be nice. Like nextChord and previousChord. A musical context would be good. This smtpe is for video sync, it’s not that useful for most vst plugins. But information about the music. LIke if it is chorus, intro, verse etc is very useful for “agents” trying to help. Like groveagent and toontrack stuff.

How should the ProcessContext::State (uint32), SystemTime (int64) and Chord::KeyNote (uint8) values be serialzed as parameters in the processor getState/setState methods?

The HelloWorld examples show ParamValue and int16 parameters as IBStreamer::readFloat/readInt32 types.

getState/setState are black boxes from the host/daw point of view. You can read and write data in whatever way you want, the host doesn’t care. What matters (for you) is the order in which you serialize the data and how you serialize it. IBStreamer has many flavors (readInt64, etc…) and you should just use the one that matches each type. But if you use writeInt32u, writeInt64, writeInt8u, (in this order), you should make sure to call readInt32u, readInt64, readInt8u otherwise you won’t get the proper values back.