summaryrefslogtreecommitdiff
path: root/src/mus2mid.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mus2mid.c')
-rw-r--r--src/mus2mid.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/src/mus2mid.c b/src/mus2mid.c
new file mode 100644
index 00000000..ba05c4cf
--- /dev/null
+++ b/src/mus2mid.c
@@ -0,0 +1,599 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// $Id: z_zone.c 434 2006-03-24 19:55:04Z fraggle $
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005 Simon Howard
+// Copyright(C) 2006 Ben Ryves 2006
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com
+// Use to convert a MUS file into a single track, type 0 MIDI file.
+
+#include <stdio.h>
+
+#include "memio.h"
+#include "mus2mid.h"
+
+// MUS event codes
+typedef enum
+{
+ mus_releasekey = 0x00,
+ mus_presskey = 0x10,
+ mus_pitchwheel = 0x20,
+ mus_systemevent = 0x30,
+ mus_changecontroller = 0x40,
+ mus_scoreend = 0x60
+} musevent;
+
+// MIDI event codes
+typedef enum
+{
+ midi_releasekey = 0x80,
+ midi_presskey = 0x90,
+ midi_aftertouchkey = 0xA0,
+ midi_changecontroller = 0xB0,
+ midi_changepatch = 0xC0,
+ midi_aftertouchchannel = 0xD0,
+ midi_pitchwheel = 0xE0
+} midievent;
+
+
+// Structure to hold MUS file header
+typedef struct
+{
+ unsigned char id[4];
+ unsigned short scorelength;
+ unsigned short scorestart;
+ unsigned short primarychannels;
+ unsigned short secondarychannels;
+ unsigned short instrumentcount;
+} musheader;
+
+
+// Standard MIDI type 0 header + track header
+static unsigned char midiheader[] =
+{
+ 'M', 'T', 'h', 'd', // Main header
+ 0x00, 0x00, 0x00, 0x06, // Header size
+ 0x00, 0x00, // MIDI type (0)
+ 0x00, 0x01, // Number of tracks
+ 0x00, 0x46, // Resolution
+ 'M', 'T', 'r', 'k', // Start of track
+ 0x00, 0x00, 0x00, 0x00 // Placeholder for track length
+};
+
+// Cached channel velocities
+static unsigned char channelvelocities[] =
+{
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127
+};
+
+// Timestamps between sequences of MUS events
+
+static unsigned int queuedtime = 0;
+
+// Counter for the length of the track
+
+static unsigned int tracksize = 0;
+
+static unsigned char mus2midi_translation[] =
+{
+ 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D,
+ 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79
+};
+
+// Write timestamp to a MIDI file.
+
+static int midi_writetime(unsigned int time, MEMFILE *midioutput)
+{
+ unsigned int buffer = time & 0x7F;
+ unsigned char writeval;
+
+ while ((time >>= 7) != 0)
+ {
+ buffer <<= 8;
+ buffer |= ((time & 0x7F) | 0x80);
+ }
+
+ for (;;)
+ {
+ writeval = (unsigned char)(buffer & 0xFF);
+
+ if (mem_fwrite(&writeval, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ ++tracksize;
+
+ if ((buffer & 0x80) != 0)
+ {
+ buffer >>= 8;
+ }
+ else
+ {
+ queuedtime = 0;
+ return 0;
+ }
+ }
+}
+
+
+// Write the end of track marker
+static int midi_writeendtrack(MEMFILE *midioutput)
+{
+ unsigned char endtrack[] = {0xFF, 0x2F, 0x00};
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(endtrack, 1, 3, midioutput) != 3)
+ {
+ return 1;
+ }
+
+ tracksize += 3;
+ return 0;
+}
+
+// Write a key press event
+static int midi_writepresskey(unsigned char channel, unsigned char key,
+ unsigned char velocity, MEMFILE *midioutput)
+{
+ unsigned char working = midi_presskey | channel;
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = key & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = velocity & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ tracksize += 3;
+
+ return 0;
+}
+
+// Write a key release event
+static int midi_writereleasekey(unsigned char channel, unsigned char key,
+ MEMFILE *midioutput)
+{
+ unsigned char working = midi_releasekey | channel;
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = key & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = 0;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ tracksize += 3;
+
+ return 0;
+}
+
+// Write a pitch wheel/bend event
+static int midi_writepitchwheel(unsigned char channel, short wheel,
+ MEMFILE *midioutput)
+{
+ unsigned char working = midi_pitchwheel | channel;
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = wheel & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = (wheel >> 7) & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ tracksize += 3;
+ return 0;
+}
+
+// Write a patch change event
+static int midi_writechangepatch(unsigned char channel, unsigned char patch,
+ MEMFILE *midioutput)
+{
+ unsigned char working = midi_changepatch | channel;
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = patch & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ tracksize += 2;
+
+ return 0;
+}
+
+
+
+// Write a valued controller change event
+static int midi_writechangecontroller_valued(unsigned char channel,
+ unsigned char control,
+ unsigned char value,
+ MEMFILE *midioutput)
+{
+ unsigned char working = midi_changecontroller | channel;
+
+ if (midi_writetime(queuedtime, midioutput))
+ {
+ return 1;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ working = control & 0x7F;
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+ // Quirk in vanilla DOOM? MUS controller values should be
+ // 7-bit, not 8-bit.
+
+ working = value;// & 0x7F;
+
+ // Fix on said quirk to stop MIDI players from complaining that
+ // the value is out of range:
+
+ if (working & 0x80)
+ {
+ working = 0x7F;
+ }
+
+ if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+ {
+ return 1;
+ }
+
+ tracksize += 3;
+
+ return 0;
+}
+
+// Write a valueless controller change event
+static int midi_writechangecontroller_valueless(unsigned char channel,
+ unsigned char control,
+ MEMFILE *midioutput)
+{
+ return midi_writechangecontroller_valued(channel, control, 0,
+ midioutput);
+}
+
+// Read a MUS file from a stream (musinput) and output a MIDI file to
+// a stream (midioutput).
+//
+// Returns 0 on success or 1 on failure.
+
+int mus2mid(MEMFILE *musinput, MEMFILE *midioutput)
+{
+ // Header for the MUS file
+ musheader musfileheader;
+
+ // Descriptor for the current MUS event
+ unsigned char eventdescriptor;
+ int channel; // Channel number
+ musevent event;
+
+
+ // Bunch of vars read from MUS lump
+ unsigned char key;
+ unsigned char controllernumber;
+ unsigned char controllervalue;
+
+ // Buffer used for MIDI track size record
+ unsigned char tracksizebuffer[4];
+
+ // Flag for when the score end marker is hit.
+ int hitscoreend = 0;
+
+ // Temp working byte
+ unsigned char working;
+ // Used in building up time delays
+ unsigned int timedelay;
+
+ int i;
+
+ // Grab the header
+ if (mem_fread(&musfileheader, sizeof(musheader), 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ // Check MUS header
+ if (musfileheader.id[0] != 'M'
+ || musfileheader.id[1] != 'U'
+ || musfileheader.id[2] != 'S'
+ || musfileheader.id[3] != 0x1A)
+ {
+ return 1;
+ }
+
+ // Seek to where the data is held
+ if (mem_fseek(musinput, (long)musfileheader.scorestart, SEEK_SET) != 0)
+ {
+ return 1;
+ }
+
+ // So, we can assume the MUS file is faintly legit. Let's start
+ // writing MIDI data...
+
+ mem_fwrite(midiheader, 1, 22, midioutput);
+
+ // Now, process the MUS file:
+ while (!hitscoreend)
+ {
+ // Handle a block of events:
+
+ while (!hitscoreend)
+ {
+
+ // Fetch channel number and event code:
+ if (mem_fread(&eventdescriptor, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ channel = eventdescriptor & 0x0F;
+ event = eventdescriptor & 0x70;
+
+ // Swap channels 15 and 9.
+ // MIDI channel 9 = percussion.
+ // MUS channel 15 = percussion.
+
+ if (channel == 15)
+ {
+ channel = 9;
+ }
+ else if (channel == 9)
+ {
+ channel = 15;
+ }
+
+ switch (event)
+ {
+
+ case mus_releasekey:
+ if (mem_fread(&key, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ if (midi_writereleasekey(channel, key, midioutput))
+ {
+ return 1;
+ }
+
+ break;
+
+ case mus_presskey:
+ if (mem_fread(&key, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ if (key & 0x80)
+ {
+ if (mem_fread(&channelvelocities[channel], 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ channelvelocities[channel] &= 0x7F;
+ }
+
+ if (midi_writepresskey(channel, key, channelvelocities[channel], midioutput))
+ {
+ return 1;
+ }
+
+ break;
+
+ case mus_pitchwheel:
+ if (mem_fread(&key, 1, 1, musinput) != 1)
+ {
+ break;
+ }
+ if (midi_writepitchwheel(channel, (short)(key * 64), midioutput))
+ {
+ return 1;
+ }
+
+ break;
+
+ case mus_systemevent:
+ if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+ if (controllernumber < 10 || controllernumber > 14)
+ {
+ return 1;
+ }
+
+ if (midi_writechangecontroller_valueless(channel, mus2midi_translation[controllernumber], midioutput))
+ {
+ return 1;
+ }
+
+ break;
+
+ case mus_changecontroller:
+ if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ if (mem_fread(&controllervalue, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ if (controllernumber == 0)
+ {
+ if (midi_writechangepatch(channel, controllervalue, midioutput))
+ {
+ return 1;
+ }
+ }
+ else
+ {
+ if (controllernumber < 1 || controllernumber > 9)
+ {
+ return 1;
+ }
+
+ if (midi_writechangecontroller_valued(channel, mus2midi_translation[controllernumber], controllervalue, midioutput))
+ {
+ return 1;
+ }
+ }
+
+ break;
+
+ case mus_scoreend:
+ hitscoreend = 1;
+ break;
+
+ default:
+ return 1;
+ break;
+ }
+
+ if (eventdescriptor & 0x80)
+ {
+ break;
+ }
+ }
+ // Now we need to read the time code:
+ if (!hitscoreend)
+ {
+ timedelay = 0;
+ for (;;)
+ {
+ if (mem_fread(&working, 1, 1, musinput) != 1)
+ {
+ return 1;
+ }
+
+ timedelay = timedelay * 128 + (working & 0x7F);
+ if ((working & 0x80) == 0)
+ {
+ break;
+ }
+ }
+ queuedtime += timedelay;
+ }
+ }
+
+ // End of track
+ if (midi_writeendtrack(midioutput))
+ {
+ return 1;
+ }
+
+ // Write the track size into the stream
+ if (mem_fseek(midioutput, 18, SEEK_SET))
+ {
+ return 1;
+ }
+
+ for (i = 0; i < 4; ++i)
+ {
+ tracksizebuffer[i] = (unsigned char)(tracksize >> 24);
+ tracksize <<= 8;
+ }
+
+ if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+