// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "ft2_header.h"
#include "ft2_audio.h"
#include "ft2_gui.h"
#include "ft2_pattern_ed.h"
#include "ft2_diskop.h"
#include "scopes/ft2_scopes.h"
#include "ft2_config.h"
#include "ft2_mouse.h"
#include "ft2_sample_ed.h"
#include "ft2_inst_ed.h"
#include "ft2_audio.h"
#include "ft2_wav_renderer.h"
#include "ft2_structs.h"

#define UPDATE_VISUALS_AT_TICK 4
#define TICKS_PER_RENDER_CHUNK 64

enum
{
	WAV_FORMAT_PCM = 0x0001,
	WAV_FORMAT_IEEE_FLOAT = 0x0003
};

typedef struct wavHeader_t
{
	uint32_t chunkID, chunkSize, format, subchunk1ID, subchunk1Size;
	uint16_t audioFormat, numChannels;
	uint32_t sampleRate, byteRate;
	uint16_t blockAlign, bitsPerSample;
	uint32_t subchunk2ID, subchunk2Size;
} wavHeader_t;

static bool renderIndividualTracks, oldMutes[MAX_CHANNELS];
static char *tmpFilename, newFilename[PATH_MAX+1];
static uint8_t WDBitDepth = 16, WDStartPos, WDStopPos, *wavRenderBuffer;
static int16_t WDAmp;
static uint32_t WDFrequency = 44100;
static SDL_Thread *thread;

void cbToggleWavRenderIndividualTracks(void)
{
	renderIndividualTracks ^= 1;
}

static void updateWavRenderer(void)
{
	char str[16];

	fillRect(194, 116, 56, 8, PAL_DESKTOP);
	textOut(236, 116, PAL_FORGRND, "Hz");
	sprintf(str, "%6d", WDFrequency);
	textOutFixed(194, 116, PAL_FORGRND, PAL_DESKTOP, str);
	
	fillRect(229, 130, 21, 8, PAL_DESKTOP);
	charOut(243, 130, PAL_FORGRND, 'x');
	sprintf(str, "%02d", WDAmp);
	textOut(229, 130, PAL_FORGRND, str);

	fillRect(122, 144, 13, 8, PAL_DESKTOP);
	hexOut(122, 144, PAL_FORGRND, WDStartPos, 2);

	fillRect(237, 144, 13, 8, PAL_DESKTOP);
	hexOut(237, 144, PAL_FORGRND, WDStopPos, 2);
}

void setWavRenderFrequency(int32_t freq)
{
	WDFrequency = CLAMP(freq, MIN_WAV_RENDER_FREQ, MAX_WAV_RENDER_FREQ);
	if (ui.wavRendererShown)
		updateWavRenderer();
}

void setWavRenderBitDepth(uint8_t bitDepth)
{
	if (bitDepth == 16)
		WDBitDepth = 16;
	else if (bitDepth == 32)
		WDBitDepth = 32;

	if (ui.wavRendererShown)
		updateWavRenderer();
}

void updateWavRendererSettings(void) // called when changing config.boostLevel
{
	WDAmp = config.boostLevel;
}

void drawWavRenderer(void)
{
	drawFramework(0,   92, 291, 17, FRAMEWORK_TYPE1);
	drawFramework(0,  109,  59, 64, FRAMEWORK_TYPE1);
	drawFramework(59, 109, 232, 64, FRAMEWORK_TYPE1);

	textOutShadow(4,   96, PAL_FORGRND, PAL_DSKTOP2, "WAV exporting:");
	textOutShadow(146, 96, PAL_FORGRND, PAL_DSKTOP2, "16-bit");
	textOutShadow(211, 96, PAL_FORGRND, PAL_DSKTOP2, "32-bit (float)");

	textOutShadow(63,  116, PAL_FORGRND, PAL_DSKTOP2, "Audio output rate");
	textOutShadow(63,  130, PAL_FORGRND, PAL_DSKTOP2, "Amplification");
	textOutShadow(63,  144, PAL_FORGRND, PAL_DSKTOP2, "Start pos");
	textOutShadow(181, 144, PAL_FORGRND, PAL_DSKTOP2, "Stop pos");
	textOutShadow(79,  159, PAL_FORGRND, PAL_DSKTOP2, "Render individual tracks");

	showPushButton(PB_WAV_RENDER);
	showPushButton(PB_WAV_EXIT);
	showPushButton(PB_WAV_FREQ_UP);
	showPushButton(PB_WAV_FREQ_DOWN);
	showPushButton(PB_WAV_AMP_UP);
	showPushButton(PB_WAV_AMP_DOWN);
	showPushButton(PB_WAV_START_UP);
	showPushButton(PB_WAV_START_DOWN);
	showPushButton(PB_WAV_END_UP);
	showPushButton(PB_WAV_END_DOWN);
	showCheckBox(CB_WAV_RENDER_INDIVIDUAL_TRACKS);

	// bitdepth radiobuttons

	radioButtons[RB_WAV_RENDER_BITDEPTH16].state = RADIOBUTTON_UNCHECKED;
	radioButtons[RB_WAV_RENDER_BITDEPTH32].state = RADIOBUTTON_UNCHECKED;

	if (WDBitDepth == 16)
		radioButtons[RB_WAV_RENDER_BITDEPTH16].state = RADIOBUTTON_CHECKED;
	else
		radioButtons[RB_WAV_RENDER_BITDEPTH32].state = RADIOBUTTON_CHECKED;

	showRadioButtonGroup(RB_GROUP_WAV_RENDER_BITDEPTH);

	updateWavRenderer();
}

