#include "GlobHdr.h"
#include "IFX.h"
#include "FFX.h"
#include "FChannel.h"
#include "FNotes.h"
#include "FTables.h"

static void FX_RefreshNotes(IFX* pFx)
{
	// reset all channels note pointers to zero
	Channels_ClearNotes(&pFx->Channels[0], ifx_max_channels);

	// refresh note list
	Notes_Refresh(&pFx->Notes);
}

static void FX_UpdateMix(IFX* pFx)
{
	Channel* pChannel;
	int count;

	//---------
	// Step 1.  Remove all playing notes terminated by the mixer
	FX_RefreshNotes(pFx);

	//---------
	// Step 3.  Process channels effects

	for( count = ifx_max_channels
	   , pChannel = &pFx->Channels[0]
	   ; count > 0
	   ; count--, pChannel++
	   )
	{
		Note* pNote = pChannel->pnote;
		pChannel->GPanning = pChannel->panning.value.start;
		pChannel->GVolume = pChannel->channel_volume.value.start;
		pChannel->NVolume = pChannel->note_volume.value.start;

		if (pNote)
		{
			pNote->ch_panning = pChannel->GPanning;
			pNote->ch_volume = pChannel->NVolume * pChannel->GVolume;
		}
	}

	//---------
	// Step 4 - Process notes and build note list

	_kernel_swi_regs r;
	r.r[0] = 253;
	CSeq_Configure(pFx->pGlb, &r);
	if (r.r[1] & seq_status_SMLASupport)
		pFx->Notes.MixFrequency = CSeq_GetPlayingFrequency(pFx->pGlb);
	else
		pFx->Notes.MixFrequency = 0;

	Notes_BuildActiveNotesList(&pFx->Notes);

	//---------
	// Step 5.  Update new notes for Volume Ramping

	for( count = ifx_max_channels
	   , pChannel = &pFx->Channels[0]
	   ; count > 0
	   ; count--, pChannel++
	   )
	{
		if (pChannel->flags & ch_flags_triggernote)
		{
			pChannel->flags &= ~ch_flags_triggernote;
			Note* pNote = pChannel->pnote;

			if (pNote)
			{
				pNote->old_panning = pNote->panning;
				pNote->old_volume = 0;
				pNote->old_frequency = pNote->frequency;
			}
		}
	}

	//---------
	// Step 7 - Update stream panning and volume

	count = pFx->Notes.ActiveNotes;
	Note** ppNote = &pFx->Notes.ActiveNotesArray[0];

	if (count > 0)
	{
		// Amplification
		uint32_t svolume = pFx->Seq.volume;

		if (pFx->PreAmp)
		{
			// use Pre-Amp
			svolume = (svolume * pFx->PreAmp) >> 8;
		}
		else
		{
			// divide by polyphony
			if (pFx->Seq.maxstreams)
				svolume = (svolume << 8) / pFx->Seq.maxstreams;
			else
				svolume = (svolume << 8);
		}

		// Scale by recommanded scale volume ?
		if (pFx->Status & ifx_status_scalevolume)
			svolume = (svolume * pFx->pGlb->emulvolume) >> 8;

		for (;count > 0; count--)
		{
			Note* pNote = *ppNote++;
			// Update new values.
			// Update streaming values for when ramping is disabled.
			// Update panning, beware we cannot ramp to surround.
			if (pNote->new_panning > 256)
				pNote->old_panning = pNote->panning;
			pNote->stream.panning =
			pNote->new_panning = pNote->panning;
			// update volume
			uint32_t volume = MulH16(svolume, pNote->volume);
			// take into account muted channels
			if (pNote->pchannel->flags & ch_flags_mute)
				pNote->old_volume = volume = 0;
			pNote->stream.volume =
			pNote->new_volume = volume;
			// update frequency
			pNote->stream.frequency =
			pNote->new_frequency = pNote->frequency;
		}
	}
}

/***
 *
 * FX_Play
 *
 * volume [0, 256], -1 to use default note volume
 * panning [0,256], 384 for surround, -1 to use default
 */
void FX_Play(IFX* pFx, Channel* pChannel, int32_t volume, int32_t panning)
{
	FX_RefreshNotes(pFx);
	Channel_SetInstrument(pChannel, true);
	Channel_TriggerNote(pChannel, &pFx->Notes);

	// update panning?
	if (panning >= 0)
	{
		if ((panning != Panning_Surround)
		&&  (panning > 256))
			panning = 128;
		pChannel->panning.value.final =
		pChannel->panning.value.start = panning;
	}

	Note* pNote = pChannel->pnote;
	if (!pNote) return;

	// update volume?
	if (volume >= 0)
	{
		if (volume > 256) volume = 256;
		pChannel->note_volume.value.final =
		pChannel->note_volume.value.start = volume;
	}

	// read sample pitch
	if (pNote->psample)
	{
		pNote->ch_pitch = (pChannel->smp_note << 8) + pNote->psample->RelTone;
	}

	// update note list
	FX_UpdateMix(pFx);
}

int FX_Changer(IFX* pFx, int duration)
{
	int time;

	// Time to update channels info?
	if (!(pFx->Status & ifx_status_paused))
	{
		if (pFx->FrameTime >= pFx->TempoTime)
		{
			pFx->FrameTime -= pFx->TempoTime;
			FX_UpdateMix(pFx);
		}

		time = pFx->TempoTime - pFx->FrameTime;
		if (duration > time)
			duration = time;
	}

	return duration;
}

/*---------------
 * ISong_Lister
 *
 * In  - R0  fill duration in 1/256 us
 *
 * Out - R0  streams ptr
 *       R1  nr of streams
 */

void FX_Lister(_kernel_swi_regs* r, IFX* pFx)
{
	if (pFx->Status & ifx_status_paused)
	{
		// return empty list
		r->r[1] = 0;

		return;
	}

	pFx->FrameTime += r->r[0];
	r->r[0] = (int) &pFx->Notes.ActiveNotesArray[0];
	r->r[1] = pFx->Notes.ActiveNotes;
}
