#include <stdio.h>#include <string.h>#include <stdint.h>#include <stdbool.h>#include <ctype.h> // tolower()#include <sys/stat.h>#include "pt2_structs.h"#include "pt2_textout.h"#include "pt2_mouse.h"#include "pt2_visuals.h"#include "pt2_helpers.h"#include "pt2_diskop.h"#include "pt2_askbox.h" #define PLAYBACK_FREQ 16574 /* C-3, period 214 */ static void removeSampleFileExt(char *text) // for sample saver{ if (text == NULL || text[0] == '\0') return; uint32_t filenameLength = (uint32_t)strlen(text); if (filenameLength < 5) return; // remove .wav/.iff/from end of sample name (if present) uint32_t fileExtPos = filenameLength - 4; if (fileExtPos > 0 && (!strncmp(&text[fileExtPos], ".wav", 4) || !strncmp(&text[fileExtPos], ".iff", 4))) text[fileExtPos] = '\0';} 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} bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename){ char fileName[128], tmpBuffer[64]; struct stat statBuffer; wavHeader_t wavHeader; samplerChunk_t samplerChunk; mptExtraChunk_t mptExtraChunk; memset(tmpBuffer, 0, sizeof (tmpBuffer)); memset(fileName, 0, sizeof (fileName)); const moduleSample_t *s = &song->samples[editor.currSample]; if (s->length == 0) { statusSampleIsEmpty(); return false; } // get sample filename if (s->text[0] == '\0') { strcpy(fileName, "untitled"); } else { for (int32_t i = 0; i < 22; i++) { tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } strcpy(fileName, tmpBuffer); } removeSampleFileExt(fileName); addSampleFileExt(fileName); // if the user picked "no" to overwriting the file, generate a new filename if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0) { for (int32_t j = 1; j <= 999; j++) { if (s->text[0] == '\0') { sprintf(fileName, "untitled-%d", j); } else { for (int32_t i = 0; i < 22; i++) { tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } removeSampleFileExt(tmpBuffer); sprintf(fileName, "%s-%d", tmpBuffer, j); } addSampleFileExt(fileName); if (stat(fileName, &statBuffer) != 0) break; // this filename can be used } } if (checkIfFileExist && stat(fileName, &statBuffer) == 0) { if (!askBox(ASKBOX_YES_NO, "OVERWRITE FILE ?")) return false; } FILE *f = fopen(fileName, "wb"); if (f == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } const int8_t *sampleData = &song->sampleData[s->offset]; const uint32_t sampleLength = s->length; const uint32_t loopStart = s->loopStart & ~1; const uint32_t loopLength = s->loopLength & ~1; switch (diskop.smpSaveType) { default: case DISKOP_SMP_WAV: { wavHeader.format = 0x45564157; // "WAVE" wavHeader.chunkID = 0x46464952; // "RIFF" wavHeader.subchunk1ID = 0x20746D66; // "fmt " wavHeader.subchunk2ID = 0x61746164; // "data" wavHeader.subchunk1Size = 16; wavHeader.subchunk2Size = sampleLength; wavHeader.chunkSize = 36 + wavHeader.subchunk2Size; wavHeader.audioFormat = 1; wavHeader.numChannels = 1; wavHeader.bitsPerSample = 8; wavHeader.sampleRate = PLAYBACK_FREQ; wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * wavHeader.bitsPerSample / 8; wavHeader.blockAlign = wavHeader.numChannels * wavHeader.bitsPerSample / 8; // set "sampler" chunk if loop is enabled if (loopStart+loopLength > 2) // loop enabled? { wavHeader.chunkSize += sizeof (samplerChunk_t); memset(&samplerChunk, 0, sizeof (samplerChunk_t)); samplerChunk.chunkID = 0x6C706D73; // "smpl" samplerChunk.chunkSize = 60; samplerChunk.dwSamplePeriod = 1000000000 / PLAYBACK_FREQ; samplerChunk.dwMIDIUnityNote = 60; // 60 = MIDI middle-C samplerChunk.cSampleLoops = 1; samplerChunk.loop.dwStart = loopStart; samplerChunk.loop.dwEnd = (loopStart + loopLength) - 1; } // set ModPlug Tracker chunk (used for sample volume only in this case) wavHeader.chunkSize += sizeof (mptExtraChunk); memset(&mptExtraChunk, 0, sizeof (mptExtraChunk)); mptExtraChunk.chunkID = 0x61727478; // "xtra" mptExtraChunk.chunkSize = sizeof (mptExtraChunk) - 4 - 4; mptExtraChunk.defaultPan = 128; // 0..255 mptExtraChunk.defaultVolume = s->volume * 4; // 0..256 mptExtraChunk.globalVolume = 64; // 0..64 fwrite(&wavHeader, sizeof (wavHeader_t), 1, f); for (uint32_t i = 0; i < sampleLength; i++) fputc((uint8_t)(sampleData[i] + 128), f); if (sampleLength & 1) fputc(0, f); // pad align byte if (loopStart+loopLength > 2) // loop enabled? fwrite(&samplerChunk, sizeof (samplerChunk), 1, f); fwrite(&mptExtraChunk, sizeof (mptExtraChunk), 1, f); } break; case DISKOP_SMP_IFF: { // "FORM" chunk iffWriteChunkHeader(f, "FORM", 0); // "FORM" chunk size is overwritten later iffWriteUint32(f, 0x38535658); // "8SVX" // "VHDR" chunk iffWriteChunkHeader(f, "VHDR", 20); if (loopStart+loopLength > 2) // loop enabled? { iffWriteUint32(f, loopStart); // oneShotHiSamples (loop start when loop is enabled) iffWriteUint32(f, loopLength); // repeatHiSamples } else { iffWriteUint32(f, sampleLength); // oneShotHiSamples (length of sample when no loop) iffWriteUint32(f, 0); // repeatHiSamples } iffWriteUint32(f, 0); // samplesPerHiCycle iffWriteUint16(f, PLAYBACK_FREQ); // samplesPerSec iffWriteUint8(f, 1); // ctOctave (number of samples) iffWriteUint8(f, 0); // sCompression iffWriteUint32(f, s->volume * 1024); // volume (max: 65536/0x10000) // "NAME" chunk uint32_t chunkLen = (uint32_t)strlen(s->text); if (chunkLen > 0) { iffWriteChunkHeader(f, "NAME", chunkLen); iffWriteChunkData(f, s->text, chunkLen); } // "ANNO" chunk (we put the program name here) const char annoStr[] = "ProTracker 2 clone"; chunkLen = sizeof (annoStr) - 1; iffWriteChunkHeader(f, "ANNO", chunkLen); iffWriteChunkData(f, annoStr, chunkLen); // "BODY" chunk chunkLen = sampleLength; iffWriteChunkHeader(f, "BODY", chunkLen); iffWriteChunkData(f, sampleData, chunkLen); // go back and fill in "FORM" chunk size chunkLen = ftell(f) - 8; fseek(f, 4, SEEK_SET); iffWriteUint32(f, chunkLen); } break; case DISKOP_SMP_RAW: fwrite(sampleData, 1, sampleLength, f); break; } fclose(f); displayMsg("SAMPLE SAVED !"); setMsgPointer(); diskop.cached = false; if (ui.diskOpScreenShown) ui.updateDiskOpFileList = true; return true;}
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <ctype.h> // tolower()
#include <sys/stat.h>
#include "pt2_structs.h"
#include "pt2_textout.h"
#include "pt2_mouse.h"
#include "pt2_visuals.h"
#include "pt2_helpers.h"
#include "pt2_diskop.h"
#include "pt2_askbox.h"
#define PLAYBACK_FREQ 16574 /* C-3, period 214 */
static void removeSampleFileExt(char *text) // for sample saver
{
if (text == NULL || text[0] == '\0')
return;
uint32_t filenameLength = (uint32_t)strlen(text);
if (filenameLength < 5)
return;
// remove .wav/.iff/from end of sample name (if present)
uint32_t fileExtPos = filenameLength - 4;
if (fileExtPos > 0 && (!strncmp(&text[fileExtPos], ".wav", 4) || !strncmp(&text[fileExtPos], ".iff", 4)))
text[fileExtPos] = '\0';
}
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
}
bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename)
{
char fileName[128], tmpBuffer[64];
struct stat statBuffer;
wavHeader_t wavHeader;
samplerChunk_t samplerChunk;
mptExtraChunk_t mptExtraChunk;
memset(tmpBuffer, 0, sizeof (tmpBuffer));
memset(fileName, 0, sizeof (fileName));
const moduleSample_t *s = &song->samples[editor.currSample];
if (s->length == 0)
{
statusSampleIsEmpty();
return false;
}
// get sample filename
if (s->text[0] == '\0')
{
strcpy(fileName, "untitled");
}
else
{
for (int32_t i = 0; i < 22; i++)
{
tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]);
if (tmpBuffer[i] == '\0') break;
sanitizeFilenameChar(&tmpBuffer[i]);
}
strcpy(fileName, tmpBuffer);
}
removeSampleFileExt(fileName);
addSampleFileExt(fileName);
// if the user picked "no" to overwriting the file, generate a new filename
if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0)
{
for (int32_t j = 1; j <= 999; j++)
{
if (s->text[0] == '\0')
{
sprintf(fileName, "untitled-%d", j);
}
else
{
for (int32_t i = 0; i < 22; i++)
{
tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]);
if (tmpBuffer[i] == '\0') break;
sanitizeFilenameChar(&tmpBuffer[i]);
}
removeSampleFileExt(tmpBuffer);
sprintf(fileName, "%s-%d", tmpBuffer, j);
}
addSampleFileExt(fileName);
if (stat(fileName, &statBuffer) != 0)
break; // this filename can be used
}
}
if (checkIfFileExist && stat(fileName, &statBuffer) == 0)
{
if (!askBox(ASKBOX_YES_NO, "OVERWRITE FILE ?"))
return false;
}
FILE *f = fopen(fileName, "wb");
if (f == NULL)
{
displayErrorMsg("FILE I/O ERROR !");
return false;
}
const int8_t *sampleData = &song->sampleData[s->offset];
const uint32_t sampleLength = s->length;
const uint32_t loopStart = s->loopStart & ~1;
const uint32_t loopLength = s->loopLength & ~1;
switch (diskop.smpSaveType)
{
default:
case DISKOP_SMP_WAV:
{
wavHeader.format = 0x45564157; // "WAVE"
wavHeader.chunkID = 0x46464952; // "RIFF"
wavHeader.subchunk1ID = 0x20746D66; // "fmt "
wavHeader.subchunk2ID = 0x61746164; // "data"
wavHeader.subchunk1Size = 16;
wavHeader.subchunk2Size = sampleLength;
wavHeader.chunkSize = 36 + wavHeader.subchunk2Size;
wavHeader.audioFormat = 1;
wavHeader.numChannels = 1;
wavHeader.bitsPerSample = 8;
wavHeader.sampleRate = PLAYBACK_FREQ;
wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * wavHeader.bitsPerSample / 8;
wavHeader.blockAlign = wavHeader.numChannels * wavHeader.bitsPerSample / 8;
// set "sampler" chunk if loop is enabled
if (loopStart+loopLength > 2) // loop enabled?
{
wavHeader.chunkSize += sizeof (samplerChunk_t);
memset(&samplerChunk, 0, sizeof (samplerChunk_t));
samplerChunk.chunkID = 0x6C706D73; // "smpl"
samplerChunk.chunkSize = 60;
samplerChunk.dwSamplePeriod = 1000000000 / PLAYBACK_FREQ;
samplerChunk.dwMIDIUnityNote = 60; // 60 = MIDI middle-C
samplerChunk.cSampleLoops = 1;
samplerChunk.loop.dwStart = loopStart;
samplerChunk.loop.dwEnd = (loopStart + loopLength) - 1;
}
// set ModPlug Tracker chunk (used for sample volume only in this case)
wavHeader.chunkSize += sizeof (mptExtraChunk);
memset(&mptExtraChunk, 0, sizeof (mptExtraChunk));
mptExtraChunk.chunkID = 0x61727478; // "xtra"
mptExtraChunk.chunkSize = sizeof (mptExtraChunk) - 4 - 4;
mptExtraChunk.defaultPan = 128; // 0..255
mptExtraChunk.defaultVolume = s->volume * 4; // 0..256
mptExtraChunk.globalVolume = 64; // 0..64
fwrite(&wavHeader, sizeof (wavHeader_t), 1, f);
for (uint32_t i = 0; i < sampleLength; i++)
fputc((uint8_t)(sampleData[i] + 128), f);
if (sampleLength & 1)
fputc(0, f); // pad align byte
if (loopStart+loopLength > 2) // loop enabled?
fwrite(&samplerChunk, sizeof (samplerChunk), 1, f);
fwrite(&mptExtraChunk, sizeof (mptExtraChunk), 1, f);
}
break;
case DISKOP_SMP_IFF:
{
// "FORM" chunk
iffWriteChunkHeader(f, "FORM", 0); // "FORM" chunk size is overwritten later
iffWriteUint32(f, 0x38535658); // "8SVX"
// "VHDR" chunk
iffWriteChunkHeader(f, "VHDR", 20);
if (loopStart+loopLength > 2) // loop enabled?
{
iffWriteUint32(f, loopStart); // oneShotHiSamples (loop start when loop is enabled)
iffWriteUint32(f, loopLength); // repeatHiSamples
}
else
{
iffWriteUint32(f, sampleLength); // oneShotHiSamples (length of sample when no loop)
iffWriteUint32(f, 0); // repeatHiSamples
}
iffWriteUint32(f, 0); // samplesPerHiCycle
iffWriteUint16(f, PLAYBACK_FREQ); // samplesPerSec
iffWriteUint8(f, 1); // ctOctave (number of samples)
iffWriteUint8(f, 0); // sCompression
iffWriteUint32(f, s->volume * 1024); // volume (max: 65536/0x10000)
// "NAME" chunk
uint32_t chunkLen = (uint32_t)strlen(s->text);
if (chunkLen > 0)
{
iffWriteChunkHeader(f, "NAME", chunkLen);
iffWriteChunkData(f, s->text, chunkLen);
}
// "ANNO" chunk (we put the program name here)
const char annoStr[] = "ProTracker 2 clone";
chunkLen = sizeof (annoStr) - 1;
iffWriteChunkHeader(f, "ANNO", chunkLen);
iffWriteChunkData(f, annoStr, chunkLen);
// "BODY" chunk
chunkLen = sampleLength;
iffWriteChunkHeader(f, "BODY", chunkLen);
iffWriteChunkData(f, sampleData, chunkLen);
// go back and fill in "FORM" chunk size
chunkLen = ftell(f) - 8;
fseek(f, 4, SEEK_SET);
iffWriteUint32(f, chunkLen);
}
break;
case DISKOP_SMP_RAW:
fwrite(sampleData, 1, sampleLength, f);
break;
}
fclose(f);
displayMsg("SAMPLE SAVED !");
setMsgPointer();
diskop.cached = false;
if (ui.diskOpScreenShown)
ui.updateDiskOpFileList = true;
return true;
}