ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_wav_renderer.c (13038B)


      1 // for finding memory leaks in debug mode with Visual Studio
      2 #if defined _DEBUG && defined _MSC_VER
      3 #include <crtdbg.h>
      4 #endif
      5 
      6 #include <stdio.h>
      7 #include <stdint.h>
      8 #include <stdbool.h>
      9 #include "ft2_header.h"
     10 #include "ft2_audio.h"
     11 #include "ft2_gui.h"
     12 #include "ft2_pattern_ed.h"
     13 #include "ft2_diskop.h"
     14 #include "scopes/ft2_scopes.h"
     15 #include "ft2_config.h"
     16 #include "ft2_mouse.h"
     17 #include "ft2_sample_ed.h"
     18 #include "ft2_inst_ed.h"
     19 #include "ft2_audio.h"
     20 #include "ft2_wav_renderer.h"
     21 #include "ft2_structs.h"
     22 
     23 #define UPDATE_VISUALS_AT_TICK 4
     24 #define TICKS_PER_RENDER_CHUNK 64
     25 
     26 enum
     27 {
     28 	WAV_FORMAT_PCM = 0x0001,
     29 	WAV_FORMAT_IEEE_FLOAT = 0x0003
     30 };
     31 
     32 typedef struct wavHeader_t
     33 {
     34 	uint32_t chunkID, chunkSize, format, subchunk1ID, subchunk1Size;
     35 	uint16_t audioFormat, numChannels;
     36 	uint32_t sampleRate, byteRate;
     37 	uint16_t blockAlign, bitsPerSample;
     38 	uint32_t subchunk2ID, subchunk2Size;
     39 } wavHeader_t;
     40 
     41 static bool useLegacyBPM = false;
     42 static uint8_t WDBitDepth = 16, WDStartPos, WDStopPos, *wavRenderBuffer;
     43 static int16_t WDAmp;
     44 static uint32_t WDFrequency = 44100;
     45 static SDL_Thread *thread;
     46 
     47 static void updateWavRenderer(void)
     48 {
     49 	char str[16];
     50 
     51 	fillRect(195, 116, 56, 8, PAL_DESKTOP);
     52 	textOut(237, 116, PAL_FORGRND, "Hz");
     53 	sprintf(str, "%6d", WDFrequency);
     54 	textOutFixed(195, 116, PAL_FORGRND, PAL_DESKTOP, str);
     55 	
     56 	fillRect(229, 130, 21, 8, PAL_DESKTOP);
     57 	charOut(243, 130, PAL_FORGRND, 'x');
     58 	sprintf(str, "%02d", WDAmp);
     59 	textOut(229, 130, PAL_FORGRND, str);
     60 
     61 	fillRect(237, 144, 13, 8, PAL_DESKTOP);
     62 	hexOut(237, 144, PAL_FORGRND, WDStartPos, 2);
     63 
     64 	fillRect(237, 158, 13, 8, PAL_DESKTOP);
     65 	hexOut(237, 158, PAL_FORGRND, WDStopPos, 2);
     66 }
     67 
     68 void cbToggleWavRenderBPMMode(void)
     69 {
     70 	useLegacyBPM ^= 1;
     71 }
     72 
     73 void setWavRenderFrequency(int32_t freq)
     74 {
     75 	WDFrequency = CLAMP(freq, MIN_WAV_RENDER_FREQ, MAX_WAV_RENDER_FREQ);
     76 	if (ui.wavRendererShown)
     77 		updateWavRenderer();
     78 }
     79 
     80 void setWavRenderBitDepth(uint8_t bitDepth)
     81 {
     82 	if (bitDepth == 16)
     83 		WDBitDepth = 16;
     84 	else if (bitDepth == 32)
     85 		WDBitDepth = 32;
     86 
     87 	if (ui.wavRendererShown)
     88 		updateWavRenderer();
     89 }
     90 
     91 void updateWavRendererSettings(void) // called when changing config.boostLevel
     92 {
     93 	WDAmp = config.boostLevel;
     94 }
     95 
     96 void drawWavRenderer(void)
     97 {
     98 	drawFramework(0,   92, 291, 17, FRAMEWORK_TYPE1);
     99 	drawFramework(0,  109,  79, 64, FRAMEWORK_TYPE1);
    100 	drawFramework(79, 109, 212, 64, FRAMEWORK_TYPE1);
    101 
    102 	textOutShadow(4,   96, PAL_FORGRND, PAL_DSKTOP2, "WAV exporting:");
    103 	textOutShadow(146, 96, PAL_FORGRND, PAL_DSKTOP2, "16-bit");
    104 	textOutShadow(211, 96, PAL_FORGRND, PAL_DSKTOP2, "32-bit (float)");
    105 
    106 	textOutShadow(19, 114, PAL_FORGRND, PAL_DSKTOP2, "Imprecise");
    107 	textOutShadow(4,  127, PAL_FORGRND, PAL_DSKTOP2, "BPM (FT2)");
    108 
    109 	textOutShadow(85, 116, PAL_FORGRND, PAL_DSKTOP2, "Audio output rate");
    110 	textOutShadow(85, 130, PAL_FORGRND, PAL_DSKTOP2, "Amplification");
    111 	textOutShadow(85, 144, PAL_FORGRND, PAL_DSKTOP2, "Start song position");
    112 	textOutShadow(85, 158, PAL_FORGRND, PAL_DSKTOP2, "Stop song position");
    113 
    114 	showPushButton(PB_WAV_RENDER);
    115 	showPushButton(PB_WAV_EXIT);
    116 	showPushButton(PB_WAV_FREQ_UP);
    117 	showPushButton(PB_WAV_FREQ_DOWN);
    118 	showPushButton(PB_WAV_AMP_UP);
    119 	showPushButton(PB_WAV_AMP_DOWN);
    120 	showPushButton(PB_WAV_START_UP);
    121 	showPushButton(PB_WAV_START_DOWN);
    122 	showPushButton(PB_WAV_END_UP);
    123 	showPushButton(PB_WAV_END_DOWN);
    124 
    125 	showCheckBox(CB_WAV_BPM_MODE);
    126 
    127 	// bitdepth radiobuttons
    128 
    129 	radioButtons[RB_WAV_RENDER_BITDEPTH16].state = RADIOBUTTON_UNCHECKED;
    130 	radioButtons[RB_WAV_RENDER_BITDEPTH32].state = RADIOBUTTON_UNCHECKED;
    131 
    132 	if (WDBitDepth == 16)
    133 		radioButtons[RB_WAV_RENDER_BITDEPTH16].state = RADIOBUTTON_CHECKED;
    134 	else
    135 		radioButtons[RB_WAV_RENDER_BITDEPTH32].state = RADIOBUTTON_CHECKED;
    136 
    137 	showRadioButtonGroup(RB_GROUP_WAV_RENDER_BITDEPTH);
    138 
    139 	updateWavRenderer();
    140 }
    141 
    142 void resetWavRenderer(void)
    143 {
    144 	WDStartPos = 0;
    145 	WDStopPos = (uint8_t)song.songLength - 1;
    146 
    147 	if (ui.wavRendererShown)
    148 		updateWavRenderer();
    149 }
    150 
    151 void showWavRenderer(void)
    152 {
    153 	if (ui.extendedPatternEditor)
    154 		exitPatternEditorExtended();
    155 
    156 	hideTopScreen();
    157 	showTopScreen(false);
    158 
    159 	ui.wavRendererShown = true;
    160 	ui.scopesShown = false;
    161 
    162 	WDStartPos = 0;
    163 	WDStopPos = (uint8_t)song.songLength - 1;
    164 
    165 	drawWavRenderer();
    166 }
    167 
    168 void hideWavRenderer(void)
    169 {
    170 	ui.wavRendererShown = false;
    171 
    172 	hidePushButton(PB_WAV_RENDER);
    173 	hidePushButton(PB_WAV_EXIT);
    174 	hidePushButton(PB_WAV_FREQ_UP);
    175 	hidePushButton(PB_WAV_FREQ_DOWN);
    176 	hidePushButton(PB_WAV_AMP_UP);
    177 	hidePushButton(PB_WAV_AMP_DOWN);
    178 	hidePushButton(PB_WAV_START_UP);
    179 	hidePushButton(PB_WAV_START_DOWN);
    180 	hidePushButton(PB_WAV_END_UP);
    181 	hidePushButton(PB_WAV_END_DOWN);
    182 	hideCheckBox(CB_WAV_BPM_MODE);
    183 	hideRadioButtonGroup(RB_GROUP_WAV_RENDER_BITDEPTH);
    184 
    185 	ui.scopesShown = true;
    186 	drawScopeFramework();
    187 }
    188 
    189 void exitWavRenderer(void)
    190 {
    191 	hideWavRenderer();
    192 }
    193 
    194 static bool dump_Init(uint32_t frq, int16_t amp, int16_t songPos)
    195 {
    196 	int32_t bytesPerSample = (WDBitDepth / 8) * 2; // 2 channels
    197 	int32_t maxSamplesPerTick = (int32_t)ceil(frq / (MIN_BPM / 2.5)) + 1;
    198 
    199 	// *2 for stereo
    200 	wavRenderBuffer = (uint8_t *)malloc((TICKS_PER_RENDER_CHUNK * maxSamplesPerTick) * bytesPerSample);
    201 	if (wavRenderBuffer == NULL)
    202 		return false;
    203 
    204 	editor.wavIsRendering = true;
    205 
    206 	setPos(songPos, 0, true);
    207 	playMode = PLAYMODE_SONG;
    208 	songPlaying = true;
    209 
    210 	resetChannels();
    211 	setNewAudioFreq(frq);
    212 	setAudioAmp(amp, config.masterVol, (WDBitDepth == 32));
    213 
    214 	stopVoices();
    215 	song.globalVolume = 64;
    216 	setMixerBPM(song.BPM);
    217 
    218 	resetPlaybackTime();
    219 	return true;
    220 }
    221 
    222 static void dump_Close(FILE *f, uint32_t totalSamples)
    223 {
    224 	wavHeader_t wavHeader;
    225 
    226 	if (wavRenderBuffer != NULL)
    227 	{
    228 		free(wavRenderBuffer);
    229 		wavRenderBuffer = NULL;
    230 	}
    231 
    232 	uint32_t totalBytes;
    233 	if (WDBitDepth == 16)
    234 		totalBytes = totalSamples * sizeof (int16_t);
    235 	else
    236 		totalBytes = totalSamples * sizeof (float);
    237 
    238 	if (totalBytes & 1)
    239 		fputc(0, f); // write pad byte
    240 
    241 	uint32_t tmpLen = ftell(f)-8;
    242 
    243 	// go back and fill in WAV header
    244 	rewind(f);
    245 
    246 	wavHeader.chunkID = 0x46464952; // "RIFF"
    247 	wavHeader.chunkSize = tmpLen;
    248 	wavHeader.format = 0x45564157; // "WAVE"
    249 	wavHeader.subchunk1ID = 0x20746D66; // "fmt "
    250 	wavHeader.subchunk1Size = 16;
    251 
    252 	if (WDBitDepth == 16)
    253 		wavHeader.audioFormat = WAV_FORMAT_PCM;
    254 	else
    255 		wavHeader.audioFormat = WAV_FORMAT_IEEE_FLOAT;
    256 
    257 	wavHeader.numChannels = 2;
    258 	wavHeader.sampleRate = WDFrequency;
    259 	wavHeader.byteRate = (wavHeader.sampleRate * wavHeader.numChannels * WDBitDepth) / 8;
    260 	wavHeader.blockAlign = (wavHeader.numChannels * WDBitDepth) / 8;
    261 	wavHeader.bitsPerSample = WDBitDepth;
    262 	wavHeader.subchunk2ID = 0x61746164; // "data"
    263 	wavHeader.subchunk2Size = totalBytes;
    264 
    265 	// write main header
    266 	fwrite(&wavHeader, 1, sizeof (wavHeader_t), f);
    267 	fclose(f);
    268 
    269 	stopPlaying();
    270 
    271 	// kludge: set speed to 6 if speed was set to 0
    272 	if (song.speed == 0)
    273 		song.speed = 6;
    274 
    275 	setBackOldAudioFreq();
    276 	setMixerBPM(song.BPM);
    277 	setAudioAmp(config.boostLevel, config.masterVol, !!(config.specialFlags & BITDEPTH_32));
    278 	editor.wavIsRendering = false;
    279 
    280 	setMouseBusy(false);
    281 }
    282 
    283 static bool dump_EndOfTune(int16_t endSongPos)
    284 {
    285 	bool returnValue = (editor.wavReachedEndFlag && song.row == 0 && song.tick == 1) || (song.speed == 0);
    286 
    287 	// FT2 bugfix for EEx (pattern delay) on first row of a pattern
    288 	if (song.pattDelTime2 > 0)
    289 		returnValue = false;
    290 
    291 	if (song.songPos == endSongPos && song.row == 0 && song.tick == 1)
    292 		editor.wavReachedEndFlag = true;
    293 
    294 	return returnValue;
    295 }
    296 
    297 void dump_TickReplayer(void)
    298 {
    299 	replayerBusy = true;
    300 	if (!musicPaused)
    301 	{
    302 		if (audio.volumeRampingFlag)
    303 			resetRampVolumes();
    304 
    305 		tickReplayer();
    306 		updateVoices();
    307 	}
    308 	replayerBusy = false;
    309 }
    310 
    311 static void updateVisuals(void)
    312 {
    313 	editor.editPattern = (uint8_t)song.pattNum;
    314 	editor.row = song.row;
    315 	editor.songPos = song.songPos;
    316 	editor.BPM = song.BPM;
    317 	editor.speed = song.speed;
    318 	editor.globalVolume = song.globalVolume;
    319 
    320 	ui.drawPosEdFlag = true;
    321 	ui.drawPattNumLenFlag = true;
    322 	ui.drawReplayerPianoFlag = true;
    323 	ui.drawBPMFlag = true;
    324 	ui.drawSpeedFlag = true;
    325 	ui.drawGlobVolFlag = true;
    326 	ui.updatePatternEditor = true;
    327 }
    328 
    329 static int32_t SDLCALL renderWavThread(void *ptr)
    330 {
    331 	(void)ptr;
    332 
    333 	FILE *f = (FILE *)editor.wavRendererFileHandle;
    334 	fseek(f, sizeof (wavHeader_t), SEEK_SET);
    335 
    336 	pauseAudio();
    337 
    338 	if (!dump_Init(WDFrequency, WDAmp, WDStartPos))
    339 	{
    340 		resumeAudio();
    341 		okBoxThreadSafe(0, "System message", "Not enough memory!", NULL);
    342 		return true;
    343 	}
    344 
    345 	uint32_t sampleCounter = 0;
    346 	bool overflow = false, renderDone = false;
    347 	uint8_t tickCounter = UPDATE_VISUALS_AT_TICK;
    348 	uint64_t tickSamplesFrac = 0;
    349 
    350 	uint64_t bytesInFile = sizeof (wavHeader_t);
    351 
    352 	editor.wavReachedEndFlag = false;
    353 	while (!renderDone)
    354 	{
    355 		uint32_t samplesInChunk = 0;
    356 
    357 		// render several ticks at once to prevent frequent disk I/O (speeds up the process)
    358 		uint8_t *ptr8 = wavRenderBuffer;
    359 		for (uint32_t i = 0; i < TICKS_PER_RENDER_CHUNK; i++)
    360 		{
    361 			if (!editor.wavIsRendering || dump_EndOfTune(WDStopPos))
    362 			{
    363 				renderDone = true;
    364 				break;
    365 			}
    366 
    367 			dump_TickReplayer();
    368 			uint32_t tickSamples = audio.samplesPerTickInt;
    369 
    370 			if (!useLegacyBPM)
    371 			{
    372 				tickSamplesFrac += audio.samplesPerTickFrac;
    373 				if (tickSamplesFrac >= BPM_FRAC_SCALE)
    374 				{
    375 					tickSamplesFrac &= BPM_FRAC_MASK;
    376 					tickSamples++;
    377 				}
    378 			}
    379 
    380 			mixReplayerTickToBuffer(tickSamples, ptr8, WDBitDepth);
    381 
    382 			tickSamples *= 2; // stereo
    383 			samplesInChunk += tickSamples;
    384 			sampleCounter += tickSamples;
    385 
    386 			// increase buffer pointer
    387 			if (WDBitDepth == 16)
    388 			{
    389 				ptr8 += tickSamples * sizeof (int16_t);
    390 				bytesInFile += tickSamples * sizeof (int16_t);
    391 			}
    392 			else
    393 			{
    394 				ptr8 += tickSamples * sizeof (float);
    395 				bytesInFile += tickSamples * sizeof (float);
    396 			}
    397 
    398 			if (bytesInFile >= INT32_MAX)
    399 			{
    400 				renderDone = true;
    401 				overflow = true;
    402 				break;
    403 			}
    404 
    405 			if (++tickCounter >= UPDATE_VISUALS_AT_TICK)
    406 			{
    407 				tickCounter = 0;
    408 				updateVisuals();
    409 			}
    410 		}
    411 
    412 		// write buffer to disk
    413 		if (samplesInChunk > 0)
    414 		{
    415 			if (WDBitDepth == 16)
    416 				fwrite(wavRenderBuffer, sizeof (int16_t), samplesInChunk, f);
    417 			else
    418 				fwrite(wavRenderBuffer, sizeof (float), samplesInChunk, f);
    419 		}
    420 	}
    421 
    422 	updateVisuals();
    423 	drawPlaybackTime(); // this is needed after the song stopped
    424 
    425 	dump_Close(f, sampleCounter);
    426 	resumeAudio();
    427 
    428 	if (overflow)
    429 		okBoxThreadSafe(0, "System message", "Rendering stopped, file exceeded 2GB!", NULL);
    430 
    431 	editor.diskOpReadOnOpen = true;
    432 	return true;
    433 }
    434 
    435 static void wavRender(bool checkOverwrite)
    436 {
    437 	WDStartPos = (uint8_t)(MAX(0, MIN(WDStartPos, song.songLength - 1)));
    438 	WDStopPos  = (uint8_t)(MAX(0, MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1)));
    439 
    440 	updateWavRenderer();
    441 
    442 	diskOpChangeFilenameExt(".wav");
    443 
    444 	char *filename = getDiskOpFilename();
    445 	if (checkOverwrite && fileExistsAnsi(filename))
    446 	{
    447 		char buf[256];
    448 		createFileOverwriteText(filename, buf);
    449 		if (okBox(2, "System request", buf, NULL) != 1)
    450 			return;
    451 	}
    452 
    453 	editor.wavRendererFileHandle = fopen(filename, "wb");
    454 	if (editor.wavRendererFileHandle == NULL)
    455 	{
    456 		okBox(0, "System message", "General I/O error while writing to WAV (is the file in use)?", NULL);
    457 		return;
    458 	}
    459 
    460 	mouseAnimOn();
    461 	thread = SDL_CreateThread(renderWavThread, NULL, NULL);
    462 	if (thread == NULL)
    463 	{
    464 		fclose((FILE *)editor.wavRendererFileHandle);
    465 		okBox(0, "System message", "Couldn't create thread!", NULL);
    466 		return;
    467 	}
    468 
    469 	SDL_DetachThread(thread);
    470 }
    471 
    472 void pbWavRender(void)
    473 {
    474 	wavRender(config.cfg_OverwriteWarning ? true : false);
    475 }
    476 
    477 void pbWavExit(void)
    478 {
    479 	exitWavRenderer();
    480 }
    481 
    482 void pbWavFreqUp(void)
    483 {
    484 	if (WDFrequency < MAX_WAV_RENDER_FREQ)
    485 	{
    486 		     if (WDFrequency ==  44100) WDFrequency = 48000;
    487 		else if (WDFrequency ==  48000) WDFrequency = 96000;
    488 		else if (WDFrequency ==  96000) WDFrequency = 192000;
    489 		else if (WDFrequency == 192000) WDFrequency = 384000;
    490 
    491 		updateWavRenderer();
    492 	}
    493 }
    494 
    495 void pbWavFreqDown(void)
    496 {
    497 	if (WDFrequency > MIN_WAV_RENDER_FREQ)
    498 	{
    499 		     if (WDFrequency == 384000) WDFrequency = 192000;
    500 		else if (WDFrequency == 192000) WDFrequency = 96000;
    501 		else if (WDFrequency ==  96000) WDFrequency = 48000;
    502 		else if (WDFrequency ==  48000) WDFrequency = 44100;
    503 
    504 		updateWavRenderer();
    505 	}
    506 }
    507 
    508 void pbWavAmpUp(void)
    509 {
    510 	if (WDAmp >= 32)
    511 		return;
    512 
    513 	WDAmp++;
    514 	updateWavRenderer();
    515 }
    516 
    517 void pbWavAmpDown(void)
    518 {
    519 	if (WDAmp <= 1)
    520 		return;
    521 
    522 	WDAmp--;
    523 	updateWavRenderer();
    524 }
    525 
    526 void pbWavSongStartUp(void)
    527 {
    528 	if (WDStartPos >= song.songLength-1)
    529 		return;
    530 
    531 	WDStartPos++;
    532 	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
    533 	updateWavRenderer();
    534 }
    535 
    536 void pbWavSongStartDown(void)
    537 {
    538 	if (WDStartPos == 0)
    539 		return;
    540 
    541 	WDStartPos--;
    542 	updateWavRenderer();
    543 }
    544 
    545 void pbWavSongEndUp(void)
    546 {
    547 	if (WDStopPos >= 255)
    548 		return;
    549 
    550 	WDStopPos++;
    551 	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
    552 	updateWavRenderer();
    553 }
    554 
    555 void pbWavSongEndDown(void)
    556 {
    557 	if (WDStopPos == 0)
    558 		return;
    559 
    560 	WDStopPos--;
    561 	WDStopPos = (uint8_t)(MIN(MAX(WDStartPos, WDStopPos), song.songLength - 1));
    562 	updateWavRenderer();
    563 }
    564 
    565 void rbWavRenderBitDepth16(void)
    566 {
    567 	checkRadioButton(RB_WAV_RENDER_BITDEPTH16);
    568 	WDBitDepth = 16;
    569 }
    570 
    571 void rbWavRenderBitDepth32(void)
    572 {
    573 	checkRadioButton(RB_WAV_RENDER_BITDEPTH32);
    574 	WDBitDepth = 32;
    575 }