Replaces X11/MIT-SHM video and Linux OSS audio with SDL2, and plays MIDI music through AudioToolbox MusicSequence/MusicPlayer (system DLS synth, no soundfont needed). MUS lumps are converted by a new mus2mid; already-MIDI lumps pass through. Also includes the 64-bit portability fixes needed to run on modern clang/LP64: pointer-size casts and struct layouts in the renderer, savegame code, zone allocator and config defaults, plus removal of obsolete headers and unsequenced-modification UB.
367 lines
7.0 KiB
C
367 lines
7.0 KiB
C
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// mus2mid - convert DOOM MUS music lumps to standard MIDI.
|
|
//
|
|
// MUS events run at 140Hz. Emitting a MIDI division of 70 ticks
|
|
// per quarter note (at the default 120bpm tempo) reproduces the
|
|
// same timing, so delay values pass through unchanged.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "doomtype.h"
|
|
#include "mus2mid.h"
|
|
|
|
|
|
// MUS event types (score byte high nibble).
|
|
enum
|
|
{
|
|
MUS_RELEASE_NOTE = 0,
|
|
MUS_PLAY_NOTE = 1,
|
|
MUS_PITCH_WHEEL = 2,
|
|
MUS_SYSTEM_EVENT = 3,
|
|
MUS_CHANGE_CTRL = 4,
|
|
MUS_SCORE_END = 7
|
|
};
|
|
|
|
// MUS controller number -> MIDI controller number.
|
|
// MUS controller 0 means program change, handled separately.
|
|
static const byte ctrl_map[10] =
|
|
{
|
|
0x00, // program change (special-cased)
|
|
0x00, // bank select
|
|
0x01, // modulation
|
|
0x07, // volume
|
|
0x0a, // pan
|
|
0x0b, // expression
|
|
0x5b, // reverb depth
|
|
0x5d, // chorus depth
|
|
0x40, // sustain pedal
|
|
0x43 // soft pedal
|
|
};
|
|
|
|
// MUS system event controller numbers (10-14) -> MIDI controllers.
|
|
static const byte system_map[5] =
|
|
{
|
|
120, // all sound off
|
|
123, // all notes off
|
|
126, // mono on
|
|
127, // poly on
|
|
121 // reset all controllers
|
|
};
|
|
|
|
|
|
// Growable byte buffer for the MIDI track.
|
|
typedef struct
|
|
{
|
|
byte* data;
|
|
int len;
|
|
int cap;
|
|
} midibuf_t;
|
|
|
|
static void
|
|
mb_put
|
|
( midibuf_t* b,
|
|
int v )
|
|
{
|
|
if (b->len == b->cap)
|
|
{
|
|
b->cap = b->cap ? b->cap*2 : 4096;
|
|
b->data = realloc(b->data, b->cap);
|
|
}
|
|
b->data[b->len++] = v;
|
|
}
|
|
|
|
// MIDI variable-length quantity.
|
|
static void
|
|
mb_put_varlen
|
|
( midibuf_t* b,
|
|
int v )
|
|
{
|
|
byte tmp[4];
|
|
int n = 0;
|
|
|
|
tmp[n++] = v & 0x7f;
|
|
while ((v >>= 7))
|
|
tmp[n++] = 0x80 | (v & 0x7f);
|
|
|
|
while (n)
|
|
mb_put(b, tmp[--n]);
|
|
}
|
|
|
|
static int
|
|
read16
|
|
( const byte* p )
|
|
{
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
|
|
static int
|
|
read16be
|
|
( const byte* p )
|
|
{
|
|
return (p[0] << 8) | p[1];
|
|
}
|
|
|
|
static int
|
|
read32be
|
|
( const byte* p )
|
|
{
|
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
|
}
|
|
|
|
// Length of a standard MIDI stream, by walking its chunks.
|
|
// Some WADs (e.g. FreeDoom) store MIDI rather than MUS lumps.
|
|
static int
|
|
midi_length
|
|
( byte* data )
|
|
{
|
|
byte* q;
|
|
int ntracks;
|
|
int len;
|
|
int i;
|
|
|
|
if (memcmp(data, "MThd", 4)
|
|
|| read32be(data + 4) != 6)
|
|
return -1;
|
|
|
|
ntracks = read16be(data + 10);
|
|
len = 14;
|
|
q = data + 14;
|
|
|
|
for (i=0 ; i<ntracks ; i++)
|
|
{
|
|
int chunklen;
|
|
|
|
if (memcmp(q, "MTrk", 4))
|
|
return -1;
|
|
chunklen = read32be(q + 4);
|
|
len += 8 + chunklen;
|
|
q += 8 + chunklen;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
// MUS channel 15 is the percussion channel -> MIDI channel 9.
|
|
static int
|
|
midichan
|
|
( int muschan )
|
|
{
|
|
return muschan == 15 ? 9 : muschan;
|
|
}
|
|
|
|
|
|
int
|
|
mus2mid
|
|
( byte* musdata,
|
|
byte** mid_out )
|
|
{
|
|
byte* score;
|
|
byte* p;
|
|
byte* end;
|
|
midibuf_t track = {0, 0, 0};
|
|
byte last_volumes[16];
|
|
int score_len;
|
|
int score_start;
|
|
int instr_cnt;
|
|
int i;
|
|
int delay;
|
|
int pending = 0;
|
|
int done = 0;
|
|
byte* mid;
|
|
byte* q;
|
|
|
|
*mid_out = NULL;
|
|
|
|
if (!musdata)
|
|
return -1;
|
|
|
|
// Already-MIDI lumps (e.g. FreeDoom) pass straight through.
|
|
if (!memcmp(musdata, "MThd", 4))
|
|
{
|
|
int midlen = midi_length(musdata);
|
|
|
|
if (midlen <= 0)
|
|
return -1;
|
|
*mid_out = malloc(midlen);
|
|
if (!*mid_out)
|
|
return -1;
|
|
memcpy(*mid_out, musdata, midlen);
|
|
return midlen;
|
|
}
|
|
|
|
if (memcmp(musdata, "MUS\x1a", 4))
|
|
return -1;
|
|
|
|
score_len = read16(musdata + 4);
|
|
score_start = read16(musdata + 6);
|
|
instr_cnt = read16(musdata + 12);
|
|
|
|
score = musdata + score_start;
|
|
p = score;
|
|
end = score + score_len;
|
|
|
|
// Default channel volumes.
|
|
for (i=0 ; i<16 ; i++)
|
|
last_volumes[i] = 0x64;
|
|
|
|
// Channel instrument assignments from the header.
|
|
// Instrument i plays on channel i.
|
|
for (i=0 ; i<instr_cnt && i<16 ; i++)
|
|
{
|
|
if (i == 15)
|
|
continue; // percussion channel needs no program
|
|
mb_put_varlen(&track, 0);
|
|
mb_put(&track, 0xc0 | midichan(i));
|
|
mb_put(&track, musdata[14 + i*2] & 0x7f);
|
|
}
|
|
|
|
while (!done && p < end)
|
|
{
|
|
int mus_event = (p[0] >> 4) & 7;
|
|
int mus_chan = p[0] & 15;
|
|
int has_delay = p[0] & 0x80;
|
|
int chan = midichan(mus_chan);
|
|
|
|
p++;
|
|
|
|
switch (mus_event)
|
|
{
|
|
case MUS_RELEASE_NOTE:
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0x80 | chan);
|
|
mb_put(&track, p[0] & 0x7f);
|
|
mb_put(&track, 0);
|
|
p += 1;
|
|
break;
|
|
|
|
case MUS_PLAY_NOTE:
|
|
{
|
|
int note = p[0] & 0x7f;
|
|
int vol;
|
|
|
|
if (p[0] & 0x80)
|
|
{
|
|
vol = p[1] & 0x7f;
|
|
last_volumes[mus_chan] = vol;
|
|
p += 2;
|
|
}
|
|
else
|
|
{
|
|
vol = last_volumes[mus_chan];
|
|
p += 1;
|
|
}
|
|
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0x90 | chan);
|
|
mb_put(&track, note);
|
|
mb_put(&track, vol);
|
|
break;
|
|
}
|
|
|
|
case MUS_PITCH_WHEEL:
|
|
// MUS bend byte (0-255, center 128) -> 14-bit MIDI bend.
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0xe0 | chan);
|
|
mb_put(&track, (p[0] & 1) << 6);
|
|
mb_put(&track, p[0] >> 1);
|
|
p += 1;
|
|
break;
|
|
|
|
case MUS_SYSTEM_EVENT:
|
|
if (p[0] >= 10 && p[0] <= 14)
|
|
{
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0xb0 | chan);
|
|
mb_put(&track, system_map[p[0] - 10]);
|
|
mb_put(&track, 0);
|
|
}
|
|
p += 1;
|
|
break;
|
|
|
|
case MUS_CHANGE_CTRL:
|
|
if (p[0] == 0)
|
|
{
|
|
// program change
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0xc0 | chan);
|
|
mb_put(&track, p[1] & 0x7f);
|
|
}
|
|
else if (p[0] >= 1 && p[0] <= 9)
|
|
{
|
|
mb_put_varlen(&track, pending);
|
|
pending = 0;
|
|
mb_put(&track, 0xb0 | chan);
|
|
mb_put(&track, ctrl_map[p[0]]);
|
|
mb_put(&track, p[1] & 0x7f);
|
|
}
|
|
p += 2;
|
|
break;
|
|
|
|
case MUS_SCORE_END:
|
|
done = 1;
|
|
break;
|
|
|
|
default:
|
|
// Unknown event: bail out.
|
|
done = 1;
|
|
break;
|
|
}
|
|
|
|
// Delay time (MUS ticks) follows when the high bit was set;
|
|
// it becomes the delta time preceding the next event.
|
|
if (has_delay)
|
|
{
|
|
delay = 0;
|
|
while (p < end)
|
|
{
|
|
delay = (delay << 7) | (*p & 0x7f);
|
|
if (!(*p++ & 0x80))
|
|
break;
|
|
}
|
|
pending += delay;
|
|
}
|
|
}
|
|
|
|
// End of track.
|
|
mb_put_varlen(&track, pending);
|
|
mb_put(&track, 0xff);
|
|
mb_put(&track, 0x2f);
|
|
mb_put(&track, 0x00);
|
|
|
|
// Assemble the MIDI file: MThd + MTrk.
|
|
mid = malloc(14 + 8 + track.len);
|
|
if (!mid)
|
|
{
|
|
free(track.data);
|
|
return -1;
|
|
}
|
|
|
|
q = mid;
|
|
memcpy(q, "MThd", 4); q += 4;
|
|
*q++ = 0; *q++ = 0; *q++ = 0; *q++ = 6; // header length
|
|
*q++ = 0; *q++ = 0; // format 0
|
|
*q++ = 0; *q++ = 1; // one track
|
|
*q++ = 0; *q++ = 70; // division: 70 ticks/qn
|
|
memcpy(q, "MTrk", 4); q += 4;
|
|
*q++ = (track.len >> 24) & 0xff;
|
|
*q++ = (track.len >> 16) & 0xff;
|
|
*q++ = (track.len >> 8) & 0xff;
|
|
*q++ = track.len & 0xff;
|
|
memcpy(q, track.data, track.len);
|
|
|
|
free(track.data);
|
|
|
|
*mid_out = mid;
|
|
return 14 + 8 + track.len;
|
|
}
|