//**************************************************************************
//**
//**	##   ##    ##    ##   ##   ####     ####   ###     ###
//**	##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
//**	 ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
//**	 ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
//**	  ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
//**	   #    ##    ##    #      ####     ####   ##       ##
//**
//**	$Id: snd_qmus2mid.cpp 3866 2008-11-17 18:58:49Z dj_jl $
//**
//**	Copyright (C) 1999-2006 Jānis Legzdiņš
//**
//**	This program 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 2
//**  of the License, or (at your option) any later version.
//**
//**	This program 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.
//**
//**************************************************************************
//
//  Quick MUS->MID ! by S.Bacquet
//
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "gamedefs.h"
#include "snd_local.h"

// MACROS ------------------------------------------------------------------

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// CODE --------------------------------------------------------------------

const vuint8		VQMus2Mid::Mus2MidControl[15] =
{
	0,				//	Program change - not a MIDI control change
	0x00,			//	Bank select
	0x01,			//	Modulation pot
	0x07,			//	Volume
	0x0A,			//	Pan pot
	0x0B,			//	Expression pot
	0x5B,			//	Reverb depth
	0x5D,			//	Chorus depth
	0x40,			//	Sustain pedal
	0x43,			//	Soft pedal
	0x78,			//	All sounds off
	0x7B,			//	All notes off
	0x7E,			//	Mono
	0x7F,			//	Poly
	0x79			//	Reset all controllers
};
const vuint8		VQMus2Mid::TrackEnd[] =
{
	0x00, 0xff, 47, 0x00
};
const vuint8		VQMus2Mid::MidiKey[] =
{
	0x00, 0xff, 0x59, 0x02, 0x00, 0x00   		// C major
};
const vuint8		VQMus2Mid::MidiTempo[] =
{
	0x00, 0xff, 0x51, 0x03, 0x09, 0xa3, 0x1a	// uS/qnote
};

//==========================================================================
//
//	VQMus2Mid::FirstChannelAvailable
//
//==========================================================================

int VQMus2Mid::FirstChannelAvailable()
{
	guard(VQMus2Mid::FirstChannelAvailable);
	int 	old15 = Mus2MidChannel[15];
	int		max = -1;

	Mus2MidChannel[15] = -1;
	for (int i = 0; i < 16; i++)
	{
		if (Mus2MidChannel[i] > max)
		{
			max = Mus2MidChannel[i];
		}
	}
	Mus2MidChannel[15] = old15;

	return (max == 8 ? 10 : max + 1);
	unguard;
}

//==========================================================================
//
//	VQMus2Mid::TWriteByte
//
//==========================================================================

void VQMus2Mid::TWriteByte(int MIDItrack, vuint8 data)
{
	guard(VQMus2Mid::TWriteByte);
	Tracks[MIDItrack].Data.Append(data);
	unguard;
}

//==========================================================================
//
//	VQMus2Mid::TWriteBuf
//
//==========================================================================

void VQMus2Mid::TWriteBuf(int MIDItrack, const vuint8* buf, int size)
{
	guard(VQMus2Mid::TWriteBuf);
	for (int i = 0; i < size; i++)
	{
		TWriteByte(MIDItrack, buf[i]);
	}
	unguard;
}

//==========================================================================
//
//	VQMus2Mid::TWriteVarLen
//
//==========================================================================

void VQMus2Mid::TWriteVarLen(int tracknum, vuint32 value)
{
	guard(VQMus2Mid::TWriteVarLen);
	vuint32 buffer = value & 0x7f;
	while ((value >>= 7))
	{
		buffer <<= 8;
		buffer |= 0x80;
		buffer += (value & 0x7f);
	}
	while (1)
	{
		TWriteByte(tracknum, buffer);
		if (buffer & 0x80)
			buffer >>= 8;
		else
			break;
	}
	unguard;
}

//==========================================================================
//
//	VQMus2Mid::ReadTime
//
//==========================================================================