void resetWavRenderer(void)
{
	WDStartPos = 0;
	WDStopPos = (uint8_t)song.songLength - 1;

	if (ui.wavRendererShown)
		updateWavRenderer();
}

void showWavRenderer(void)
{
	if (ui.extendedPatternEditor)
		exitPatternEditorExtended();

	hideTopScreen();
	showTopScreen(DONT_RESTORE_SCREENS);

	ui.wavRendererShown = true;
	ui.scopesShown = false;

	WDStartPos = 0;
	WDStopPos = (uint8_t)song.songLength - 1;

	drawWavRenderer();
}

void hideWavRenderer(void)
{
	ui.wavRendererShown = false;

	hidePushButton(PB_WAV_RENDER);
	hidePushButton(PB_WAV_EXIT);
	hidePushButton(PB_WAV_FREQ_UP);
	hidePushButton(PB_WAV_FREQ_DOWN);
	hidePushButton(PB_WAV_AMP_UP);
	hidePushButton(PB_WAV_AMP_DOWN);
	hidePushButton(PB_WAV_START_UP);
	hidePushButton(PB_WAV_START_DOWN);
	hidePushButton(PB_WAV_END_UP);
	hidePushButton(PB_WAV_END_DOWN);
	hideRadioButtonGroup(RB_GROUP_WAV_RENDER_BITDEPTH);
	hideCheckBox(CB_WAV_RENDER_INDIVIDUAL_TRACKS);

	ui.scopesShown = true;
	drawScopeFramework();
}

void exitWavRenderer(void)
{
	hideWavRenderer();
}

static bool dump_Init(uint32_t frq, int16_t amp, int16_t songPos)
{
	int32_t bytesPerSample = (WDBitDepth / 8) * 2; // 2 channels
	int32_t maxSamplesPerTick = (int32_t)ceil(frq / (MIN_BPM / 2.5)) + 2; // +2 needed

	// *2 for stereo
	wavRenderBuffer = (uint8_t *)malloc((TICKS_PER_RENDER_CHUNK * maxSamplesPerTick) * bytesPerSample);
	if (wavRenderBuffer == NULL)
		return false;

	// wait for main audio callback to catch WAV render flag
	editor.wavIsRendering = true;
	while (audio.callbackOngoing)
		SDL_Delay(5);

	setSongPos(songPos, 0, RESET_SONG_TICK);
	playMode = PLAYMODE_SONG;
	songPlaying = true;

	resetChannels();
	setNewAudioFreq(frq);
	setAudioAmp(amp, config.masterVol, (WDBitDepth == 32));

	stopVoices();
	song.globalVolume = 64;
	setMixerBPM(song.BPM);

	resetPlaybackTime();
	return true;
}

