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.