blob: fec57a7a845ea0c1feab558e2e558fd98eabb3e6 [file] [log] [blame] [raw]
/*
* MUS loader for DOSMid
*
* Copyright (c) 2014, 2015 Mateusz Viste
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h> /* memcpy() */
#include "mem.h"
#include "midi.h"
#include "mus.h" /* include self for control */
#define TICKLEN 500000l /* length of a single MUS tick, in us */
#define TIMEUNITDIV 70 /* the MIDI time unit divisor */
/* loads a MUS file into memory, returns the id of the first event on success,
* or -1 on error. channelsusage contains 16 flags indicating what channels
* are used. */
long mus_load(FILE *fd, unsigned long *totlen, unsigned short *timeunitdiv, unsigned short *channelsusage) {
unsigned char remapchannel[16] = {0,1,2,3,4,5,6,7,8,15,10,11,12,13,14,9};
unsigned char hdr_or_chanvol[16];
unsigned short scorestart;
int bytebuff, bytebuff2, loadflag = 0;
unsigned long event_dtime, nextwait = 0;
unsigned short event_type;
unsigned short event_channel;
long res = -1;
int tickduration = 0;
long mslen = 0;
struct midi_event_t midievent;
rewind(fd);
/* read the 16 bytes header first, and populate hdr data */
if (fread(hdr_or_chanvol, 1, 16, fd) != 16) return(-8);
if ((hdr_or_chanvol[0] != 'M') || (hdr_or_chanvol[1] != 'U') || (hdr_or_chanvol[2] != 'S') || (hdr_or_chanvol[3] != 0x1A)) {
return(-9);
}
scorestart = hdr_or_chanvol[6] | (hdr_or_chanvol[7] << 8);
/* position the next reading position to first event */
fseek(fd, scorestart, SEEK_SET);
/* set tempo to 140 bpm (428571 us per quarter note) */
memset(&midievent, 0, sizeof(struct midi_event_t));
midievent.type = EVENT_TEMPO;
*timeunitdiv = TIMEUNITDIV;
midievent.data.tempoval = TICKLEN;
if (pusheventqueue(&midievent, &res) != 0) return(MUS_OUTOFMEM);
/* since now on, hdr_or_chanvol is used to store volume of channels */
memset(hdr_or_chanvol, 0, 16);
*channelsusage = 0; /* zero out the used instruments map */
/* read events from the MUS file and translate them into midi events */
for (;;) {
bytebuff = fgetc(fd);
if (bytebuff < 0) return(-5); /* if EOF, abort with error */
event_channel = bytebuff & 0x0F;
bytebuff >>= 4;
event_type = bytebuff & 7;
bytebuff >>= 3;
event_dtime = bytebuff; /* if the 'last' bit is set, remember to read time after the event later */
/* if channel is 15, it is percussion, and must be remapped to MIDI #9 */
event_channel = remapchannel[event_channel];
/* clear out midievent to make room for the incoming MIDI message */
memset(&midievent, 0, sizeof(struct midi_event_t));
/* read complementary data, if any */
switch (event_type) {
case 0: /* release note (1 byte follows) */
bytebuff = fgetc(fd);
if ((bytebuff & 128) != 0) return(-13); /* MSB should always be zero */
midievent.type = EVENT_NOTEOFF;
midievent.data.note.note = bytebuff;
midievent.data.note.chan = event_channel;
midievent.data.note.velocity = 0;
break;
case 1: /* play note (1 or 2 bytes follow) */
*channelsusage |= (1 << event_channel); /* update the channel usage flags */
bytebuff = fgetc(fd);
midievent.type = EVENT_NOTEON;
midievent.data.note.note = bytebuff & 127;
midievent.data.note.chan = event_channel;
if ((bytebuff & 128) != 0) {
midievent.data.note.velocity = fgetc(fd);
hdr_or_chanvol[event_channel] = midievent.data.note.velocity; /* remember the last velocity, might be needed later */
} else {
midievent.data.note.velocity = hdr_or_chanvol[event_channel];
}
break;
case 2: /* pitch wheel (1 byte follows) */
/* MIDI says that pitch wheel is a 14bit value with the center being
* at 0x2000. MUS on the other hand provides only 8bits, so some
* adjustement must be made as to fit into this scheme:
* 0 = two half-tones down
* 64 = one half-tone down
* 128 = normal (default)
* 192 = one half-tone up
* 255 = two half-tones up */
bytebuff = fgetc(fd);
midievent.type = EVENT_PITCH;
midievent.data.pitch.wheel = bytebuff << 6; /* convert wheel value to 14 bits as expected by MIDI */
midievent.data.pitch.chan = event_channel;
break;
case 3: /* sysex (1 byte follows) - I ignore SYSEX messages */
bytebuff = fgetc(fd);
if ((bytebuff & 128) != 0) return(-11); /* MSB should always be zero */
break;
case 4: /* control (2 bytes follow) */
bytebuff = fgetc(fd);
bytebuff2 = fgetc(fd);
if ((bytebuff == 0) && (bytebuff2 < 128)) { /* change program */
midievent.type = EVENT_PROGCHAN;
midievent.data.prog.prog = bytebuff2;
midievent.data.note.chan = event_channel;
} else if ((bytebuff >= 1) && (bytebuff <= 9)) { /* else it maps directly to a MIDI controller message */
int tmpmap[10] = {0,0,1,7,10,11,91,93,64,67}; /* map MUS controllers to MIDI controller IDs */
midievent.type = EVENT_CONTROL;
midievent.data.control.chan = event_channel;
midievent.data.control.id = tmpmap[bytebuff];
midievent.data.control.val = bytebuff2;
} else { /* else it's an illegate byte pattern - abort mission, captain! */
return(-91);
}
break;
case 6: /* end of song (no byte follow) */
if (pusheventqueue(NULL, NULL) != 0) return(MUS_OUTOFMEM);
loadflag = 1;
break;
default: /* unknown event type - abort */
return(-3);
break;
}
/* if file loaded fine, break out of the loop now */
if (loadflag != 0) break;
/* fill in the delta time */
midievent.deltatime = nextwait;
/* if dtime is non-zero, read the number of ticks to wait before next note */
nextwait = 0;
while (event_dtime != 0) {
bytebuff = fgetc(fd);
if (bytebuff < 0) return(-6);
event_dtime = bytebuff & 128;
nextwait <<= 7;
nextwait |= (bytebuff & 127);
}
/* push the event into memory */
if (pusheventqueue(&midievent, NULL) != 0) return(MUS_OUTOFMEM);
/* recompute total song's length */
tickduration += nextwait;
while (tickduration >= TIMEUNITDIV) {
mslen += TICKLEN / 1000; /* mslen is in miliseconds, while TICKLEN is in us */
tickduration -= TIMEUNITDIV;
}
}
mslen += (tickduration / TIMEUNITDIV * TICKLEN) / 1000;
*totlen = mslen / 1000; /* totlen is in seconds */
/* */
return(res);
}