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 }