0
0
Fork 0
mirror of https://github.com/id-Software/DOOM-3-BFG.git synced 2025-03-16 15:41:16 +00:00
doom3-bfg/doomclassic/doom/mus2midi.cpp
2022-09-05 22:25:33 +02:00

392 lines
9.9 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>
// 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;
if( cur[0] != 'M' || cur[1] != 'U' || cur[2] != 'S' )
{
return 0;
}
// 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;
}