static void dump_Close(FILE *f, uint32_t totalSamples)
{
	wavHeader_t wavHeader;

	if (wavRenderBuffer != NULL)
	{
		free(wavRenderBuffer);
		wavRenderBuffer = NULL;
	}

	uint32_t totalBytes;
	if (WDBitDepth == 16)
		totalBytes = totalSamples * sizeof (int16_t);
	else
		totalBytes = totalSamples * sizeof (float);

	if (totalBytes & 1)
		fputc(0, f); // write pad byte

	uint32_t tmpLen = ftell(f)-8;

	// go back and fill in WAV header
	rewind(f);

	wavHeader.chunkID = 0x46464952; // "RIFF"
	wavHeader.chunkSize = tmpLen;
	wavHeader.format = 0x45564157; // "WAVE"
	wavHeader.subchunk1ID = 0x20746D66; // "fmt "
	wavHeader.subchunk1Size = 16;

	if (WDBitDepth == 16)
		wavHeader.audioFormat = WAV_FORMAT_PCM;
	else
		wavHeader.audioFormat = WAV_FORMAT_IEEE_FLOAT;

	wavHeader.numChannels = 2;
	wavHeader.sampleRate = WDFrequency;
	wavHeader.byteRate = (wavHeader.sampleRate * wavHeader.numChannels * WDBitDepth) / 8;
	wavHeader.blockAlign = (wavHeader.numChannels * WDBitDepth) / 8;
	wavHeader.bitsPerSample = WDBitDepth;
	wavHeader.subchunk2ID = 0x61746164; // "data"
	wavHeader.subchunk2Size = totalBytes;

	// write main header
	fwrite(&wavHeader, 1, sizeof (wavHeader_t), f);
	fclose(f);

	stopPlaying();

	// kludge: set speed to 6 if speed was set to 0
	if (song.speed == 0)
		song.speed = 6;

	setBackOldAudioFreq();
	setMixerBPM(song.BPM);
	setAudioAmp(config.boostLevel, config.masterVol, !!(config.specialFlags & BITDEPTH_32));
	editor.wavIsRendering = false;

	setMouseBusy(false);
}

static bool dump_EndOfTune(int16_t endSongPos)
{
	bool returnValue = (editor.wavReachedEndFlag && song.row == 0 && song.tick == 1) || (song.speed == 0);

	// FT2 bugfix for EEx (pattern delay) on first row of a pattern
	if (song.pattDelTime2 > 0)
		returnValue = false;

	if (song.songPos == endSongPos && song.row == 0 && song.tick == 1)
		editor.wavReachedEndFlag = true;

	return returnValue;
}

void dump_TickReplayer(void)
{
	replayerBusy = true;
	if (!musicPaused)
	{
		if (audio.volumeRampingFlag)
			resetRampVolumes();

		tickReplayer();
		updateVoices();
	}
	replayerBusy = false;
}

static void updateVisuals(void)
{
	editor.editPattern = (uint8_t)song.pattNum;
	editor.row = song.row;
	editor.songPos = song.songPos;
	editor.BPM = song.BPM;
	editor.speed = song.speed;
	editor.globalVolume = song.globalVolume;

	ui.drawPosEdFlag = true;
	ui.drawPattNumLenFlag = true;
	ui.drawReplayerPianoFlag = true;
	ui.drawBPMFlag = true;
	ui.drawSpeedFlag = true;
	ui.drawGlobVolFlag = true;
	ui.updatePatternEditor = true;
}

static int32_t renderWavThread(void *ptr)
{
	(void)ptr;

	FILE *f = (FILE *)editor.wavRendererFileHandle;
	fseek(f, sizeof (wavHeader_t), SEEK_SET);

	pauseAudio();

	if (!dump_Init(WDFrequency, WDAmp, WDStartPos))
	{
		resumeAudio();
		okBoxThreadSafe(0, "System message", "Not enough memory!", NULL);
		return true;
	}

	uint32_t sampleCounter = 0;
	bool overflow = false, renderDone = false;
	uint8_t tickCounter = UPDATE_VISUALS_AT_TICK;
	uint64_t tickSamplesFrac = 0;

	uint64_t bytesInFile = sizeof (wavHeader_t);

	editor.wavReachedEndFlag = false;
	while (!renderDone)
	{
		uint32_t samplesInChunk = 0;

		// render several ticks at once to prevent frequent disk I/O (speeds up the process)
		uint8_t *ptr8 = wavRenderBuffer;
		for (uint32_t i = 0; i < TICKS_PER_RENDER_CHUNK; i++)
		{
			if (editor.stopWavRender || !editor.wavIsRendering || dump_EndOfTune(WDStopPos))
			{
				editor.stopWavRender = false;
				renderDone = true;
				break;
			}

			dump_TickReplayer();
			uint32_t tickSamples = audio.samplesPerTickInt;

			tickSamplesFrac += audio.samplesPerTickFrac;
			if (tickSamplesFrac >= BPM_FRAC_SCALE)
			{
				tickSamplesFrac &= BPM_FRAC_MASK;
				tickSamples++;
			}

			mixReplayerTickToBuffer(tickSamples, ptr8, WDBitDepth);

			tickSamples *= 2; // stereo
			samplesInChunk += tickSamples;
			sampleCounter += tickSamples;

			// increase buffer pointer
			if (WDBitDepth == 16)
			{
				ptr8 += tickSamples * sizeof (int16_t);
				bytesInFile += tickSamples * sizeof (int16_t);
			}
			else
			{
				ptr8 += tickSamples * sizeof (float);
				bytesInFile += tickSamples * sizeof (float);
			}

			if (bytesInFile >= INT32_MAX)
			{
				renderDone = true;
				overflow = true;
				break;
			}

			if (++tickCounter >= UPDATE_VISUALS_AT_TICK)
			{
				tickCounter = 0;
				updateVisuals();
			}
		}

		// write buffer to disk
		if (samplesInChunk > 0)
		{
			if (WDBitDepth == 16)
				fwrite(wavRenderBuffer, sizeof (int16_t), samplesInChunk, f);
			else
				fwrite(wavRenderBuffer, sizeof (float), samplesInChunk, f);
		}
	}

	updateVisuals();
	drawPlaybackTime(); // this is needed after the song stopped

	dump_Close(f, sampleCounter);
	resumeAudio();

	if (overflow)
		okBoxThreadSafe(0, "System message", "Rendering stopped, file exceeded 2GB!", NULL);

	editor.diskOpReadOnOpen = true;
	return true;
}