vuint32 VQMus2Mid::ReadTime(VStream& Strm)
{
	guard(VQMus2Mid::ReadTime);
	vuint32		time = 0;
	vuint8		data;

	if (Strm.AtEnd())
		return 0;
	do
	{
		Strm << data;
		time = (time << 7) + (data & 0x7F);
	} while (!Strm.AtEnd() && (data & 0x80));

	return time;
	unguard;
}

//==========================================================================
//
//  VQMus2Mid::Convert
//
//==========================================================================

bool VQMus2Mid::Convert(VStream& Strm)
{
	guard(VQMus2Mid::Convert);
	vuint8				et;
	int					MUSchannel;
	int					MIDIchannel;
	int					MIDItrack = 0;
	int					NewEvent;
	int 				i;
	vuint8				event;
	vuint8				data;
	vuint32				DeltaTime;
	vuint8				MIDIchan2track[16];
	bool 				ouch = false;
	FMusHeader			MUSh;

	for (i = 0; i < 16; i++)
	{
		Mus2MidChannel[i] = -1;
	}
	for (i = 0; i < 32; i++)
	{
		Tracks[i].DeltaTime = 0;
		Tracks[i].LastEvent = 0;
		Tracks[i].Vel = 64;
		Tracks[i].Data.Clear();
	}

	Strm.Serialise(&MUSh, sizeof(FMusHeader));
	if (VStr::NCmp(MUSh.ID, MUSMAGIC, 4))
	{
		GCon->Log("Not a MUS file");
		return false;
	}

	if ((vuint16)LittleShort(MUSh.NumChannels) > 15)	 /* <=> MUSchannels+drums > 16 */
	{
		GCon->Log(NAME_Dev,"Too many channels");
		return false;
	}

	Strm.Seek((vuint16)LittleShort(MUSh.ScoreStart));

	TWriteBuf(0, MidiKey, 6);
	TWriteBuf(0, MidiTempo, 7);

	TrackCnt = 1;	//	Music starts here

	Strm << event;
	et = (event & 0x70) >> 4;
	MUSchannel = event & 0x0f;
	while ((et != 6) && !Strm.AtEnd())
	{
		if (Mus2MidChannel[MUSchannel] == -1)
		{
			MIDIchannel = Mus2MidChannel[MUSchannel] =
				(MUSchannel == 15 ? 9 : FirstChannelAvailable());
			MIDItrack = MIDIchan2track[MIDIchannel] = TrackCnt++;
		}
		else
		{
			MIDIchannel = Mus2MidChannel[MUSchannel];
			MIDItrack = MIDIchan2track[MIDIchannel];
		}
		TWriteVarLen(MIDItrack, Tracks[MIDItrack].DeltaTime);
		Tracks[MIDItrack].DeltaTime = 0;

		switch (et)
		{
		//	Release note
		case 0:
			//NewEvent = 0x90 | MIDIchannel;
			NewEvent = 0x80 | MIDIchannel;
			TWriteByte(MIDItrack, NewEvent);
			Tracks[MIDItrack].LastEvent = NewEvent;
			Strm << data;
			TWriteByte(MIDItrack, data);
			//TWriteByte(MIDItrack, 0);
			TWriteByte(MIDItrack, 64);
			break;

		//	Note on
		case 1:
			NewEvent = 0x90 | MIDIchannel;
			TWriteByte(MIDItrack, NewEvent);
			Tracks[MIDItrack].LastEvent = NewEvent;
			Strm << data;
			TWriteByte(MIDItrack, data & 0x7F);
			if (data & 0x80)
			{
				Strm << data;
				Tracks[MIDItrack].Vel = data;
			}
			TWriteByte(MIDItrack, Tracks[MIDItrack].Vel);
			break;

		//	Pitch wheel
		case 2:
			NewEvent = 0xE0 | MIDIchannel;
			TWriteByte(MIDItrack, NewEvent);
			Tracks[MIDItrack].LastEvent = NewEvent;
			Strm << data;
			TWriteByte(MIDItrack, (data & 1) << 6);
			TWriteByte(MIDItrack, data >> 1);
			break;

		//	Control change
		case 3:
			NewEvent = 0xB0 | MIDIchannel;
			TWriteByte(MIDItrack, NewEvent);
			Tracks[MIDItrack].LastEvent = NewEvent;
			Strm << data;
			check(data < 15);
			TWriteByte(MIDItrack, Mus2MidControl[data]);
			if (data == 12)
				//TWriteByte(MIDItrack, LittleShort(MUSh.NumChannels) + 1);
				TWriteByte(MIDItrack, LittleShort(MUSh.NumChannels));
			else
				TWriteByte(MIDItrack, 0);
			break;

		//	Control or program change
		case 4:
			Strm << data;
			if (data)
			{
				NewEvent = 0xB0 | MIDIchannel;
				TWriteByte(MIDItrack, NewEvent);
				Tracks[MIDItrack].LastEvent = NewEvent;
				check(data < 15);
				TWriteByte(MIDItrack, Mus2MidControl[data]);
			}
			else
			{
				NewEvent = 0xC0 | MIDIchannel;
				TWriteByte(MIDItrack, NewEvent);
				Tracks[MIDItrack].LastEvent = NewEvent;
			}
			Strm << data;
			TWriteByte(MIDItrack, data);
			break;

		case 5:
		case 7:
			GCon->Log(NAME_Dev,"MUS file corupted");
			return false;
		default:
			break;
		}

		if (event & 0x80)
		{
			DeltaTime = ReadTime(Strm);
			for (i = 0; i < 16; i++)
				Tracks[i].DeltaTime += DeltaTime;
		}

		if (!Strm.AtEnd())
		{
			Strm << event;
			et = (event & 0x70) >> 4;
			MUSchannel = event & 0x0f;
		}
		else
		{
			ouch = true;
		}
	}

	for (i = 0; i < TrackCnt; i++)
	{
		TWriteBuf(i, TrackEnd, 4);
	}

	if (ouch)
	{
		GCon->Logf(NAME_Dev, "WARNING : There are bytes missing at the end.");
		GCon->Logf(NAME_Dev, "The end of the MIDI file might not fit the original one.");
	}

	return true;
	unguard;
}

