ref: 00e1d902b0391ce80e40e42071abcf6a95314ff8
dir: /src/ft2_sample_saver.c/
// 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> #ifndef _WIN32 #include <unistd.h> // chdir() #endif #include "ft2_header.h" #include "ft2_gui.h" #include "ft2_sample_ed.h" #include "ft2_diskop.h" #include "ft2_mouse.h" #include "ft2_structs.h" 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; typedef struct sampleLoop_t { uint32_t dwIdentifier, dwType, dwStart, dwEnd, dwFraction, dwPlayCount; } sampleLoop_t; typedef struct samplerChunk_t { uint32_t chunkID, chunkSize, dwManufacturer, dwProduct, dwSamplePeriod; uint32_t dwMIDIUnityNote, dwMIDIPitchFraction, dwSMPTEFormat; uint32_t dwSMPTEOffset, cSampleLoops, cbSamplerData; sampleLoop_t loop; } samplerChunk_t; typedef struct mptExtraChunk_t { uint32_t chunkID, chunkSize, flags; uint16_t defaultPan, defaultVolume, globalVolume, reserved; uint8_t vibratoType, vibratoSweep, vibratoDepth, vibratoRate; } mptExtraChunk_t; static const char *rangedDataStr = "Ranged data from FT2"; // thread data static bool saveRangeFlag; static SDL_Thread *thread; // restores modified interpolation tap samples after loopEnd (for .RAW/.IFF/.WAV samples after save) static void fileRestoreFixedSampleData(UNICHAR *filenameU, uint32_t sampleDataOffset, sample_t *s) { if (!s->isFixed) return; // nothing to restore int32_t sampleFixPos = s->fixedPos; int32_t sampleFixOffset = 0; int32_t samplesToWrite = SINC_MAX_RIGHT_TAPS; if (saveRangeFlag) { const int32_t markStart = getSampleRangeStart(); const int32_t markEnd = getSampleRangeEnd(); if (markStart > sampleFixPos+SINC_MAX_RIGHT_TAPS || markEnd < sampleFixPos) return; // nothing to do here if (markStart > sampleFixPos) { sampleFixOffset += markStart-sampleFixPos; samplesToWrite -= markStart-sampleFixPos; } sampleFixPos -= markStart; if (sampleFixPos + samplesToWrite > markEnd) samplesToWrite = markEnd - sampleFixPos; if (samplesToWrite < 0 || samplesToWrite > SINC_MAX_RIGHT_TAPS || sampleFixPos < 0 || sampleFixOffset < 0 || sampleFixOffset >= SINC_MAX_RIGHT_TAPS) return; } FILE* f = UNICHAR_FOPEN(filenameU, "r+"); // open in read+update mode if (f == NULL) return; bool sample16Bit = !!(s->flags & SAMPLE_16BIT); if (sample16Bit) fseek(f, sampleDataOffset + (sampleFixPos * 2), SEEK_SET); else fseek(f, sampleDataOffset + sampleFixPos, SEEK_SET); if (sample16Bit) { fwrite(&s->fixedSmp[sampleFixOffset], sizeof (int16_t), samplesToWrite, f); } else { for (int32_t i = 0; i < samplesToWrite; i++) { int8_t fixedSmp = (int8_t)s->fixedSmp[sampleFixOffset+i]; if (editor.sampleSaveMode == SMP_SAVE_MODE_WAV) // on 8-bit WAVs the sample data is unsigned fixedSmp ^= 0x80; // signed -> unsigned fwrite(&fixedSmp, sizeof (int8_t), 1, f); } } fclose(f); } static bool saveRawSample(UNICHAR *filenameU, bool saveRangedData) { int8_t *samplePtr; uint32_t sampleLen; instr_t *ins = instr[editor.curInstr]; if (ins == NULL || ins->smp[editor.curSmp].dataPtr == NULL || ins->smp[editor.curSmp].length == 0) { okBoxThreadSafe(0, "System message", "The sample is empty!"); return false; } sample_t *smp = &instr[editor.curInstr]->smp[editor.curSmp]; bool sample16Bit = !!(smp->flags & SAMPLE_16BIT); if (saveRangedData) { samplePtr = &smp->dataPtr[getSampleRangeStart() << sample16Bit]; sampleLen = getSampleRangeLength(); } else { sampleLen = smp->length; samplePtr = smp->dataPtr; } FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL) { okBoxThreadSafe(0, "System message", "General I/O error during saving! Is the file in use?"); return false; } if (fwrite(samplePtr, sampleLen, 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving sample: General I/O error!"); return false; } fclose(f); // restore modified interpolation tap samples after loopEnd bool loopEnabled = GET_LOOPTYPE(smp->flags) != LOOP_OFF; if (loopEnabled && smp->length > smp->loopStart+smp->loopLength) fileRestoreFixedSampleData(filenameU, 0, smp); editor.diskOpReadDir = true; // force diskop re-read setMouseBusy(false); return true; } static void iffWriteChunkHeader(FILE *f, char *chunkName, uint32_t chunkLen) { fwrite(chunkName, sizeof (int32_t), 1, f); chunkLen = SWAP32(chunkLen); fwrite(&chunkLen, sizeof (int32_t), 1, f); } static void iffWriteUint32(FILE *f, uint32_t value) { value = SWAP32(value); fwrite(&value, sizeof (int32_t), 1, f); } static void iffWriteUint16(FILE *f, uint16_t value) { value = SWAP16(value); fwrite(&value, sizeof (int16_t), 1, f); } static void iffWriteUint8(FILE *f, const uint8_t value) { fwrite(&value, sizeof (int8_t), 1, f); } static void iffWriteChunkData(FILE *f, const void *data, size_t length) { fwrite(data, sizeof (int8_t), length, f); if (length & 1) fputc(0, f); // write pad byte if chunk size is uneven } static bool saveIFFSample(UNICHAR *filenameU, bool saveRangedData) { char *smpNamePtr; int8_t *samplePtr; uint32_t sampleLen, smpNameLen, chunkLen; instr_t *ins = instr[editor.curInstr]; if (ins == NULL || ins->smp[editor.curSmp].dataPtr == NULL || ins->smp[editor.curSmp].length == 0) { okBoxThreadSafe(0, "System message", "The sample is empty!"); return false; } sample_t *smp = &instr[editor.curInstr]->smp[editor.curSmp]; FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL) { okBoxThreadSafe(0, "System message", "General I/O error during saving! Is the file in use?"); return false; } bool sample16Bit = !!(smp->flags & SAMPLE_16BIT); if (saveRangedData) { samplePtr = &smp->dataPtr[getSampleRangeStart() << sample16Bit]; sampleLen = getSampleRangeLength(); } else { sampleLen = smp->length; samplePtr = smp->dataPtr; } // "FORM" chunk iffWriteChunkHeader(f, "FORM", 0); // "FORM" chunk size is overwritten later iffWriteUint32(f, sample16Bit ? 0x31365356 : 0x38535658); // bitdepth - "16SV" (16-bit) or "8SVX" (8-bit) // "VHDR" chunk iffWriteChunkHeader(f, "VHDR", 20); if (!saveRangedData && GET_LOOPTYPE(smp->flags) != LOOP_OFF) { iffWriteUint32(f, smp->loopStart << sample16Bit); // oneShotHiSamples iffWriteUint32(f, smp->loopLength << sample16Bit); // repeatHiSamples } else { iffWriteUint32(f, 0); // oneShotHiSamples iffWriteUint32(f, 0); // repeatHiSamples } iffWriteUint32(f, 0); // samplesPerHiCycle // samplesPerSec uint32_t tmp32 = getSampleMiddleCRate(smp); if (tmp32 == 0 || tmp32 > 65535) tmp32 = 16726; iffWriteUint16(f, (uint16_t)tmp32); iffWriteUint8(f, 1); // ctOctave (number of samples) iffWriteUint8(f, 0); // sCompression iffWriteUint32(f, smp->volume * 1024); // volume (max: 65536/0x10000) // "NAME" chunk if (saveRangedData) { smpNamePtr = (char *)rangedDataStr; smpNameLen = (uint32_t)strlen(rangedDataStr); } else { smpNamePtr = smp->name; smpNameLen = 0; while (smpNameLen < 22) { if (smpNamePtr[smpNameLen] == '\0') break; smpNameLen++; } } // "NAME" chunk chunkLen = smpNameLen; if (chunkLen > 0) { iffWriteChunkHeader(f, "NAME", chunkLen); iffWriteChunkData(f, smpNamePtr, chunkLen); } // "ANNO" chunk (we put the program name here) chunkLen = sizeof (PROG_NAME_STR) - 1; iffWriteChunkHeader(f, "ANNO", chunkLen); iffWriteChunkData(f, PROG_NAME_STR, chunkLen); // "BODY" chunk chunkLen = sampleLen << sample16Bit; iffWriteChunkHeader(f, "BODY", chunkLen); const uint32_t sampleDataPos = ftell(f); iffWriteChunkData(f, samplePtr, chunkLen); // go back and fill in "FORM" chunk size chunkLen = ftell(f) - 8; fseek(f, 4, SEEK_SET); iffWriteUint32(f, chunkLen); fclose(f); // restore modified interpolation tap samples after loopEnd bool loopEnabled = GET_LOOPTYPE(smp->flags) != LOOP_OFF; if (loopEnabled && smp->length > smp->loopStart+smp->loopLength) fileRestoreFixedSampleData(filenameU, sampleDataPos, smp); editor.diskOpReadDir = true; // force diskop re-read setMouseBusy(false); return true; } static bool saveWAVSample(UNICHAR *filenameU, bool saveRangedData) { char *smpNamePtr; int8_t *samplePtr; uint32_t i, sampleLen, riffChunkSize, smpNameLen, tmpLen; wavHeader_t wavHeader; samplerChunk_t samplerChunk; mptExtraChunk_t mptExtraChunk; instr_t *ins = instr[editor.curInstr]; if (ins == NULL || ins->smp[editor.curSmp].dataPtr == NULL || ins->smp[editor.curSmp].length == 0) { okBoxThreadSafe(0, "System message", "The sample is empty!"); return false; } sample_t *smp = &ins->smp[editor.curSmp]; FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL) { okBoxThreadSafe(0, "System message", "General I/O error during saving! Is the file in use?"); return false; } bool sample16Bit = !!(smp->flags & SAMPLE_16BIT); if (saveRangedData) { samplePtr = &smp->dataPtr[getSampleRangeStart() << sample16Bit]; sampleLen = getSampleRangeLength(); } else { sampleLen = smp->length; samplePtr = smp->dataPtr; } const uint8_t sampleBitDepth = sample16Bit ? 16 : 8; wavHeader.chunkID = 0x46464952; // "RIFF" wavHeader.chunkSize = 0; // is filled later wavHeader.format = 0x45564157; // "WAVE" wavHeader.subchunk1ID = 0x20746D66; // "fmt " wavHeader.subchunk1Size = 16; wavHeader.audioFormat = 1; wavHeader.numChannels = 1; wavHeader.sampleRate = getSampleMiddleCRate(smp); wavHeader.byteRate = (wavHeader.sampleRate * wavHeader.numChannels * sampleBitDepth) / 8; wavHeader.blockAlign = (wavHeader.numChannels * sampleBitDepth) / 8; wavHeader.bitsPerSample = sampleBitDepth; wavHeader.subchunk2ID = 0x61746164; // "data" wavHeader.subchunk2Size = sampleLen << sample16Bit; // write main header fwrite(&wavHeader, sizeof (wavHeader_t), 1, f); // write sample data const uint32_t sampleDataPos = ftell(f); if (sampleBitDepth == 16) { fwrite((int16_t *)samplePtr, sizeof (int16_t), sampleLen, f); } else { for (i = 0; i < sampleLen; i++) fputc(samplePtr[i] ^ 0x80, f); // write as unsigned 8-bit data } if (wavHeader.subchunk2Size & 1) fputc(0, f); // write pad byte if chunk size is uneven // write "smpl" chunk if loop is enabled if (!saveRangedData && GET_LOOPTYPE(smp->flags) != LOOP_OFF) { memset(&samplerChunk, 0, sizeof (samplerChunk)); samplerChunk.chunkID = 0x6C706D73; // "smpl" samplerChunk.chunkSize = sizeof (samplerChunk) - 4 - 4; samplerChunk.dwSamplePeriod = 1000000000 / wavHeader.sampleRate; samplerChunk.dwMIDIUnityNote = 60; // 60 = MIDI middle-C samplerChunk.cSampleLoops = 1; samplerChunk.loop.dwType = GET_LOOPTYPE(smp->flags)-1; // 0 = forward, 1 = ping-pong samplerChunk.loop.dwStart = smp->loopStart; samplerChunk.loop.dwEnd = (smp->loopStart + smp->loopLength) - 1; fwrite(&samplerChunk, sizeof (samplerChunk), 1, f); if (samplerChunk.chunkSize & 1) fputc(0, f); // write pad byte if chunk size is uneven } // write modplug tracker "xtra" chunk if (!saveRangedData) { memset(&mptExtraChunk, 0, sizeof (mptExtraChunk)); mptExtraChunk.chunkID = 0x61727478; // "xtra" mptExtraChunk.chunkSize = sizeof (mptExtraChunk) - 4 - 4; mptExtraChunk.flags = 0x20; // set pan flag mptExtraChunk.defaultPan = smp->panning; // 0..255 mptExtraChunk.defaultVolume = smp->volume * 4; // 0..256 mptExtraChunk.globalVolume = 64; // 0..64 mptExtraChunk.vibratoType = ins->autoVibType; // 0..3 0 = sine, 1 = square, 2 = ramp up, 3 = ramp down mptExtraChunk.vibratoSweep = ins->autoVibSweep; // 0..255 mptExtraChunk.vibratoDepth = ins->autoVibDepth; // 0..15 mptExtraChunk.vibratoRate = ins->autoVibRate; // 0..63 fwrite(&mptExtraChunk, sizeof (mptExtraChunk), 1, f); if (mptExtraChunk.chunkSize & 1) fputc(0, f); // write pad byte if chunk size is uneven } // write LIST->INFO->INAM chunk if (saveRangedData) { smpNamePtr = (char *)rangedDataStr; smpNameLen = (uint32_t)strlen(smpNamePtr); } else { smpNamePtr = smp->name; smpNameLen = 0; while (smpNameLen < 22) { if (smpNamePtr[smpNameLen] == '\0') break; smpNameLen++; } } const uint32_t progNameLen = sizeof (PROG_NAME_STR) - 1; tmpLen = 4 + (4 + 4) + (progNameLen + 1 + ((progNameLen + 1) & 1)); if (smpNameLen > 0) tmpLen += ((4 + 4) + (progNameLen + 1 + ((progNameLen + 1) & 1))); fwrite("LIST", sizeof (int32_t), 1, f); fwrite(&tmpLen, sizeof (int32_t), 1, f); fwrite("INFO", sizeof (int32_t), 1, f); if (smpNameLen > 0) { tmpLen = smpNameLen + 1; fwrite("INAM", sizeof (int32_t), 1, f); fwrite(&tmpLen, sizeof (int32_t), 1, f); fwrite(smpNamePtr, 1, smpNameLen, f); fputc(0, f); // string termination if (tmpLen & 1) fputc(0, f); // pad byte } tmpLen = progNameLen + 1; fwrite("ISFT", sizeof (int32_t), 1, f); fwrite(&tmpLen, sizeof (int32_t), 1, f); fwrite(PROG_NAME_STR, 1, progNameLen, f); fputc(0, f); // string termination if (tmpLen & 1) fputc(0, f); // pad byte // go back and fill in "RIFF" chunk size riffChunkSize = ftell(f) - 8; fseek(f, 4, SEEK_SET); fwrite(&riffChunkSize, sizeof (int32_t), 1, f); fclose(f); // restore modified interpolation tap samples after loopEnd bool loopEnabled = GET_LOOPTYPE(smp->flags) != LOOP_OFF; if (loopEnabled && smp->length > smp->loopStart+smp->loopLength) fileRestoreFixedSampleData(filenameU, sampleDataPos, smp); editor.diskOpReadDir = true; // force diskop re-read setMouseBusy(false); return true; } static int32_t SDLCALL saveSampleThread(void *ptr) { if (editor.tmpFilenameU == NULL) { okBoxThreadSafe(0, "System message", "General I/O error during saving! Is the file in use?"); return false; } const UNICHAR *oldPathU = getDiskOpCurPath(); // in "save range mode", we must enter the sample directory if (saveRangeFlag) UNICHAR_CHDIR(getDiskOpSmpPath()); switch (editor.sampleSaveMode) { case SMP_SAVE_MODE_RAW: saveRawSample(editor.tmpFilenameU, saveRangeFlag); break; case SMP_SAVE_MODE_IFF: saveIFFSample(editor.tmpFilenameU, saveRangeFlag); break; default: case SMP_SAVE_MODE_WAV: saveWAVSample(editor.tmpFilenameU, saveRangeFlag); break; } // set back old working directory if we changed it if (saveRangeFlag) UNICHAR_CHDIR(oldPathU); return true; (void)ptr; } void saveSample(UNICHAR *filenameU, bool saveAsRange) { saveRangeFlag = saveAsRange; UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); mouseAnimOn(); thread = SDL_CreateThread(saveSampleThread, NULL, NULL); if (thread == NULL) { okBoxThreadSafe(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); }