/*
===========================================================================

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;

	// 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;
}

