mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2026-03-20 09:00:25 +01:00
Initial commit
This commit is contained in:
358
doomclassic/doom/mus2midi.cpp
Normal file
358
doomclassic/doom/mus2midi.cpp
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
===========================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user