static int32_t renderWavIndividualTracksThread(void *ptr)
{
	bool overflow = false;

	(void)ptr;

	int32_t bytesPerSample = (WDBitDepth / 8) * 2; // 2 channels
	int32_t maxSamplesPerTick = (int32_t)ceil(WDFrequency / (MIN_BPM / 2.5)) + 2; // +2 because some headroom is needed

	// *2 for stereo
	wavRenderBuffer = (uint8_t *)malloc((TICKS_PER_RENDER_CHUNK * maxSamplesPerTick) * bytesPerSample);
	if (wavRenderBuffer == NULL)
	{
		diskOpChangeFilenameExt(".wav");
		okBoxThreadSafe(0, "System message", "Not enough memory!", NULL);
		return false;
	}

	// unmute all channels
	for (int32_t i = 0; i < song.numChannels; i++)
	{
		oldMutes[i] = channel[i].channelOff;
		channel[i].channelOff = false;
	}

	pauseAudio();

	// wait for main audio callback to catch WAV render flag
	editor.wavIsRendering = true;
	while (audio.callbackOngoing)
		SDL_Delay(5);

	setNewAudioFreq(WDFrequency);
	setAudioAmp(WDAmp, config.masterVol, (WDBitDepth == 32));

	for (int32_t j = 0; j < song.numChannels; j++)
	{
		if (editor.stopWavRender || overflow)
		{
			editor.stopWavRender = false;
			break;
		}

		sprintf(newFilename, "%s (ch %02d of %02d).wav", tmpFilename, j+1, song.numChannels);
		FILE *f = fopen(newFilename, "wb");
		if (f == NULL)
		{
			editor.stopWavRender = false;

			for (int32_t i = 0; i < song.numChannels; i++)
			{
				channel[i].channelOff = oldMutes[i];
				channel[i].dontRenderThisChannel = false;
			}

			setBackOldAudioFreq();
			setMixerBPM(song.BPM);
			setAudioAmp(config.boostLevel, config.masterVol, !!(config.specialFlags & BITDEPTH_32));
			editor.wavIsRendering = false;
			setMouseBusy(false);
			updateVisuals();
			drawPlaybackTime(); // this is needed after the song stopped
			resumeAudio();

			diskOpChangeFilenameExt(".wav");
			editor.diskOpReadOnOpen = true;

			okBoxThreadSafe(0, "System message", "General I/O error while writing to WAV (is the file in use)?", NULL);
			return true;
		}

		fseek(f, sizeof (wavHeader_t), SEEK_SET);

		resetChannels();
		setSongPos(WDStartPos, 0, RESET_SONG_TICK);
		playMode = PLAYMODE_SONG;
		songPlaying = true;
		stopVoices();
		song.globalVolume = 64;
		setMixerBPM(song.BPM);
		resetPlaybackTime();

		for (int32_t k = 0; k < song.numChannels; k++)
			channel[k].dontRenderThisChannel = (j == k) ? false : true;

		uint32_t sampleCounter = 0;
		bool renderDone = false;
		uint8_t tickCounter = UPDATE_VISUALS_AT_TICK;
		uint64_t tickSamplesFrac = 0;

		uint64_t bytesInFile = sizeof (wavHeader_t);
		overflow = false;

		editor.wavReachedEndFlag = false;
		while (!renderDone)
		{
			uint32_t samplesInChunk = 0;

			// render several ticks at once to prevent frequent disk I/O (speeds up the process)
			uint8_t *ptr8 = wavRenderBuffer;
			for (uint32_t i = 0; i < TICKS_PER_RENDER_CHUNK; i++)
			{
				if (editor.stopWavRender || !editor.wavIsRendering || dump_EndOfTune(WDStopPos))
				{
					renderDone = true;
					break;
				}

				dump_TickReplayer();
				uint32_t tickSamples = audio.samplesPerTickInt;

				tickSamplesFrac += audio.samplesPerTickFrac;
				if (tickSamplesFrac >= BPM_FRAC_SCALE)
				{
					tickSamplesFrac &= BPM_FRAC_MASK;
					tickSamples++;
				}

				mixReplayerTickToBuffer(tickSamples, ptr8, WDBitDepth);

				tickSamples *= 2; // stereo
				samplesInChunk += tickSamples;
				sampleCounter += tickSamples;

				// increase buffer pointer
				if (WDBitDepth == 16)
				{
					ptr8 += tickSamples * sizeof (int16_t);
					bytesInFile += tickSamples * sizeof (int16_t);
				}
				else
				{
					ptr8 += tickSamples * sizeof (float);
					bytesInFile += tickSamples * sizeof (float);
				}

				if (bytesInFile >= INT32_MAX)
				{
					renderDone = true;
					overflow = true;
					break;
				}

				if (++tickCounter >= UPDATE_VISUALS_AT_TICK)
				{
					tickCounter = 0;
					updateVisuals();
				}
			}

			// write buffer to disk
			if (samplesInChunk > 0)
			{
				if (WDBitDepth == 16)
					fwrite(wavRenderBuffer, sizeof (int16_t), samplesInChunk, f);
				else
					fwrite(wavRenderBuffer, sizeof (float), samplesInChunk, f);
			}
		}

		stopPlaying();

		// kludge: set speed to 6 if speed was set to 0
		if (song.speed == 0)
			song.speed = 6;

		wavHeader_t wavHeader;

		uint32_t totalBytes;
		if (WDBitDepth == 16)
			totalBytes = sampleCounter * sizeof (int16_t);
		else
			totalBytes = sampleCounter * sizeof (float);

		if (totalBytes & 1)
			fputc(0, f); // write pad byte

		uint32_t tmpLen = ftell(f)-8;

		// go back and fill in WAV header
		rewind(f);

		wavHeader.chunkID = 0x46464952; // "RIFF"
		wavHeader.chunkSize = tmpLen;
		wavHeader.format = 0x45564157; // "WAVE"
		wavHeader.subchunk1ID = 0x20746D66; // "fmt "
		wavHeader.subchunk1Size = 16;

		if (WDBitDepth == 16)
			wavHeader.audioFormat = WAV_FORMAT_PCM;
		else
			wavHeader.audioFormat = WAV_FORMAT_IEEE_FLOAT;

		wavHeader.numChannels = 2;
		wavHeader.sampleRate = WDFrequency;
		wavHeader.byteRate = (wavHeader.sampleRate * wavHeader.numChannels * WDBitDepth) / 8;
		wavHeader.blockAlign = (wavHeader.numChannels * WDBitDepth) / 8;
		wavHeader.bitsPerSample = WDBitDepth;
		wavHeader.subchunk2ID = 0x61746164; // "data"
		wavHeader.subchunk2Size = totalBytes;

		// write main header
		fwrite(&wavHeader, 1, sizeof (wavHeader_t), f);
		fclose(f);
	}

	free(wavRenderBuffer);

	editor.stopWavRender = false;
	for (int32_t i = 0; i < song.numChannels; i++)
	{
		channel[i].channelOff = oldMutes[i];
		channel[i].dontRenderThisChannel = false;
	}

	setBackOldAudioFreq();
	setMixerBPM(song.BPM);
	setAudioAmp(config.boostLevel, config.masterVol, !!(config.specialFlags & BITDEPTH_32));
	editor.wavIsRendering = false;
	setMouseBusy(false);
	updateVisuals();
	drawPlaybackTime(); // this is needed after the song stopped
	resumeAudio();

	diskOpChangeFilenameExt(".wav");
	editor.diskOpReadOnOpen = true;

	if (overflow)
		okBoxThreadSafe(0, "System message", "Rendering stopped, file exceeded 2GB!", NULL);

	return true;
}

