Files
gronod 1eb2d9a1bf macOS port: buildable and runnable on macOS 12+ (x86_64)
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.
2026-09-18 17:56:06 +01:00

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;
}