shithub: choc

Download patch

ref: e898c2f0dec6ea5c6365fef9d494c7f94240bea5
parent: b2e5952c454dd46b29ca6c64e742b4505c20d5b7
author: Simon Howard <fraggle@gmail.com>
date: Wed May 7 20:44:50 EDT 2014

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.

--- 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 @@
     // 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 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 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 @@
         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 @@
     // 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,13 +1203,7 @@
 
     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)
     {
         InitChannel(track, &track->channels[i]);
@@ -1228,6 +1235,13 @@
     num_tracks = MIDI_NumTracks(file);
     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)
     {