ref: 4beea686fd27e6c6ba19ec52ea8d80c2a7cc56eb
dir: /src/pt2_sampleloader.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 <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <ctype.h> // tolower()/toupper() #ifdef _WIN32 #include <io.h> #else #include <unistd.h> #endif #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include "pt2_header.h" #include "pt2_textout.h" #include "pt2_mouse.h" #include "pt2_sampler.h" #include "pt2_audio.h" #include "pt2_sampleloader.h" #include "pt2_visuals.h" #include "pt2_helpers.h" #include "pt2_unicode.h" #include "pt2_filters.h" enum { WAV_FORMAT_PCM = 0x0001, WAV_FORMAT_IEEE_FLOAT = 0x0003 }; static bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling); static bool loadIFFSample(UNICHAR *fileName, char *entryName); static bool loadRAWSample(UNICHAR *fileName, char *entryName); static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling); static bool loadedFileWasAIFF; void extLoadWAVOrAIFFSampleCallback(bool downsample) { if (loadedFileWasAIFF) loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); else loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); } bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling) { bool wavSampleNameFound; uint8_t *audioDataU8; int16_t *audioDataS16, tempVol, smp16; uint16_t audioFormat, numChannels, bitsPerSample; int32_t *audioDataS32, smp32, smp32_l, smp32_r; uint32_t *audioDataU32, i, nameLen, chunkID, chunkSize; uint32_t sampleLength, sampleRate, filesize, loopFlags; uint32_t loopStart, loopEnd, dataPtr, dataLen, fmtPtr, endOfChunk, bytesRead; uint32_t fmtLen, inamPtr, inamLen, smplPtr, smplLen, xtraPtr, xtraLen; float *fAudioDataFloat, fSmp; double *dAudioDataDouble, dSmp; FILE *f; moduleSample_t *s; loadedFileWasAIFF = false; // zero out chunk pointers and lengths fmtPtr = 0; fmtLen = 0; dataPtr = 0; dataLen = 0; inamPtr = 0; inamLen = 0; xtraPtr = 0; xtraLen = 0; smplPtr = 0; smplLen = 0; wavSampleNameFound = false; s = &modEntry->samples[editor.currSample]; if (forceDownSampling == -1) { // these two *must* be fully wiped, for outputting reasons memset(editor.fileNameTmpU, 0, PATH_MAX); memset(editor.entryNameTmp, 0, PATH_MAX); UNICHAR_STRCPY(editor.fileNameTmpU, fileName); strcpy(editor.entryNameTmp, entryName); } f = UNICHAR_FOPEN(fileName, "rb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } fseek(f, 0, SEEK_END); filesize = ftell(f); if (filesize == 0) { fclose(f); displayErrorMsg("NOT A WAV !"); return false; } // look for wanted chunks and set up pointers + lengths fseek(f, 12, SEEK_SET); bytesRead = 0; while (!feof(f) && bytesRead < filesize-12) { fread(&chunkID, 4, 1, f); if (feof(f)) break; fread(&chunkSize, 4, 1, f); if (feof(f)) break; endOfChunk = (ftell(f) + chunkSize) + (chunkSize & 1); switch (chunkID) { case 0x20746D66: // "fmt " { fmtPtr = ftell(f); fmtLen = chunkSize; } break; case 0x61746164: // "data" { dataPtr = ftell(f); dataLen = chunkSize; } break; case 0x5453494C: // "LIST" { if (chunkSize >= 4) { fread(&chunkID, 4, 1, f); if (chunkID == 0x4F464E49) // "INFO" { bytesRead = 0; while (!feof(f) && (bytesRead < chunkSize)) { fread(&chunkID, 4, 1, f); fread(&chunkSize, 4, 1, f); switch (chunkID) { case 0x4D414E49: // "INAM" { inamPtr = ftell(f); inamLen = chunkSize; } break; default: break; } bytesRead += (chunkSize + (chunkSize & 1)); } } } } break; case 0x61727478: // "xtra" { xtraPtr = ftell(f); xtraLen = chunkSize; } break; case 0x6C706D73: // "smpl" { smplPtr = ftell(f); smplLen = chunkSize; } break; default: break; } bytesRead += chunkSize + (chunkSize & 1); fseek(f, endOfChunk, SEEK_SET); } // we need at least "fmt " and "data" - check if we found them sanely if ((fmtPtr == 0 || fmtLen < 16) || (dataPtr == 0 || dataLen == 0)) { fclose(f); displayErrorMsg("NOT A WAV !"); return false; } // ---- READ "fmt " CHUNK ---- fseek(f, fmtPtr, SEEK_SET); fread(&audioFormat, 2, 1, f); fread(&numChannels, 2, 1, f); fread(&sampleRate, 4, 1, f); fseek(f, 6, SEEK_CUR); fread(&bitsPerSample, 2, 1, f); sampleLength = dataLen; // --------------------------- if (sampleRate == 0 || sampleLength == 0 || sampleLength >= filesize*(bitsPerSample/8)) { fclose(f); displayErrorMsg("WAV CORRUPT !"); return false; } if (audioFormat != WAV_FORMAT_PCM && audioFormat != WAV_FORMAT_IEEE_FLOAT) { fclose(f); displayErrorMsg("WAV UNSUPPORTED !"); return false; } if ((numChannels == 0) || (numChannels > 2)) { fclose(f); displayErrorMsg("WAV UNSUPPORTED !"); return false; } if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample != 32 && bitsPerSample != 64) { fclose(f); displayErrorMsg("WAV UNSUPPORTED !"); return false; } if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32 && bitsPerSample != 64) { fclose(f); displayErrorMsg("WAV UNSUPPORTED !"); return false; } if (sampleRate > 22050) { if (forceDownSampling == -1) { showDownsampleAskDialog(); fclose(f); return true; } } else { forceDownSampling = false; } // ---- READ SAMPLE DATA ---- fseek(f, dataPtr, SEEK_SET); if (bitsPerSample == 8) // 8-BIT INTEGER SAMPLE { if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataU8 = (uint8_t *)malloc(sampleLength * sizeof (uint8_t)); if (audioDataU8 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataU8, 1, sampleLength, f) != sampleLength) { fclose(f); free(audioDataU8); displayErrorMsg("I/O ERROR !"); return false; } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp16 = (audioDataU8[(i * 2) + 0] - 128) + (audioDataU8[(i * 2) + 1] - 128); smp16 = 128 + (smp16 >> 1); audioDataU8[i] = (uint8_t)smp16; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataU8[i] = audioDataU8[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataU8[i] - 128; else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataU8); } else if (bitsPerSample == 16) // 16-BIT INTEGER SAMPLE { sampleLength /= 2; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t)); if (audioDataS16 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataS16, 2, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS16); displayErrorMsg("I/O ERROR !"); return false; } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32 = (audioDataS16[(i * 2) + 0] + audioDataS16[(i * 2) + 1]) >> 1; audioDataS16[i] = (int16_t)smp32; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS16[i] = audioDataS16[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize16bitSigned(audioDataS16, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8; else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS16); } else if (bitsPerSample == 24) // 24-BIT INTEGER SAMPLE { sampleLength /= 3; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t)); if (audioDataS32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data audioDataU8 = (uint8_t *)audioDataS32; for (i = 0; i < sampleLength; i++) { audioDataU8[0] = 0; fread(&audioDataU8[1], 3, 1, f); audioDataU8 += sizeof (int32_t); } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32_l = audioDataS32[(i * 2) + 0] >> 1; smp32_r = audioDataS32[(i * 2) + 1] >> 1; audioDataS32[i] = smp32_l + smp32_r; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS32[i] = audioDataS32[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize32bitSigned(audioDataS32, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24); else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS32); } else if (audioFormat == WAV_FORMAT_PCM && bitsPerSample == 32) // 32-BIT INTEGER SAMPLE { sampleLength /= 4; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t)); if (audioDataS32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataS32, 4, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS32); displayErrorMsg("I/O ERROR !"); return false; } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32_l = audioDataS32[(i * 2) + 0] >> 1; smp32_r = audioDataS32[(i * 2) + 1] >> 1; audioDataS32[i] = smp32_l + smp32_r; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS32[i] = audioDataS32[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize32bitSigned(audioDataS32, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24); else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS32); } else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 32) // 32-BIT FLOATING POINT SAMPLE { sampleLength /= 4; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataU32 = (uint32_t *)malloc(sampleLength * sizeof (uint32_t)); if (audioDataU32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataU32, 4, sampleLength, f) != sampleLength) { fclose(f); free(audioDataU32); displayErrorMsg("I/O ERROR !"); return false; } fAudioDataFloat = (float *)audioDataU32; // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { fSmp = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f; fAudioDataFloat[i] = fSmp; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) fAudioDataFloat[i] = fAudioDataFloat[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize8bitFloatSigned(fAudioDataFloat, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) { smp32 = (int32_t)fAudioDataFloat[i]; CLAMP8(smp32); modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32; } else { modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } } free(audioDataU32); } else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 64) // 64-BIT FLOATING POINT SAMPLE { sampleLength /= 8; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataU32 = (uint32_t *)malloc(sampleLength * (sizeof (uint32_t) * 2)); if (audioDataU32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataU32, 8, sampleLength, f) != sampleLength) { fclose(f); free(audioDataU32); displayErrorMsg("I/O ERROR !"); return false; } dAudioDataDouble = (double *)audioDataU32; // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { dSmp = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5; dAudioDataDouble[i] = dSmp; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) dAudioDataDouble[i] = dAudioDataDouble[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize8bitDoubleSigned(dAudioDataDouble, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) { smp32 = (int32_t)dAudioDataDouble[i]; CLAMP8(smp32); modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32; } else { modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } } free(audioDataU32); } // set sample length if (sampleLength & 1) { if (++sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; } s->length = sampleLength; s->fineTune = 0; s->volume = 64; s->loopStart = 0; s->loopLength = 2; // ---- READ "smpl" chunk ---- if (smplPtr != 0 && smplLen > 52) { fseek(f, smplPtr + 28, SEEK_SET); // seek to first wanted byte fread(&loopFlags, 4, 1, f); fseek(f, 12, SEEK_CUR); fread(&loopStart, 4, 1, f); fread(&loopEnd, 4, 1, f); loopEnd++; if (forceDownSampling) { // we already downsampled 2x, so we're half the original length loopStart /= 2; loopEnd /= 2; } loopStart &= 0xFFFFFFFE; loopEnd &= 0xFFFFFFFE; if (loopFlags) { if (loopStart+(loopEnd-loopStart) <= s->length) { s->loopStart = loopStart; s->loopLength = loopEnd - loopStart; if (s->loopLength < 2) { s->loopStart = 0; s->loopLength = 2; } } } } // --------------------------- // ---- READ "xtra" chunk ---- if (xtraPtr != 0 && xtraLen >= 8) { fseek(f, xtraPtr + 4, SEEK_SET); // seek to first wanted byte // volume (0..256) fseek(f, 2, SEEK_CUR); fread(&tempVol, 2, 1, f); if (tempVol > 256) tempVol = 256; s->volume = (int8_t)(tempVol / 4); } // --------------------------- // ---- READ "INAM" chunk ---- if (inamPtr != 0 && inamLen > 0) { fseek(f, inamPtr, SEEK_SET); // seek to first wanted byte for (i = 0; i < 21; i++) { if (i < inamLen) s->text[i] = (char)toupper(fgetc(f)); else s->text[i] = '\0'; } s->text[21] = '\0'; s->text[22] = '\0'; wavSampleNameFound = true; } // --------------------------- fclose(f); // copy over sample name if (!wavSampleNameFound) { nameLen = (uint32_t)strlen(entryName); for (i = 0; i < 21; i++) s->text[i] = (i < nameLen) ? toupper(entryName[i]) : '\0'; s->text[21] = '\0'; s->text[22] = '\0'; } // remove .wav from end of sample name (if present) nameLen = (uint32_t)strlen(s->text); if (nameLen >= 4 && !_strnicmp(&s->text[nameLen-4], ".WAV", 4)) memset(&s->text[nameLen-4], '\0', 4); editor.sampleZero = false; editor.samplePos = 0; fixSampleBeep(s); updateCurrSample(); updateWindowTitle(MOD_IS_MODIFIED); return true; } bool loadIFFSample(UNICHAR *fileName, char *entryName) { bool nameFound, is16Bit; char tmpCharBuf[23]; int8_t *sampleData; int16_t sample16, *ptr16; int32_t filesize; uint32_t i, sampleLength, sampleLoopStart, sampleLoopLength; uint32_t sampleVolume, blockName, blockSize; uint32_t vhdrPtr, vhdrLen, bodyPtr, bodyLen, namePtr, nameLen; FILE *f; moduleSample_t *s; s = &modEntry->samples[editor.currSample]; vhdrPtr = 0; vhdrLen = 0; bodyPtr = 0; bodyLen = 0; namePtr = 0; nameLen = 0; f = UNICHAR_FOPEN(fileName, "rb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } fseek(f, 0, SEEK_END); filesize = ftell(f); if (filesize == 0) { displayErrorMsg("IFF IS CORRUPT !"); return false; } fseek(f, 8, SEEK_SET); fread(tmpCharBuf, 1, 4, f); is16Bit = !strncmp(tmpCharBuf, "16SV", 4); sampleLength = 0; nameFound = false; sampleVolume = 65536; // max volume fseek(f, 12, SEEK_SET); while (!feof(f) && ftell(f) < filesize-12) { fread(&blockName, 4, 1, f); if (feof(f)) break; fread(&blockSize, 4, 1, f); if (feof(f)) break; blockName = SWAP32(blockName); blockSize = SWAP32(blockSize); switch (blockName) { case 0x56484452: // VHDR { vhdrPtr = ftell(f); vhdrLen = blockSize; } break; case 0x4E414D45: // NAME { namePtr = ftell(f); nameLen = blockSize; } break; case 0x424F4459: // BODY { bodyPtr = ftell(f); bodyLen = blockSize; } break; default: break; } fseek(f, blockSize + (blockSize & 1), SEEK_CUR); } if (vhdrPtr == 0 || vhdrLen < 20 || bodyPtr == 0) { fclose(f); displayErrorMsg("NOT A VALID IFF !"); return false; } // kludge for some really strange IFFs if (bodyLen == 0) bodyLen = filesize - bodyPtr; if ((bodyPtr + bodyLen) > (uint32_t)filesize) bodyLen = filesize - bodyPtr; fseek(f, vhdrPtr, SEEK_SET); fread(&sampleLoopStart, 4, 1, f); sampleLoopStart = SWAP32(sampleLoopStart); fread(&sampleLoopLength, 4, 1, f); sampleLoopLength = SWAP32(sampleLoopLength); fseek(f, 4 + 2 + 1, SEEK_CUR); if (fgetc(f) != 0) // sample type { fclose(f); displayErrorMsg("UNSUPPORTED IFF !"); return false; } fread(&sampleVolume, 4, 1, f); sampleVolume = SWAP32(sampleVolume); if (sampleVolume > 65536) sampleVolume = 65536; sampleVolume = (int32_t)((sampleVolume / 1024.0) + 0.5); if (sampleVolume > 64) sampleVolume = 64; sampleLength = bodyLen; if (is16Bit) { if (sampleLength > MAX_SAMPLE_LEN*2) sampleLength = MAX_SAMPLE_LEN*2; } else { if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; } if (sampleLength == 0) { fclose(f); displayErrorMsg("NOT A VALID IFF !"); return false; } sampleData = (int8_t *)malloc(sampleLength); if (sampleData == NULL) { fclose(f); statusOutOfMemory(); return false; } if (is16Bit) { sampleLength /= 2; sampleLoopStart /= 2; sampleLoopLength /= 2; } sampleLength &= 0xFFFFFFFE; sampleLoopStart &= 0xFFFFFFFE; sampleLoopLength &= 0xFFFFFFFE; if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; if (sampleLoopLength < 2) { sampleLoopStart = 0; sampleLoopLength = 2; } if (sampleLoopStart >= MAX_SAMPLE_LEN || sampleLoopLength > MAX_SAMPLE_LEN) { sampleLoopStart= 0; sampleLoopLength = 2; } if (sampleLoopStart+sampleLoopLength > sampleLength) { sampleLoopStart = 0; sampleLoopLength = 2; } if (sampleLoopStart > sampleLength-2) { sampleLoopStart = 0; sampleLoopLength = 2; } turnOffVoices(); fseek(f, bodyPtr, SEEK_SET); if (is16Bit) // FT2 specific 16SV format (little-endian samples) { fread(sampleData, 1, sampleLength * 2, f); ptr16 = (int16_t *)sampleData; for (i = 0; i < sampleLength; i++) { sample16 = ptr16[i]; modEntry->sampleData[s->offset+i] = sample16 >> 8; } } else { fread(sampleData, 1, sampleLength, f); memcpy(&modEntry->sampleData[s->offset], sampleData, sampleLength); } if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data memset(&modEntry->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength); free(sampleData); // set sample attributes s->volume = sampleVolume; s->fineTune = 0; s->length = sampleLength; s->loopStart = sampleLoopStart; s->loopLength = sampleLoopLength; // read name if (namePtr != 0 && nameLen > 0) { fseek(f, namePtr, SEEK_SET); memset(tmpCharBuf, 0, sizeof (tmpCharBuf)); if (nameLen > 21) { fread(tmpCharBuf, 1, 21, f); fseek(f, nameLen - 21, SEEK_CUR); } else { fread(tmpCharBuf, 1, nameLen, f); } nameFound = true; } fclose(f); // copy over sample name memset(s->text, '\0', sizeof (s->text)); if (nameFound) { nameLen = (uint32_t)strlen(tmpCharBuf); if (nameLen > 21) nameLen = 21; for (i = 0; i < nameLen; i++) s->text[i] = toupper(tmpCharBuf[i]); } else { nameLen = (uint32_t)strlen(entryName); if (nameLen > 21) nameLen = 21; for (i = 0; i < nameLen; i++) s->text[i] = toupper(entryName[i]); } // remove .iff from end of sample name (if present) nameLen = (uint32_t)strlen(s->text); if (nameLen >= 4 && !strncmp(&s->text[nameLen-4], ".IFF", 4)) memset(&s->text[nameLen-4], '\0', 4); editor.sampleZero = false; editor.samplePos = 0; fixSampleBeep(s); updateCurrSample(); updateWindowTitle(MOD_IS_MODIFIED); return false; } bool loadRAWSample(UNICHAR *fileName, char *entryName) { uint8_t i; uint32_t nameLen, fileSize; FILE *f; moduleSample_t *s; s = &modEntry->samples[editor.currSample]; f = UNICHAR_FOPEN(fileName, "rb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } fseek(f, 0, SEEK_END); fileSize = ftell(f); fseek(f, 0, SEEK_SET); fileSize &= 0xFFFFFFFE; if (fileSize > MAX_SAMPLE_LEN) fileSize = MAX_SAMPLE_LEN; turnOffVoices(); fread(&modEntry->sampleData[s->offset], 1, fileSize, f); fclose(f); if (fileSize < MAX_SAMPLE_LEN) memset(&modEntry->sampleData[s->offset + fileSize], 0, MAX_SAMPLE_LEN - fileSize); // set sample attributes s->volume = 64; s->fineTune = 0; s->length = fileSize; s->loopStart = 0; s->loopLength = 2; // copy over sample name nameLen = (uint32_t)strlen(entryName); for (i = 0; i < 21; i++) s->text[i] = (i < nameLen) ? toupper(entryName[i]) : '\0'; s->text[21] = '\0'; s->text[22] = '\0'; editor.sampleZero = false; editor.samplePos = 0; fixSampleBeep(s); updateCurrSample(); updateWindowTitle(MOD_IS_MODIFIED); return true; } static int32_t getAIFFRate(uint8_t *in) { int32_t exp; uint32_t lo, hi; double dOut; exp = (int32_t)(((in[0] & 0x7F) << 8) | in[1]); lo = (in[2] << 24) | (in[3] << 16) | (in[4] << 8) | in[5]; hi = (in[6] << 24) | (in[7] << 16) | (in[8] << 8) | in[9]; if (exp == 0 && lo == 0 && hi == 0) return 0; exp -= 16383; dOut = ldexp(lo, -31 + exp) + ldexp(hi, -63 + exp); return (int32_t)(dOut + 0.5); } bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling) { char compType[4]; int8_t *audioDataS8; uint8_t *audioDataU8, sampleRateBytes[10]; int16_t *audioDataS16, smp16; uint16_t bitDepth, numChannels; int32_t filesize, *audioDataS32, smp32, smp32_l, smp32_r; uint32_t nameLen, i, offset, sampleRate, sampleLength, blockName, blockSize; uint32_t commPtr, commLen, ssndPtr, ssndLen; FILE *f; moduleSample_t *s; loadedFileWasAIFF = true; if (forceDownSampling == -1) { // these two *must* be fully wiped, for outputting reasons memset(editor.fileNameTmpU, 0, PATH_MAX); memset(editor.entryNameTmp, 0, PATH_MAX); UNICHAR_STRCPY(editor.fileNameTmpU, fileName); strcpy(editor.entryNameTmp, entryName); } s = &modEntry->samples[editor.currSample]; commPtr = 0; commLen = 0; ssndPtr = 0; ssndLen = 0; f = UNICHAR_FOPEN(fileName, "rb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } fseek(f, 0, SEEK_END); filesize = ftell(f); if (filesize == 0) { displayErrorMsg("AIFF IS CORRUPT !"); return false; } fseek(f, 12, SEEK_SET); while (!feof(f) && ftell(f) < filesize-12) { fread(&blockName, 4, 1, f); if (feof(f)) break; fread(&blockSize, 4, 1, f); if (feof(f)) break; blockName = SWAP32(blockName); blockSize = SWAP32(blockSize); switch (blockName) { case 0x434F4D4D: // "COMM" { commPtr = ftell(f); commLen = blockSize; } break; case 0x53534E44: // "SSND" { ssndPtr = ftell(f); ssndLen = blockSize; } break; default: break; } fseek(f, blockSize + (blockSize & 1), SEEK_CUR); } if (commPtr == 0 || commLen < 18 || ssndPtr == 0) { fclose(f); displayErrorMsg("NOT A VALID AIFF!"); return false; } // kludge for some really strange AIFFs if (ssndLen == 0) ssndLen = filesize - ssndPtr; if (ssndPtr+ssndLen > (uint32_t)filesize) ssndLen = filesize - ssndPtr; fseek(f, commPtr, SEEK_SET); fread(&numChannels, 2, 1, f); numChannels = SWAP16(numChannels); fseek(f, 4, SEEK_CUR); fread(&bitDepth, 2, 1, f); bitDepth = SWAP16(bitDepth); fread(sampleRateBytes, 1, 10, f); fseek(f, 4 + 2 + 1, SEEK_CUR); if (numChannels != 1 && numChannels != 2) // sample type { fclose(f); displayErrorMsg("UNSUPPORTED AIFF!"); return false; } if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32) { fclose(f); displayErrorMsg("UNSUPPORTED AIFF!"); return false; } // read compression type (if present) if (commLen > 18) { fread(&compType, 1, 4, f); if (memcmp(compType, "NONE", 4)) { fclose(f); displayErrorMsg("UNSUPPORTED AIFF!"); return false; } } sampleRate = getAIFFRate(sampleRateBytes); // sample data chunk fseek(f, ssndPtr, SEEK_SET); fread(&offset, 4, 1, f); if (offset > 0) { fclose(f); displayErrorMsg("UNSUPPORTED AIFF!"); return false; } fseek(f, 4, SEEK_CUR); ssndLen -= 8; // don't include offset and blockSize datas sampleLength = ssndLen; if (sampleLength == 0) { fclose(f); displayErrorMsg("NOT A VALID AIFF!"); return false; } if (sampleRate > 22050) { if (forceDownSampling == -1) { showDownsampleAskDialog(); fclose(f); return true; } } else { forceDownSampling = false; } if (bitDepth == 8) // 8-BIT INTEGER SAMPLE { if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS8 = (int8_t *)malloc(sampleLength * sizeof (int8_t)); if (audioDataS8 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataS8, 1, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS8); displayErrorMsg("I/O ERROR !"); return false; } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1; audioDataS8[i] = (uint8_t)smp16; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS8[i] = audioDataS8[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS8[i]; else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS8); } else if (bitDepth == 16) // 16-BIT INTEGER SAMPLE { sampleLength /= 2; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t)); if (audioDataS16 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataS16, 2, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS16); displayErrorMsg("I/O ERROR !"); return false; } // fix endianness for (i = 0; i < sampleLength; i++) audioDataS16[i] = SWAP16(audioDataS16[i]); // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32 = (audioDataS16[(i * 2) + 0] + audioDataS16[(i * 2) + 1]) >> 1; audioDataS16[i] = (int16_t)(smp32); } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS16[i] = audioDataS16[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize16bitSigned(audioDataS16, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8; else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS16); } else if (bitDepth == 24) // 24-BIT INTEGER SAMPLE { sampleLength /= 3; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS32 = (int32_t *)malloc((sampleLength * 2) * sizeof (int32_t)); if (audioDataS32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(&audioDataS32[sampleLength / 4], 3, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS32); displayErrorMsg("I/O ERROR !"); return false; } // convert to 32-bit audioDataU8 = (uint8_t *)audioDataS32 + sampleLength; for (i = 0; i < sampleLength; i++) { audioDataS32[i] = (audioDataU8[0] << 24) | (audioDataU8[1] << 16) | (audioDataU8[2] << 8); audioDataU8 += 3; } // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32_l = audioDataS32[(i * 2) + 0] >> 1; smp32_r = audioDataS32[(i * 2) + 1] >> 1; audioDataS32[i] = smp32_l + smp32_r; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS32[i] = audioDataS32[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize32bitSigned(audioDataS32, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24); else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS32); } else if (bitDepth == 32) // 32-BIT INTEGER SAMPLE { sampleLength /= 4; if (sampleLength > MAX_SAMPLE_LEN*4) sampleLength = MAX_SAMPLE_LEN*4; audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t)); if (audioDataS32 == NULL) { fclose(f); statusOutOfMemory(); return false; } // read sample data if (fread(audioDataS32, 4, sampleLength, f) != sampleLength) { fclose(f); free(audioDataS32); displayErrorMsg("I/O ERROR !"); return false; } // fix endianness for (i = 0; i < sampleLength; i++) audioDataS32[i] = SWAP32(audioDataS32[i]); // convert from stereo to mono (if needed) if (numChannels == 2) { sampleLength /= 2; for (i = 0; i < sampleLength-1; i++) // add right channel to left channel { smp32_l = audioDataS32[(i * 2) + 0] >> 1; smp32_r = audioDataS32[(i * 2) + 1] >> 1; audioDataS32[i] = smp32_l + smp32_r; } } // 2x downsampling - remove every other sample (if needed) if (forceDownSampling) { if (ptConfig.sampleLowpass) lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR); sampleLength /= 2; for (i = 1; i < sampleLength; i++) audioDataS32[i] = audioDataS32[i * 2]; } if (sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; normalize32bitSigned(audioDataS32, sampleLength); turnOffVoices(); for (i = 0; i < MAX_SAMPLE_LEN; i++) { if (i <= (sampleLength & 0xFFFFFFFE)) modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24); else modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample } free(audioDataS32); } // set sample length if (sampleLength & 1) { if (++sampleLength > MAX_SAMPLE_LEN) sampleLength = MAX_SAMPLE_LEN; } s->length = sampleLength; s->fineTune = 0; s->volume = 64; s->loopStart = 0; s->loopLength = 2; fclose(f); // copy over sample name nameLen = (uint32_t)strlen(entryName); for (i = 0; i < 21; i++) s->text[i] = (i < nameLen) ? toupper(entryName[i]) : '\0'; s->text[21] = '\0'; s->text[22] = '\0'; // remove .aiff from end of sample name (if present) nameLen = (uint32_t)strlen(s->text); if (nameLen >= 5 && !_strnicmp(&s->text[nameLen-5], ".AIFF", 5)) memset(&s->text[nameLen-5], '\0', 5); editor.sampleZero = false; editor.samplePos = 0; fixSampleBeep(s); updateCurrSample(); updateWindowTitle(MOD_IS_MODIFIED); return true; } bool loadSample(UNICHAR *fileName, char *entryName) { uint32_t fileSize, ID; FILE *f; f = UNICHAR_FOPEN(fileName, "rb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } fseek(f, 0, SEEK_END); fileSize = ftell(f); fseek(f, 0, SEEK_SET); // first, check heades before we eventually load as RAW if (fileSize > 16) { fread(&ID, 4, 1, f); // check if it's actually a WAV sample if (ID == 0x46464952) // "RIFF" { fseek(f, 4, SEEK_CUR); fread(&ID, 4, 1, f); if (ID == 0x45564157) // "WAVE" { fread(&ID, 4, 1, f); if (ID == 0x20746D66) // "fmt " { fclose(f); return loadWAVSample(fileName, entryName, -1); } } } else if (ID == 0x4D524F46) // "FORM" { fseek(f, 4, SEEK_CUR); fread(&ID, 4, 1, f); // check if it's an Amiga IFF sample if (ID == 0x58565338 || ID == 0x56533631) // "8SVX" (normal) and "16SV" (FT2 sample) { fclose(f); return loadIFFSample(fileName, entryName); } // check if it's an AIFF sample else if (ID == 0x46464941) // "AIFF" { fclose(f); return loadAIFFSample(fileName, entryName, -1); } } } // nope, continue loading as RAW fclose(f); return loadRAWSample(fileName, entryName); } static void removeWavIffExt(char *text) // for sample saver { uint32_t fileExtPos; uint32_t filenameLength; filenameLength = (uint32_t)strlen(text); if (filenameLength < 5) return; // remove .wav/.iff/from end of sample name (if present) fileExtPos = filenameLength - 4; if (fileExtPos > 0 && (!strncmp(&text[fileExtPos], ".wav", 4) || !strncmp(&text[fileExtPos], ".iff", 4))) text[fileExtPos] = '\0'; } bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename) { char fileName[128], tmpBuffer[64]; uint8_t smp; uint16_t j; int32_t i; uint32_t sampleSize, iffSize, iffSampleSize; uint32_t loopStart, loopLen, tmp32; FILE *f; struct stat statBuffer; moduleSample_t *s; wavHeader_t wavHeader; samplerChunk_t samplerChunk; if (modEntry->samples[editor.currSample].length == 0) { displayErrorMsg("SAMPLE IS EMPTY"); return false; } memset(fileName, 0, sizeof (fileName)); if (*modEntry->samples[editor.currSample].text == '\0') { strcpy(fileName, "untitled"); } else { for (i = 0; i < 22; i++) { tmpBuffer[i] = (char)tolower(modEntry->samples[editor.currSample].text[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } strcpy(fileName, tmpBuffer); } removeWavIffExt(fileName); switch (editor.diskop.smpSaveType) { case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break; case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break; case DISKOP_SMP_RAW: break; default: return false; // make compiler happy } if (giveNewFreeFilename) { if (stat(fileName, &statBuffer) == 0) { for (j = 1; j <= 9999; j++) // This number should satisfy all! ;) { memset(fileName, 0, sizeof (fileName)); if (*modEntry->samples[editor.currSample].text == '\0') { sprintf(fileName, "untitled-%d", j); } else { for (i = 0; i < 22; i++) { tmpBuffer[i] = (char)tolower(modEntry->samples[editor.currSample].text[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } removeWavIffExt(tmpBuffer); sprintf(fileName, "%s-%d", tmpBuffer, j); } switch (editor.diskop.smpSaveType) { default: case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break; case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break; case DISKOP_SMP_RAW: break; } if (stat(fileName, &statBuffer) != 0) break; } } } if (checkIfFileExist && stat(fileName, &statBuffer) == 0) { editor.ui.askScreenShown = true; editor.ui.askScreenType = ASK_SAVESMP_OVERWRITE; pointerSetMode(POINTER_MODE_MSG1, NO_CARRY); setStatusMessage("OVERWRITE FILE ?", NO_CARRY); renderAskDialog(); return -1; } if (editor.ui.askScreenShown) { editor.ui.answerNo = false; editor.ui.answerYes = false; editor.ui.askScreenShown = false; } f = fopen(fileName, "wb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } sampleSize = modEntry->samples[editor.currSample].length; switch (editor.diskop.smpSaveType) { default: case DISKOP_SMP_WAV: { s = &modEntry->samples[editor.currSample]; wavHeader.format = 0x45564157; // "WAVE" wavHeader.chunkID = 0x46464952; // "RIFF" wavHeader.subchunk1ID = 0x20746D66; // "fmt " wavHeader.subchunk2ID = 0x61746164; // "data" wavHeader.subchunk1Size = 16; wavHeader.subchunk2Size = sampleSize; wavHeader.chunkSize = wavHeader.subchunk2Size + 36; wavHeader.audioFormat = 1; wavHeader.numChannels = 1; wavHeader.bitsPerSample = 8; wavHeader.sampleRate = 16574; wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * wavHeader.bitsPerSample / 8; wavHeader.blockAlign = wavHeader.numChannels * wavHeader.bitsPerSample / 8; if (s->loopLength > 2) { wavHeader.chunkSize += sizeof (samplerChunk_t); memset(&samplerChunk, 0, sizeof (samplerChunk_t)); samplerChunk.chunkID = 0x6C706D73; // "smpl" samplerChunk.chunkSize = 60; samplerChunk.dwSamplePeriod = 1000000000 / 16574; samplerChunk.dwMIDIUnityNote = 60; // 60 = C-4 samplerChunk.cSampleLoops = 1; samplerChunk.loop.dwStart = (uint32_t)s->loopStart; samplerChunk.loop.dwEnd = (uint32_t)((s->loopStart + s->loopLength) - 1); } fwrite(&wavHeader, sizeof (wavHeader_t), 1, f); for (i = 0; i < (int32_t)sampleSize; i++) { smp = modEntry->sampleData[modEntry->samples[editor.currSample].offset + i] + 128; fputc(smp, f); } if (sampleSize & 1) fputc(0, f); // pad align byte if (s->loopLength > 2) fwrite(&samplerChunk, sizeof (samplerChunk), 1, f); } break; case DISKOP_SMP_IFF: { // dwords are big-endian in IFF loopStart = modEntry->samples[editor.currSample].loopStart & 0xFFFFFFFE; loopLen = modEntry->samples[editor.currSample].loopLength & 0xFFFFFFFE; loopStart = SWAP32(loopStart); loopLen = SWAP32(loopLen); iffSize = SWAP32(sampleSize + 100); iffSampleSize = SWAP32(sampleSize); fputc(0x46, f);fputc(0x4F, f);fputc(0x52, f);fputc(0x4D, f); // "FORM" fwrite(&iffSize, 4, 1, f); fputc(0x38, f);fputc(0x53, f);fputc(0x56, f);fputc(0x58, f); // "8SVX" fputc(0x56, f);fputc(0x48, f);fputc(0x44, f);fputc(0x52, f); // "VHDR" fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x14, f); // 0x00000014 if (modEntry->samples[editor.currSample].loopLength > 2) { fwrite(&loopStart, 4, 1, f); fwrite(&loopLen, 4, 1, f); } else { fwrite(&iffSampleSize, 4, 1, f); fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x00, f); // 0x00000000 } fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x00, f); // 0x00000000 fputc(0x41, f);fputc(0x56, f); // 16726 (rate) fputc(0x01, f);fputc(0x00, f); // numSamples and compression tmp32 = modEntry->samples[editor.currSample].volume * 1024; tmp32 = SWAP32(tmp32); fwrite(&tmp32, 4, 1, f); fputc(0x4E, f);fputc(0x41, f);fputc(0x4D, f);fputc(0x45, f); // "NAME" fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x16, f); // 0x00000016 for (i = 0; i < 22; i++) fputc(tolower(modEntry->samples[editor.currSample].text[i]), f); fputc(0x41, f);fputc(0x4E, f);fputc(0x4E, f);fputc(0x4F, f); // "ANNO" fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x15, f); // 0x00000012 fprintf(f, "ProTracker 2 clone"); fputc(0x00, f); // even padding fputc(0x42, f);fputc(0x4F, f);fputc(0x44, f);fputc(0x59, f); // "BODY" fwrite(&iffSampleSize, 4, 1, f); fwrite(modEntry->sampleData + modEntry->samples[editor.currSample].offset, 1, sampleSize, f); // shouldn't happen, but in just case: safety even padding if (sampleSize & 1) fputc(0x00, f); } break; case DISKOP_SMP_RAW: fwrite(modEntry->sampleData + modEntry->samples[editor.currSample].offset, 1, sampleSize, f); break; } fclose(f); editor.diskop.cached = false; if (editor.ui.diskOpScreenShown) editor.ui.updateDiskOpFileList = true; displayMsg("SAMPLE SAVED !"); setMsgPointer(); return true; }