DPF icon indicating copy to clipboard operation
DPF copied to clipboard

Mapping MIDI time to frame count

Open ryukau opened this issue 5 years ago • 11 comments

I looked up TimePosition and BarBeatTick, but they don't seem to have informations to map MIDI time to frame count.

I'd like to get following informations:

  • Length of tick, beat or bar in frames.
  • Frame count to reach next tick, beat or bar from the start of current buffer.

Or is it expected to handle MIDI time code directly?

It also looks like VstTimeInfo in vestige.h has all relevant information.

ryukau avatar Jul 14 '20 02:07 ryukau

DPF follows the JACK transport API when it comes to tempo. https://jackaudio.org/api/structjack__position__t.html https://distrho.github.io/DPF/structTimePosition_1_1BarBeatTick.html

ticksPerBeat tells you how many ticks there are in a beat. beatsPerBar tells you how many beats there are in a bar.

Ticks are unrelated to frames. Frame is monotonic, increases as long as host keeps playing without reposition of timeline. The amount of ticks in a beat varies between applications, some provide more resolution than others. for example, LMMS uses 48, while MusE uses 1920

falkTX avatar Jul 14 '20 02:07 falkTX

So tempo and ticksPerBeat can be used to get the length of the tick in seconds.

auto ticksPerSecond = bbt.ticksPerBeat * bbt.beatsPerMinute / 60;
auto secondsPerTick = 1.0 / ticksPerSecond;

Thank you for the explanation.

ryukau avatar Jul 14 '20 02:07 ryukau

It is best not to do things based on tick count though. Being an integer value, it does not have full precision.

I guess I can make it a double.. JACK standalones do not have this fine precision, but LV2 and VST2 plugins do. Let me know if you run into issues.

falkTX avatar Jul 14 '20 03:07 falkTX

Hi. I have another question.

I'm trying to make a CV plugin which outputs trigger signal at the start of bar/beat. I wrote a code like following:

void run(const float **, float **outputs, uint32_t frames) override
{
  const auto timePos = getTimePosition();
  if (timePos.bbt.valid) {
    auto &bbt = timePos.bbt;

    double secondsPerTick = 60.0 / (bbt.ticksPerBeat * bbt.beatsPerMinute);

    double tickLength = sampleRate * secondsPerTick;
    double beatLength = tickLength * bbt.ticksPerBeat;
    double barLength = beatLength * bbt.beatsPerBar;

    double barStartFrame = tickLength * bbt.barStartTick;
    double beatStartFrame = barStartFrame + beatLength * (bbt.beat - 1);
    double tickStartFrame = beatStartFrame + tickLength * bbt.tick;

    double offsetToNextTick = timePos.frame - tickStartFrame;

    // ...
  }
}

I'm trying to calculate frame offset from the start of current buffer to the next tick as offsetToNextTick. The code above is assuming that bbt.tick indicates latest tick count right before the start of current buffer. However, offsetToNextTick can be both positive or negative when I tested.

The question is that where exactly is bbt.tick indicating? Is it something like the closest tick to the start of current buffer?

Following is a drawing of my understanding of relation about tick and the start of buffer. Start of buffer is more close to B. I'd like to know which of tick A or tick B becomes bbt.tick. Or could it be possible that bbt.tick indicates completely different time?

bbt_tick

ryukau avatar Jul 15 '20 10:07 ryukau

the transport information refers to the frame 0 of each process cycle. so yes, closest to the start of each buffer

falkTX avatar Jul 15 '20 11:07 falkTX

Thanks for the answer.

I tested more and found that the code on previous comment breaks when tempo is changed. I looked up LV2 metronome example, and it seems like they are using barBeat which is provided by float.

Is it possible to add barBeat to BarBeatTick?

ryukau avatar Jul 16 '20 04:07 ryukau

hey, sorry for the lack of news on this. to make things clear... I am slowing down a bit on the hobby-project work, focusing on a specific project at a time. I have scheduled on my calendar time that I will dedicate to each project. First carla, then cadence, then jack2, then distrho-ports and finally DPF. I will get back to DPF full-force in november. someone else has been asking about exactly the same thing (in regards to needing a precise timing source)

falkTX avatar Aug 19 '20 20:08 falkTX

I'd like to say thanks for your works. DPF and Carla made my plugin development far easier.

Just one thing, I know a person who got mentally ill for working too hard, so please take your time and get a break when feeling tired. It's OK to procrastinate.

For anyone stumbled upon this issue, I have temporary patches to solve this issue on here (DistrhoPlugin*.patch). Example snippet is also available. Hope this helps.

ryukau avatar Aug 20 '20 02:08 ryukau

In hvcc I'm doing this: https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/c2dpf/templates/HeavyDPF.cpp#L192

Here we generate MTC clock signals, which is 24 ticks per beat, that are scheduled into the hvcc processing buffer. Any remaining frames are taken over to the next processing cycle cycle and handled there. This way the window of midi ticks vs processing frames moves along. I use a double to count samples, should that be enough?

CardinalPlugin does something similar here, right? -> https://github.com/DISTRHO/Cardinal/blob/main/src/CardinalPlugin.cpp#L926

It would be nice to know some best-practice around this stuff in DPF.

dromer avatar Jan 21 '22 13:01 dromer

MTC in plugins doesnt make sense though. Cardinal purposefully ignores this old MIDI thing and uses the host time information instead (which is much more accurate)

The midi time code in cardinal only for start/stop/continue triggers.

falkTX avatar Jan 21 '22 14:01 falkTX

In hvcc it's all puredata logic to do stuff. so having a sample accurate MTC is nice there. You have no other way to sync to anything otherwise. So yeah it's just midi paradigm you generally have to work with.

dromer avatar Jan 21 '22 18:01 dromer