Skip to content

How to convert MIDI delta times into frames

Technical discussion which is not directly related to VGM files. Talk about Hardware and Software.

Moderator: Staff

  • User avatar
  • Sik Offline
  • Not a musician
    Not a musician
  • Posts: 75
  • Joined: 2011-12-12, 12:43:15

How to convert MIDI delta times into frames

Post by Sik »

For those making tools that take in MIDI files and convert them into a format for a sound engine (or even VGM directly if you dare), you need to know how to convert MIDI timestamps into whatever the sound engine uses. There are two types of timings: the standard one that almost all MIDIs use and the SMPTE one. I figured out the calculations for both and I'm posting them here.

This takes a delta time and turns in into Echo ticks or NTSC frames (60 Hz ticks):
  • Normal timing: delta × 3600 ÷ tempo ÷ ticks per beat
  • SMPTE timing: delta × 60 ÷ framerate ÷ ticks per frame
To convert to other speeds, change those numbers accordingly, e.g. for PAL frames (50 Hz) you'd use 3000 and 50, respectively (in other words, the output rate × 60 for the first, and just the output rate for the second).

Also, take into account that's the raw calculations. In practice it's likely you'll find events happening at less than a frame of distance, so if you use those calculations directly with integers (and thereby truncation) expect lots of issues. Either use fixed point (like I do in midi2esf) or use floating point.
  • Tom Offline
  • Ragequit Member
    Ragequit Member
  • Posts: 496
  • Joined: 2011-11-30, 17:26:44
  • Location: Italy
  • Contact:

Post by Tom »

Great stuff :)

However, keep in mind that not everyone is familiar with MIDI files timings in general, so I'll post a link to a page with the complete documentation: http://segaretro.org/MIDI ;)

For reference, this is the WriteVgmWait subroutine from my YM2612 midi2vgm converter, which obviously only works with non-SMPTE timing:

Code: Select all

Private Sub WriteVgmWait()
Dim tmp As Long
Dim tmp2 As Byte
VgmWait& = ((midiWait& * (Tempo& / 1000000)) * 44100) / TimeDivision
TotalVgmWait = TotalVgmWait + VgmWait&
tmp = VgmWait& \ 65535
For tmp2 = 1 To tmp
vgmData$ = vgmData$ + Chr$(&H61) + String$(2, 255)
Next tmp2
tmp = VgmWait& Mod 65535
Select Case tmp
Case Is = 0
Case Is < 17
vgmData$ = vgmData$ + Chr$(&H6F + tmp)
Case Is = 735
vgmData$ = vgmData$ + Chr$(&H62)
Case Is = 882
vgmData$ = vgmData$ + Chr$(&H63)
Case Else
vgmData$ = vgmData$ + Chr$(&H61) + Chr$(tmp And 255) + Chr$(tmp \ 256)
End Select
midiWait& = 0
End Sub
In this code, the TimeDivision variable is that value taken from the header when you're not using SMPTE, and it tells how many ticks there are in a quarter note (a typical value is 480, for example), and the Tempo& variable is read from the MIDI data (meta event $51), it defaults to 500000, and it's measured in microseconds per quarter note. The midiWait variable is incremented by reading the delta times between notes and events, and it's obviously reseted to 0 once the Vgm Wait has been written into the file. It is technically possible for a MIDI to have two events which happen faster than the VGM resolution (1/44100th of a second); in that case, you're fucked. Fortunately it's not a common case.

Note that the code above is probably obsolete by now, as I gave the complete source code of my YM2612 midi2vgm converter to ValleyBell, who's been improving it a lot. However, this works, and it can be a starting point for other people :)
Also known as nineko.
  • User avatar
  • ValleyBell Offline
  • Posts: 4767
  • Joined: 2011-12-01, 20:20:07
  • Location: Germany

Post by ValleyBell »

Looks like it's time for me posting some code - give it the absolute tick (not delay) and get the absolute sample:

Code: Select all

Private Function Tick2Sample(ByVal TickVal As Long) As Long

    Dim TempQud As Long
    Dim TickCount As Long
    
    TickCount = TickVal - MidTempo.BaseTick
    TempQud = CCur(TickCount) * VGM_SMPL_RATE / 1000@ * MidTempo.Tempo / 1000@ / MidTickRate
    
    Tick2Sample = MidTempo.SmplTime + TempQud

End Function
This code comes from mid2vgm OPLL (and mid2vgm PSG) and is slightly more accurate then Tom's code. (I think it's up to 100 samples every minute, but depends on the tempo and delays.)
VGM_SMPL_RATE is constant 44100, default Tempo is 500 000. (120 BPM)
CCur(...) is just a type-conversion to a 64-bit (sort of) integer type.
I'm dividing /1000 two times to prevent it from overflowing when multiplying with Tempo, btw.

The MidTempo.BaseTick and .SmplTime are refreshed when a tempo change occours, so they're usually 0.


I have to note that I don't check for SMPTE timing, so my tools may show run-time errors, hang or have some "undefined" behaviour, because of a negative MidTick value.
  • User avatar
  • Sik Offline
  • Not a musician
    Not a musician
  • Posts: 75
  • Joined: 2011-12-12, 12:43:15

Post by Sik »

I don't think that using absolute values is a good idea when you consider the tempo can change mid-tune (meaning you suddenly have to deal with ticks that can have different durations).

EDIT: I just realized I made topic ID #111.
Last edited by Sik on 2011-12-26, 0:03:17, edited 1 time in total.
  • User avatar
  • ValleyBell Offline
  • Posts: 4767
  • Joined: 2011-12-01, 20:20:07
  • Location: Germany

Post by ValleyBell »

The trick is, that I update the MidTempo structure when the tempo changes.
I.e. I calculate all ticks and samples relative to the last tempo change. This includes the tick/sample of the next tempo change.

It works very well and is quite accurate even with odd tempos.
  • Tom Offline
  • Ragequit Member
    Ragequit Member
  • Posts: 496
  • Joined: 2011-11-30, 17:26:44
  • Location: Italy
  • Contact:

Post by Tom »

ValleyBell wrote:This code comes from mid2vgm OPLL (and mid2vgm PSG) and is slightly more accurate then Tom's code. (I think it's up to 100 samples every minute, but depends on the tempo and delays.)
I agree, it's probably more accurate than mine, since I introduce potential rounding errors with every delta time. I went for a progressive approach because I thought it was the best way to handle tempo variations.
In practice, though, I merged several songs converted with my converter and your ones, and I never noticed any audible timing difference.
Sik wrote:I just realized I made topic ID #111.
If you want I can renumber it to 112 :2:
Also known as nineko.
Post Reply