/*
** music_midistream.cpp
** Implements base class for MIDI and MUS streaming.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

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

#include "i_musicinterns.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"

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

#define MAX_TIME	(1000000/10)	// Send out 1/10 of a sec of events at a time.

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

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

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

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

EXTERN_CVAR(Float, snd_musicvolume)

#ifdef _WIN32
extern UINT mididevice;
#endif

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

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

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

//==========================================================================
//
// MIDIStreamer Constructor
//
//==========================================================================

MIDIStreamer::MIDIStreamer(EMIDIDevice type)
:
#ifdef _WIN32
  PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
#endif
  MIDI(0), Division(0), InitialTempo(500000), DeviceType(type)
{
#ifdef _WIN32
	BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (BufferDoneEvent == NULL)
	{
		Printf(PRINT_BOLD, "Could not create buffer done event for MIDI playback\n");
	}
	ExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (ExitEvent == NULL)
	{
		Printf(PRINT_BOLD, "Could not create exit event for MIDI playback\n");
		return;
	}
#endif
}

//==========================================================================
//
// MIDIStreamer OPL Dumping Constructor
//
//==========================================================================

MIDIStreamer::MIDIStreamer(const char *dumpname, EMIDIDevice type)
:
#ifdef _WIN32
  PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
#endif
  MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
{
#ifdef _WIN32
	BufferDoneEvent = NULL;
	ExitEvent = NULL;
#endif
}

//==========================================================================
//
// MIDIStreamer Destructor
//
//==========================================================================

MIDIStreamer::~MIDIStreamer()
{
	Stop();
#ifdef _WIN32
	if (ExitEvent != NULL)
	{
		CloseHandle(ExitEvent);
	}
	if (BufferDoneEvent != NULL)
	{
		CloseHandle(BufferDoneEvent);
	}
#endif
	if (MIDI != NULL)
	{
		delete MIDI;
	}
}

//==========================================================================
//
// MIDIStreamer :: IsMIDI
//
// You bet it is!
//
//==========================================================================

bool MIDIStreamer::IsMIDI() const
{
	return true;
}

//==========================================================================
//
// MIDIStreamer :: IsValid
//
//==========================================================================

bool MIDIStreamer::IsValid() const
{
#ifdef _WIN32
	return ExitEvent != NULL && Division != 0;
#else
	return Division != 0;
#endif
}

//==========================================================================
//
// MIDIStreamer :: CheckCaps
//
// Called immediately after the device is opened in case a subclass should
// want to alter its behavior depending on which device it got.
//
//==========================================================================

void MIDIStreamer::CheckCaps()
{
}

//==========================================================================
//
// MIDIStreamer :: Play
//
//==========================================================================

void MIDIStreamer::Play(bool looping, int subsong)
{
	DWORD tid;

	m_Status = STATE_Stopped;
	m_Looping = looping;
	EndQueued = 0;
	VolumeChanged = false;
	Restarting = true;
	InitialPlayback = true;

	assert(MIDI == NULL);
	if (DumpFilename.IsNotEmpty())
	{
		if (DeviceType == MIDI_OPL)
		{
			MIDI = new OPLDumperMIDIDevice(DumpFilename);
		}
		else if (DeviceType == MIDI_Timidity)
		{
#ifdef USE_TIMIDITY
			MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
#endif
		}
	}
	else switch(DeviceType)
	{
	case MIDI_Win:
#ifdef _WIN32
		MIDI = new WinMIDIDevice(mididevice);
		break;
#endif
		assert(0);
		// Intentional fall-through for non-Windows systems.

	case MIDI_Timidity:
#ifdef USE_TIMIDITY
		MIDI = new TimidityMIDIDevice;
#endif
		break;

	case MIDI_OPL:
		MIDI = new OPLMIDIDevice;
		break;
	}
	
#ifndef _WIN32
	assert(MIDI->NeedThreadedCallback() == false);
#endif

	if (0 != MIDI->Open(Callback, this))
	{
		Printf(PRINT_BOLD, "Could not open MIDI out device\n");
		return;
	}

	CheckCaps();
	Precache();

	// Set time division and tempo.
	if (0 != MIDI->SetTimeDiv(Division) ||
		0 != MIDI->SetTempo(Tempo = InitialTempo))
	{
		Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
		MIDI->Close();
		return;
	}

	MusicVolumeChanged();	// set volume to current music's properties
	OutputVolume(Volume);

#ifdef _WIN32
	ResetEvent(ExitEvent);
	ResetEvent(BufferDoneEvent);
#endif

	// Fill the initial buffers for the song.
	BufferNum = 0;
	do
	{
		int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
		if (res == SONG_MORE)
		{
			if (0 != MIDI->StreamOutSync(&Buffer[BufferNum]))
			{
				Printf ("Initial midiStreamOut failed\n");
				Stop();
				return;
			}
			BufferNum ^= 1;
		}
		else if (res == SONG_DONE)
		{
			// Do not play super short songs that can't fill the initial two buffers.
			Stop();
			return;
		}
		else
		{
			Stop();
			return;
		}
	}
	while (BufferNum != 0);

	if (0 != MIDI->Resume())
	{
		Printf ("Starting MIDI playback failed\n");
		Stop();
	}
	else
	{
#ifdef _WIN32
		if (MIDI->NeedThreadedCallback())
		{
			PlayerThread = CreateThread(NULL, 0, PlayerProc, this, 0, &tid);
			if (PlayerThread == NULL)
			{
				Printf ("Creating MIDI thread failed\n");
				Stop();
			}
			else
			{
				m_Status = STATE_Playing;
			}
		}
		else
#endif
		{
			m_Status = STATE_Playing;
		}
	}
}

//==========================================================================
//
// MIDIStreamer :: Pause
//
// "Pauses" the song by setting it to zero volume and filling subsequent
// buffers with NOPs until the song is unpaused. A MIDI device that
// supports real pauses will return true from its Pause() method.
//
//==========================================================================

void MIDIStreamer::Pause()
{
	if (m_Status == STATE_Playing)
	{
		m_Status = STATE_Paused;
		if (!MIDI->Pause(true))
		{
			OutputVolume(0);
		}
	}
}

//==========================================================================
//
// MIDIStreamer :: Resume
//
// "Unpauses" a song by restoring the volume and letting subsequent
// buffers store real MIDI events again.
//
//==========================================================================

void MIDIStreamer::Resume()
{
	if (m_Status == STATE_Paused)
	{
		if (!MIDI->Pause(false))
		{
			OutputVolume(Volume);
		}
		m_Status = STATE_Playing;
	}
}

//==========================================================================
//
// MIDIStreamer :: Stop
//
// Stops playback and closes the player thread and MIDI device.
//
//==========================================================================

void MIDIStreamer::Stop()
{
	EndQueued = 2;
#ifdef _WIN32
	if (PlayerThread != NULL)
	{
		SetEvent(ExitEvent);
		WaitForSingleObject(PlayerThread, INFINITE);
		CloseHandle(PlayerThread);
		PlayerThread = NULL;
	}
#endif
	if (MIDI != NULL && MIDI->IsOpen())
	{
		MIDI->Stop();
		MIDI->UnprepareHeader(&Buffer[0]);
		MIDI->UnprepareHeader(&Buffer[1]);
		MIDI->Close();
	}
	if (MIDI != NULL)
	{
		delete MIDI;
		MIDI = NULL;
	}
	m_Status = STATE_Stopped;
}

//==========================================================================
//
// MIDIStreamer :: IsPlaying
//
//==========================================================================

bool MIDIStreamer::IsPlaying()
{
	return m_Status != STATE_Stopped;
}

//==========================================================================
//
// MIDIStreamer :: MusicVolumeChanged
//
// WinMM MIDI doesn't go through the sound system, so the normal volume
// changing procedure doesn't work for it.
//
//==========================================================================

void MIDIStreamer::MusicVolumeChanged()
{
	if (MIDI->FakeVolume())
	{
		float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f);
		Volume = clamp<DWORD>((DWORD)(realvolume * 65535.f), 0, 65535);
	}
	else
	{
		Volume = 0xFFFF;
	}
	if (m_Status == STATE_Playing)
	{
		OutputVolume(Volume);
	}
}

void MIDIStreamer::TimidityVolumeChanged()
{
	if (MIDI != NULL)
	{
		MIDI->TimidityVolumeChanged();
	}
}


//==========================================================================
//
// MIDIStreamer :: OutputVolume
//
// Signals the buffer filler to send volume change events on all channels.
//
//==========================================================================

void MIDIStreamer::OutputVolume (DWORD volume)
{
	if (MIDI->FakeVolume())
	{
		NewVolume = volume;
		VolumeChanged = true;
	}
}

//==========================================================================
//
// MIDIStreamer :: VolumeControllerChange
//
// Some devices don't support master volume
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
// so assume none of them do and scale channel volumes manually.
//
//==========================================================================

int MIDIStreamer::VolumeControllerChange(int channel, int volume)
{
	ChannelVolumes[channel] = volume;
	return ((volume + 1) * Volume) >> 16;
}

//==========================================================================
//
// MIDIStreamer :: Callback											Static
//
// Signals the BufferDoneEvent to prepare the next buffer. The buffer is not
// prepared in the callback directly, because it's generally still in use by
// the MIDI streamer when this callback is executed.
//
//==========================================================================

void MIDIStreamer::Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2)
{
	MIDIStreamer *self = (MIDIStreamer *)userdata;

	if (self->EndQueued > 1)
	{
		return;
	}
	if (uMsg == MOM_DONE)
	{
#ifdef _WIN32
		if (self->PlayerThread != NULL)
		{
			SetEvent(self->BufferDoneEvent);
		}
		else
#endif
		{
			self->ServiceEvent();
		}
	}
}

//==========================================================================
//
// MIDIStreamer :: Update
//
// Called periodically to see if the player thread is still alive. If it
// isn't, stop playback now.
//
//==========================================================================

void MIDIStreamer::Update()
{
#ifdef _WIN32
	// If the PlayerThread is signalled, then it's dead.
	if (PlayerThread != NULL &&
		WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0)
	{
		CloseHandle(PlayerThread);
		PlayerThread = NULL;
		Printf ("MIDI playback failure\n");
		Stop();
	}
#endif
}

//==========================================================================
//
// MIDIStreamer :: PlayerProc										Static
//
// Entry point for the player thread.
//
//==========================================================================

#ifdef _WIN32
DWORD WINAPI MIDIStreamer::PlayerProc (LPVOID lpParameter)
{
	return ((MIDIStreamer *)lpParameter)->PlayerLoop();
}
#endif

//==========================================================================
//
// MIDIStreamer :: PlayerLoop
//
// Services MIDI playback events.
//
//==========================================================================

#ifdef _WIN32
DWORD MIDIStreamer::PlayerLoop()
{
	HANDLE events[2] = { BufferDoneEvent, ExitEvent };

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

	for (;;)
	{
		switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
		{
		case WAIT_OBJECT_0:
			if (ServiceEvent())
			{
				return 1;
			}
			break;

		case WAIT_OBJECT_0 + 1:
			return 0;

		default:
			// Should not happen.
			return 1;
		}
	}
}
#endif

//==========================================================================
//
// MIDIStreamer :: ServiceEvent
//
// Fills the buffer that just finished playing with new events and appends
// it to the MIDI stream queue. Stops the song if playback is over. Returns
// true if a problem occured and playback should stop.
//
//==========================================================================

bool MIDIStreamer::ServiceEvent()
{
	if (EndQueued == 1)
	{
		return false;
	}
	if (0 != MIDI->UnprepareHeader(&Buffer[BufferNum]))
	{
		return true;
	}
fill:
	switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
	{
	case SONG_MORE:
		if ((MIDI->NeedThreadedCallback() && 0 != MIDI->StreamOutSync(&Buffer[BufferNum])) ||
			(!MIDI->NeedThreadedCallback() && 0 != MIDI->StreamOut(&Buffer[BufferNum])))
		{
			return true;
		}
		else
		{
			BufferNum ^= 1;
		}
		break;

	case SONG_DONE:
		if (m_Looping)
		{
			Restarting = true;
			goto fill;
		}
		EndQueued = 1;
		break;

	default:
		return true;
	}
	return false;
}

//==========================================================================
//
// MIDIStreamer :: FillBuffer
//
// Copies MIDI events from the SMF and puts them into a MIDI stream
// buffer. Filling the buffer stops when the song end is encountered, the
// buffer space is used up, or the maximum time for a buffer is hit.
//
// Can return:
// - SONG_MORE if the buffer was prepared with data.
// - SONG_DONE if the song's end was reached.
//             The buffer will never have data in this case.
// - SONG_ERROR if there was a problem preparing the buffer.
//
//==========================================================================

int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
{
	if (!Restarting && CheckDone())
	{
		return SONG_DONE;
	}

	int i;
	DWORD *events = Events[buffer_num], *max_event_p;
	DWORD tot_time = 0;
	DWORD time = 0;

	// The final event is for a NOP to hold the delay from the last event.
	max_event_p = events + (max_events - 1) * 3;

	if (InitialPlayback)
	{
		InitialPlayback = false;
		// Send the full master volume SysEx message.
		events[0] = 0;								// dwDeltaTime
		events[1] = 0;								// dwStreamID
		events[2] = (MEVT_LONGMSG << 24) | 8;		// dwEvent
		events[3] = MAKE_ID(0xf0,0x7f,0x7f,0x04);	// dwParms[0]
		events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7);	// dwParms[1]
		events += 5;
		DoInitialSetup();
	}

	// If the volume has changed, stick those events at the start of this buffer.
	if (VolumeChanged && (m_Status != STATE_Paused || NewVolume == 0))
	{
		VolumeChanged = false;
		for (i = 0; i < 16; ++i)
		{
			BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
			events[0] = 0;				// dwDeltaTime
			events[1] = 0;				// dwStreamID
			events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
			events += 3;
		}
	}

	// Play nothing while paused.
	if (m_Status == STATE_Paused)
	{
		// Be more responsive when unpausing by only playing each buffer
		// for a third of the maximum time.
		events[0] = MAX<DWORD>(1, (max_time / 3) * Division / Tempo);
		events[1] = 0;
		events[2] = MEVT_NOP << 24;
		events += 3;
	}
	else
	{
		if (Restarting)
		{
			Restarting = false;
			// Stop all notes in case any were left hanging.
			for (i = 0; i < 16; ++i)
			{
				events[0] = 0;				// dwDeltaTime
				events[1] = 0;				// dwStreamID
				events[2] = MIDI_CTRLCHANGE | i | (123 << 8);	// All notes off
				events[3] = 0;
				events[4] = 0;
				events[5] = MIDI_CTRLCHANGE | i | (121 << 8);	// Reset controllers
				events += 6;
			}
			DoRestart();
		}
		events = MakeEvents(events, max_event_p, max_time);
	}
	memset(&Buffer[buffer_num], 0, sizeof(MIDIHDR));
	Buffer[buffer_num].lpData = (LPSTR)Events[buffer_num];
	Buffer[buffer_num].dwBufferLength = DWORD((LPSTR)events - Buffer[buffer_num].lpData);
	Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength;
	if (0 != MIDI->PrepareHeader(&Buffer[buffer_num]))
	{
		return SONG_ERROR;
	}
	return SONG_MORE;
}

//==========================================================================
//
// MIDIStreamer :: GetStats
//
//==========================================================================

FString MIDIStreamer::GetStats()
{
	if (MIDI == NULL)
	{
		return "No MIDI device in use.";
	}
	return MIDI->GetStats();
}

//==========================================================================
//
// MIDIDevice stubs.
//
//==========================================================================

MIDIDevice::MIDIDevice()
{
}

MIDIDevice::~MIDIDevice()
{
}

//==========================================================================
//
// MIDIDevice :: PrecacheInstruments
//
// The MIDIStreamer calls this method between device open and the first
// buffered stream with a list of instruments known to be used by the song.
// If the device can benefit from preloading the instruments, it can do so
// now.
//
// Each entry is packed as follows:
//   Bits 0- 6: Instrument number
//   Bits 7-13: Bank number
//   Bit    14: Select drum set if 1, tone bank if 0
//
//==========================================================================

void MIDIDevice::PrecacheInstruments(const WORD *instruments, int count)
{
}

//==========================================================================
//
// MIDIDevice :: TimidityVolumeChanged
//
//==========================================================================

void MIDIDevice::TimidityVolumeChanged()
{
}

//==========================================================================
//
// MIDIDevice :: GetStats
//
//==========================================================================

FString MIDIDevice::GetStats()
{
	return "This MIDI device does not have any stats.";
}
