foxygit / doom Log in
commit e898c2f0dec6ea5c6365fef9d494c7f94240bea5
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Thu May 8 00:44:50 2014 -0400
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Thu May 8 00:44:50 2014 -0400

    opl: Process MIDI 'set tempo' meta events.

    The MIDI format includes a special meta event to set the tempo of
    playback, and some WADs depend on this - notably the music in Alien
    Vendetta. Move the variables controlling tempo to the global scope
    (they are not per-track as I previously thought) and set when the
    tempo events are encountered.

    This is some progress towards resolving #334, but that bug is not
    yet completely fixed, because the tempo change does not
    retroactively apply to OPL timers that have already been set.
---
 src/i_oplmusic.c | 38 ++++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c
index a2bc22d8..f38e7162 100644
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -46,6 +46,9 @@

 #define PERCUSSION_LOG_LEN 16

+// TODO: Figure out why this is needed.
+#define TEMPO_FUDGE_FACTOR 0.26
+
 typedef struct
 {
     byte tremolo;
@@ -103,11 +106,6 @@ typedef struct
     // Track iterator used to read new events.

     midi_track_iter_t *iter;
-
-    // Tempo control variables
-
-    unsigned int ticks_per_beat;
-    unsigned int ms_per_beat;
 } opl_track_data_t;

 typedef struct opl_voice_s opl_voice_t;
@@ -324,6 +322,11 @@ static unsigned int num_tracks = 0;
 static unsigned int running_tracks = 0;
 static boolean song_looping;

+// Tempo control variables
+
+static unsigned int ticks_per_beat;
+static unsigned int us_per_beat;
+
 // Mini-log of recently played percussion instruments:

 static uint8_t last_perc[PERCUSSION_LOG_LEN];
@@ -1017,6 +1020,9 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)

 static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
 {
+    byte *data = event->data.meta.data;
+    unsigned int data_len = event->data.meta.length;
+
     switch (event->data.meta.type)
     {
         // Things we can just ignore.
@@ -1032,6 +1038,13 @@ static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
         case MIDI_META_SEQUENCER_SPECIFIC:
             break;

+        case MIDI_META_SET_TEMPO:
+            if (data_len == 3)
+            {
+                us_per_beat = (data[0] << 16) | (data[1] << 8) | data[2];
+            }
+            break;
+
         // End of track - actually handled when we run out of events
         // in the track, see below.

@@ -1161,7 +1174,7 @@ static void ScheduleTrack(opl_track_data_t *track)
     // Get the number of milliseconds until the next event.

     nticks = MIDI_GetDeltaTime(track->iter);
-    ms = (nticks * track->ms_per_beat) / track->ticks_per_beat;
+    ms = (nticks * us_per_beat * TEMPO_FUDGE_FACTOR) / ticks_per_beat;
     total += ms;

     // Set a timer to be invoked when the next event is
@@ -1190,12 +1203,6 @@ static void StartTrack(midi_file_t *file, unsigned int track_num)

     track = &tracks[track_num];
     track->iter = MIDI_IterateTrack(file, track_num);
-    track->ticks_per_beat = MIDI_GetFileTimeDivision(file);
-
-    // Default is 120 bpm.
-    // TODO: this is wrong
-
-    track->ms_per_beat = 500 * 260;

     for (i=0; i<MIDI_CHANNELS_PER_TRACK; ++i)
     {
@@ -1229,6 +1236,13 @@ static void I_OPL_PlaySong(void *handle, boolean looping)
     running_tracks = num_tracks;
     song_looping = looping;

+    ticks_per_beat = MIDI_GetFileTimeDivision(file);
+
+    // Default is 120 bpm.
+    // TODO: this is wrong
+
+    us_per_beat = 500 * 1000;
+
     for (i=0; i<num_tracks; ++i)
     {
         StartTrack(file, i);