//==========================================================================
//
//	VQMus2Mid::WriteMIDIFile
//
//==========================================================================

void VQMus2Mid::WriteMIDIFile(VStream& Strm)
{
	guard(VQMus2Mid::WriteMIDIFile);
	//	Header
	char HdrId[4] = { 'M', 'T', 'h', 'd' };
	vuint32 HdrSize = 6;
	vuint16 HdrType = 1;
	vuint16 HdrNumTracks = TrackCnt;
	vuint16 HdrDivisions = 89;

	Strm.Serialise(HdrId, 4);
	Strm.SerialiseBigEndian(&HdrSize, 4);
	Strm.SerialiseBigEndian(&HdrType, 2);
	Strm.SerialiseBigEndian(&HdrNumTracks, 2);
	Strm.SerialiseBigEndian(&HdrDivisions, 2);

	//	Tracks
	for (int i = 0; i < (int)TrackCnt; i++)
	{
		//	Identifier.
		char TrackId[4] = { 'M', 'T', 'r', 'k' };
		Strm.Serialise(TrackId, 4);

		//	Data size.
		vuint32 TrackSize = Tracks[i].Data.Num();
		Strm.SerialiseBigEndian(&TrackSize, 4);

		//	Data.
		Strm.Serialise(Tracks[i].Data.Ptr(), Tracks[i].Data.Num());
	}
	unguard;
}

//==========================================================================
//
//  VQMus2Mid::Run
//
//==========================================================================

int VQMus2Mid::Run(VStream& InStrm, VStream& OutStrm)
{
	guard(VQMus2Mid::Run);
	if (Convert(InStrm))
	{
		WriteMIDIFile(OutStrm);
	}
	return OutStrm.TotalSize();
	unguard;
}