static void wavRender(bool checkOverwrite)
{
	WDStartPos = (uint8_t)(MAX(0, MIN(WDStartPos, song.songLength - 1)));
	WDStopPos  = (uint8_t)(MAX(0, MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1)));

	updateWavRenderer();

	if (!renderIndividualTracks)
	{
		diskOpChangeFilenameExt(".wav");

		char *filename = getDiskOpFilename();
		if (checkOverwrite && fileExistsAnsi(filename))
		{
			char buf[256];
			createFileOverwriteText(filename, buf);
			if (okBox(2, "System request", buf, NULL) != 1)
				return;
		}

		editor.wavRendererFileHandle = fopen(filename, "wb");
		if (editor.wavRendererFileHandle == NULL)
		{
			okBox(0, "System message", "General I/O error while writing to WAV (is the file in use)?", NULL);
			return;
		}
	}
	else
	{
		diskOpChangeFilenameExt("");
		tmpFilename = getDiskOpFilename();
	}

	mouseAnimOn();
	thread = SDL_CreateThread(renderIndividualTracks ? renderWavIndividualTracksThread : renderWavThread, "WAV render thread", NULL);
	if (thread == NULL)
	{
		fclose((FILE *)editor.wavRendererFileHandle);
		okBox(0, "System message", "Couldn't create thread!", NULL);
		return;
	}

	SDL_DetachThread(thread);
}

