doom3-bfg/doomclassic/doom/mus2midi.cpp
2012-11-26 12:58:24 -06:00

358 lines
9.5 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "Precompiled.h"
#include "globaldata.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "idlib/sys/sys_defines.h"
// mus header
// reads a variable length integer
unsigned long ReadVarLen( char* buffer ) {
unsigned long value;
byte c;
if ((value = *buffer++) & 0x80) {
value &= 0x7f;
do {
value = (value << 7) + ((c = *buffer++) & 0x7f);
} while (c & 0x80);
}
return value;
}
// Writes a variable length integer to a buffer, and returns bytes written
int WriteVarLen( long value, byte* out )
{
long buffer, count = 0;
buffer = value & 0x7f;
while ((value >>= 7) > 0) {
buffer <<= 8;
buffer += 0x80;
buffer += (value & 0x7f);
}
while (1) {
++count;
*out = (byte)buffer;
++out;
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
return count;
}
// writes a byte, and returns the buffer
unsigned char* WriteByte(void* buf, byte b)
{
unsigned char* buffer = (unsigned char*)buf;
*buffer++ = b;
return buffer;
}
unsigned char* WriteShort(void* b, unsigned short s)
{
unsigned char* buffer = (unsigned char*)b;
*buffer++ = (s >> 8);
*buffer++ = (s & 0x00FF);
return buffer;
}
unsigned char* WriteInt(void* b, unsigned int i)
{
unsigned char* buffer = (unsigned char*)b;
*buffer++ = (i & 0xff000000) >> 24;
*buffer++ = (i & 0x00ff0000) >> 16;
*buffer++ = (i & 0x0000ff00) >> 8;
*buffer++ = (i & 0x000000ff);
return buffer;
}
// Format - 0(1 track only), 1(1 or more tracks, each play same time), 2(1 or more, each play seperatly)
void Midi_CreateHeader(MidiHeaderChunk_t* header, short format, short track_count, short division)
{
WriteInt(header->name, 'MThd');
WriteInt(&header->length, 6);
WriteShort(&header->format, format);
WriteShort(&header->ntracks, track_count);
WriteShort(&header->division, division);
}
unsigned char* Midi_WriteTempo(unsigned char* buffer, int tempo)
{
buffer = WriteByte(buffer, 0x00); // delta time
buffer = WriteByte(buffer, 0xff); // sys command
buffer = WriteShort(buffer, 0x5103); // command - set tempo
buffer = WriteByte(buffer, tempo & 0x000000ff);
buffer = WriteByte(buffer, (tempo & 0x0000ff00) >> 8);
buffer = WriteByte(buffer, (tempo & 0x00ff0000) >> 16);
return buffer;
}
int Midi_UpdateBytesWritten(int* bytes_written, int to_add, int max)
{
*bytes_written += to_add;
if (max && *bytes_written > max)
{
assert(0);
return 0;
}
return 1;
}
unsigned char MidiMap[] =
{
0, // prog change
0, // bank sel
1, //2 // mod pot
0x07, //3 // volume
0x0A, //4 // pan pot
0x0B, //5 // expression pot
0x5B, //6 // reverb depth
0x5D, //7 // chorus depth
0x40, //8 // sustain pedal
0x43, //9 // soft pedal
0x78, //10 // all sounds off
0x7B, //11 // all notes off
0x7E, //12 // mono(use numchannels + 1)
0x7F, //13 // poly
0x79, //14 // reset all controllers
};
// The MUS data is stored in little-endian.
namespace {
unsigned short LittleToNative( const unsigned short value ) {
return value;
}
}
int Mus2Midi(unsigned char* bytes, unsigned char* out, int* len)
{
// mus header and instruments
MUSheader_t header;
// current position in read buffer
unsigned char* cur = bytes,* end;
// Midi header(format 0)
MidiHeaderChunk_t midiHeader;
// Midi track header, only 1 needed(format 0)
MidiTrackChunk_t midiTrackHeader;
// Stores the position of the midi track header(to change the size)
byte* midiTrackHeaderOut;
// Delta time for midi event
int delta_time = 0;
int temp;
int channel_volume[MIDI_MAXCHANNELS] = {0};
int bytes_written = 0;
int channelMap[MIDI_MAXCHANNELS], currentChannel = 0;
byte last_status = 0;
// read the mus header
memcpy(&header, cur, sizeof(header));
cur += sizeof(header);
header.scoreLen = LittleToNative( header.scoreLen );
header.scoreStart = LittleToNative( header.scoreStart );
header.channels = LittleToNative( header.channels );
header.sec_channels = LittleToNative( header.sec_channels );
header.instrCnt = LittleToNative( header.instrCnt );
header.dummy = LittleToNative( header.dummy );
// only 15 supported
if (header.channels > MIDI_MAXCHANNELS - 1)
return 0;
// Map channel 15 to 9(percussions)
for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) {
channelMap[temp] = -1;
channel_volume[temp] = 0x40;
}
channelMap[15] = 9;
// Get current position, and end of position
cur = bytes + header.scoreStart;
end = cur + header.scoreLen;
// Write out midi header
Midi_CreateHeader(&midiHeader, 0, 1, 0x0059);
Midi_UpdateBytesWritten(&bytes_written, MIDIHEADERSIZE, *len);
memcpy(out, &midiHeader, MIDIHEADERSIZE); // cannot use sizeof(packs it to 16 bytes)
out += MIDIHEADERSIZE;
// Store this position, for later filling in the midiTrackHeader
Midi_UpdateBytesWritten(&bytes_written, sizeof(midiTrackHeader), *len);
midiTrackHeaderOut = out;
out += sizeof(midiTrackHeader);
// microseconds per quarter note(yikes)
Midi_UpdateBytesWritten(&bytes_written, 7, *len);
out = Midi_WriteTempo(out, 0x001aa309);
// Percussions channel starts out at full volume
Midi_UpdateBytesWritten(&bytes_written, 4, *len);
out = WriteByte(out, 0x00);
out = WriteByte(out, 0xB9);
out = WriteByte(out, 0x07);
out = WriteByte(out, 127);
// Main Loop
while (cur < end) {
byte channel;
byte event;
byte temp_buffer[32]; // temp buffer for current iterator
byte *out_local = temp_buffer;
byte status, bit1, bit2, bitc = 2;
// Read in current bit
event = *cur++;
channel = (event & 15); // current channel
// Write variable length delta time
out_local += WriteVarLen(delta_time, out_local);
if (channelMap[channel] < 0) {
// Set all channels to 127 volume
out_local = WriteByte(out_local, 0xB0 + currentChannel);
out_local = WriteByte(out_local, 0x07);
out_local = WriteByte(out_local, 127);
out_local = WriteByte(out_local, 0x00);
channelMap[channel] = currentChannel++;
if (currentChannel == 9)
++currentChannel;
}
status = channelMap[channel];
// Handle ::g->events
switch ((event & 122) >> 4)
{
default:
assert(0);
break;
case MUSEVENT_KEYOFF:
status |= 0x80;
bit1 = *cur++;
bit2 = 0x40;
break;
case MUSEVENT_KEYON:
status |= 0x90;
bit1 = *cur & 127;
if (*cur++ & 128) // volume bit?
channel_volume[channelMap[channel]] = *cur++;
bit2 = channel_volume[channelMap[channel]];
break;
case MUSEVENT_PITCHWHEEL:
status |= 0xE0;
bit1 = (*cur & 1) >> 6;
bit2 = (*cur++ >> 1) & 127;
break;
case MUSEVENT_CHANNELMODE:
status |= 0xB0;
assert(*cur < sizeof(MidiMap) / sizeof(MidiMap[0]));
bit1 = MidiMap[*cur++];
bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00;
break;
case MUSEVENT_CONTROLLERCHANGE:
if (*cur == 0) {
cur++;
status |= 0xC0;
bit1 = *cur++;
bitc = 1;
} else {
status |= 0xB0;
assert(*cur < sizeof(MidiMap) / sizeof(MidiMap[0]));
bit1 = MidiMap[*cur++];
bit2 = *cur++;
}
break;
case 5: // Unknown
assert(0);
break;
case MUSEVENT_END: // End
status = 0xff;
bit1 = 0x2f;
bit2 = 0x00;
assert(cur == end);
break;
case 7: // Unknown
assert(0);
break;
}
// Write it out
out_local = WriteByte(out_local, status);
out_local = WriteByte(out_local, bit1);
if (bitc == 2)
out_local = WriteByte(out_local, bit2);
// Write out temp stuff
if (out_local != temp_buffer)
{
Midi_UpdateBytesWritten(&bytes_written, out_local - temp_buffer, *len);
memcpy(out, temp_buffer, out_local - temp_buffer);
out += out_local - temp_buffer;
}
if (event & 128) {
delta_time = 0;
do {
delta_time = delta_time * 128 + (*cur & 127);
} while ((*cur++ & 128));
} else {
delta_time = 0;
}
}
// Write out track header
WriteInt(midiTrackHeader.name, 'MTrk');
WriteInt(&midiTrackHeader.length, out - midiTrackHeaderOut - sizeof(midiTrackHeader));
memcpy(midiTrackHeaderOut, &midiTrackHeader, sizeof(midiTrackHeader));
// Store length written
*len = bytes_written;
/*{
FILE* file = f o pen("d:\\test.midi", "wb");
fwrite(midiTrackHeaderOut - sizeof(MidiHeaderChunk_t), bytes_written, 1, file);
fclose(file);
}*/
return 1;
}