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().)
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).
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.
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
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.