void pbWavRender(void)
{
	wavRender(config.cfg_OverwriteWarning ? true : false);
}

void pbWavExit(void)
{
	exitWavRenderer();
}

void pbWavFreqUp(void)
{
	if (WDFrequency < MAX_WAV_RENDER_FREQ)
	{
		     if (WDFrequency ==   8000) WDFrequency = 11025;
		else if (WDFrequency ==  11025) WDFrequency = 16000;
		else if (WDFrequency ==  16000) WDFrequency = 22050;
		else if (WDFrequency ==  22050) WDFrequency = 32000;
		else if (WDFrequency ==  32000) WDFrequency = 44100;
		else if (WDFrequency ==  44100) WDFrequency = 48000;
		else if (WDFrequency ==  48000) WDFrequency = 96000;
		else if (WDFrequency ==  96000) WDFrequency = 192000;
		else if (WDFrequency == 192000) WDFrequency = 384000;

		updateWavRenderer();
	}
}

void pbWavFreqDown(void)
{
	if (WDFrequency > MIN_WAV_RENDER_FREQ)
	{
		     if (WDFrequency == 384000) WDFrequency = 192000;
		else if (WDFrequency == 192000) WDFrequency = 96000;
		else if (WDFrequency ==  96000) WDFrequency = 48000;
		else if (WDFrequency ==  48000) WDFrequency = 44100;
		else if (WDFrequency ==  44100) WDFrequency = 32000;
		else if (WDFrequency ==  32000) WDFrequency = 22050;
		else if (WDFrequency ==  22050) WDFrequency = 16000;
		else if (WDFrequency ==  16000) WDFrequency = 11025;
		else if (WDFrequency ==  11025) WDFrequency = 8000;

		updateWavRenderer();
	}
}

void pbWavAmpUp(void)
{
	if (WDAmp >= 32)
		return;

	WDAmp++;
	updateWavRenderer();
}

void pbWavAmpDown(void)
{
	if (WDAmp <= 1)
		return;

	WDAmp--;
	updateWavRenderer();
}

void pbWavSongStartUp(void)
{
	if (WDStartPos >= song.songLength-1)
		return;

	WDStartPos++;
	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
	updateWavRenderer();
}

void pbWavSongStartDown(void)
{
	if (WDStartPos == 0)
		return;

	WDStartPos--;
	updateWavRenderer();
}

void pbWavSongEndUp(void)
{
	if (WDStopPos >= 255)
		return;

	WDStopPos++;
	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
	updateWavRenderer();
}

void pbWavSongEndDown(void)
{
	if (WDStopPos == 0)
		return;

	WDStopPos--;
	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
	updateWavRenderer();
}

void rbWavRenderBitDepth16(void)
{
	checkRadioButton(RB_WAV_RENDER_BITDEPTH16);
	WDBitDepth = 16;
}

void rbWavRenderBitDepth32(void)
{
	checkRadioButton(RB_WAV_RENDER_BITDEPTH32);
	WDBitDepth = 32;
}
