ref: 22c28f1c0de3a76cac067309abfd77f8d508c2bb
dir: /src/ft2_module_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 <stdbool.h>
#include "ft2_header.h"
#include "ft2_audio.h"
#include "ft2_gui.h"
#include "ft2_mouse.h"
#include "ft2_sample_ed.h"
#include "ft2_module_loader.h"
#include "ft2_tables.h"
#include "ft2_structs.h"
/* These savers are directly ported, so they should act identical to FT2
** except for some very minor changes.
*/
static SDL_Thread *thread;
static uint8_t packedPattData[65536];
static uint16_t packPatt(uint8_t *writePtr, uint8_t *pattPtr, uint16_t numRows);
static const char modSig[32][5] =
{
"1CHN", "2CHN", "3CHN", "4CHN", "5CHN", "6CHN", "7CHN", "8CHN",
"9CHN", "10CH", "11CH", "12CH", "13CH", "14CH", "15CH", "16CH",
"17CH", "18CH", "19CH", "20CH", "21CH", "22CH", "23CH", "24CH",
"25CH", "26CH", "27CH", "28CH", "29CH", "30CH", "31CH", "32CH"
};
bool saveXM(UNICHAR *filenameU)
{
int16_t ap, ai, i, j, k, a;
size_t result;
songHeaderTyp h;
patternHeaderTyp ph;
instrTyp *ins;
instrHeaderTyp ih;
sampleTyp *s;
sampleHeaderTyp *dst;
FILE *f;
f = UNICHAR_FOPEN(filenameU, "wb");
if (f == NULL)
{
okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?");
return false;
}
memcpy(h.sig, "Extended Module: ", 17);
memset(h.name, ' ', 20);
h.name[20] = 0x1A;
memcpy(h.name, song.name, strlen(song.name));
memcpy(h.progName, PROG_NAME_STR, 20);
h.ver = 0x0104;
h.headerSize = 20 + 256;
h.len = song.len;
h.repS = song.repS;
h.antChn = (uint16_t)song.antChn;
h.defTempo = song.tempo;
h.defSpeed = song.speed;
// count number of patterns
ap = MAX_PATTERNS;
do
{
if (patternEmpty(ap - 1))
ap--;
else
break;
}
while (ap > 0);
h.antPtn = ap;
// count number of instruments
ai = 128;
while (ai > 0 && getUsedSamples(ai) == 0 && song.instrName[ai][0] == '\0')
ai--;
h.antInstrs = ai;
h.flags = audio.linearFreqTable;
memcpy(h.songTab, song.songTab, sizeof (song.songTab));
if (fwrite(&h, sizeof (h), 1, f) != 1)
{
fclose(f);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
return false;
}
for (i = 0; i < ap; i++)
{
if (patternEmpty(i))
{
if (patt[i] != NULL)
{
free(patt[i]);
patt[i] = NULL;
}
pattLens[i] = 64;
}
ph.patternHeaderSize = sizeof (patternHeaderTyp);
ph.pattLen = pattLens[i];
ph.typ = 0;
if (patt[i] == NULL)
{
ph.dataLen = 0;
if (fwrite(&ph, ph.patternHeaderSize, 1, f) != 1)
{
fclose(f);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
return false;
}
}
else
{
ph.dataLen = packPatt(packedPattData, (uint8_t *)patt[i], pattLens[i]);
result = fwrite(&ph, ph.patternHeaderSize, 1, f);
result += fwrite(packedPattData, ph.dataLen, 1, f);
if (result != 2) // write was not OK
{
fclose(f);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
return false;
}
}
}
memset(&ih, 0, sizeof (ih)); // important, clears reserved stuff
for (i = 1; i <= ai; i++)
{
if (instr[i] == NULL)
j = 0;
else
j = i;
a = getUsedSamples(i);
memset(ih.name, 0, 22);
memcpy(ih.name, song.instrName[i], strlen(song.instrName[i]));
ih.typ = 0;
ih.antSamp = a;
ih.sampleSize = sizeof (sampleHeaderTyp);
if (a > 0)
{
ins = instr[j];
memcpy(ih.ta, ins->ta, 96);
memcpy(ih.envVP, ins->envVP, 12*2*sizeof(int16_t));
memcpy(ih.envPP, ins->envPP, 12*2*sizeof(int16_t));
ih.envVPAnt = ins->envVPAnt;
ih.envPPAnt = ins->envPPAnt;
ih.envVSust = ins->envVSust;
ih.envVRepS = ins->envVRepS;
ih.envVRepE = ins->envVRepE;
ih.envPSust = ins->envPSust;
ih.envPRepS = ins->envPRepS;
ih.envPRepE = ins->envPRepE;
ih.envVTyp = ins->envVTyp;
ih.envPTyp = ins->envPTyp;
ih.vibTyp = ins->vibTyp;
ih.vibSweep = ins->vibSweep;
ih.vibDepth = ins->vibDepth;
ih.vibRate = ins->vibRate;
ih.fadeOut = ins->fadeOut;
ih.midiOn = ins->midiOn ? 1 : 0;
ih.midiChannel = ins->midiChannel;
ih.midiProgram = ins->midiProgram;
ih.midiBend = ins->midiBend;
ih.mute = ins->mute ? 1 : 0;
ih.instrSize = INSTR_HEADER_SIZE;
for (k = 0; k < a; k++)
{
s = &instr[j]->samp[k];
dst = &ih.samp[k];
dst->len = s->len;
dst->repS = s->repS;
dst->repL = s->repL;
dst->vol = s->vol;
dst->fine = s->fine;
dst->typ = s->typ;
dst->pan = s->pan;
dst->relTon = s->relTon;
uint8_t nameLen = (uint8_t)strlen(s->name);
dst->nameLen = nameLen;
memset(dst->name, ' ', 22);
memcpy(dst->name, s->name, nameLen);
if (s->pek == NULL)
dst->len = 0;
}
}
else
{
ih.instrSize = 22 + 11;
}
if (fwrite(&ih, ih.instrSize + (a * sizeof (sampleHeaderTyp)), 1, f) != 1)
{
fclose(f);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
return false;
}
for (k = 1; k <= a; k++)
{
s = &instr[j]->samp[k-1];
if (s->pek != NULL)
{
restoreSample(s);
samp2Delta(s->pek, s->len, s->typ);
result = fwrite(s->pek, 1, s->len, f);
delta2Samp(s->pek, s->len, s->typ);
fixSample(s);
if (result != (size_t)s->len) // write not OK
{
fclose(f);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
return false;
}
}
}
}
removeSongModifiedFlag();
fclose(f);
editor.diskOpReadDir = true; // force diskop re-read
setMouseBusy(false);
return true;
}
static bool saveMOD(UNICHAR *filenameU)
{
bool test, tooManyInstr, incompatEfx, noteUnderflow;
int8_t *srcPtr, *dstPtr;
uint8_t ton, inst, pattBuff[64*4*32];
int16_t a, i, ap;
int32_t j, k, l1, l2, l3, writeLen, bytesToWrite, bytesWritten;
FILE *f;
instrTyp *ins;
sampleTyp *smp;
tonTyp *t;
songMOD31HeaderTyp hm;
tooManyInstr = false;
incompatEfx = false;
noteUnderflow = false;
if (audio.linearFreqTable)
okBoxThreadSafe(0, "System message", "Linear frequency table used!");
// sanity checking
test = false;
if (song.len > 128)
test = true;
for (i = 100; i < 256; i++)
{
if (patt[i] != NULL)
{
test = true;
break;
}
}
if (test) okBoxThreadSafe(0, "System message", "Too many patterns!");
for (i = 32; i <= 128; i++)
{
if (getRealUsedSamples(i) > 0)
{
okBoxThreadSafe(0, "System message", "Too many instruments!");
break;
}
}
test = false;
for (i = 1; i <= 31; i++)
{
ins = instr[i];
if (ins == NULL)
continue;
smp = &ins->samp[0];
j = getRealUsedSamples(i);
if (j > 1)
{
test = true;
break;
}
if (j == 1)
{
if (smp->len > 65534 || ins->fadeOut != 0 || ins->envVTyp != 0 || ins->envPTyp != 0 ||
(smp->typ & 3) == 2 || smp->relTon != 0 || ins->midiOn)
{
test = true;
break;
}
}
}
if (test) okBoxThreadSafe(0, "System message", "Incompatible instruments!");
for (i = 0; i < 99; i++)
{
if (patt[i] != NULL)
{
if (pattLens[i] != 64)
{
okBoxThreadSafe(0, "System message", "Unable to convert module. (Illegal pattern length)");
return false;
}
for (j = 0; j < 64; j++)
{
for (k = 0; k < song.antChn; k++)
{
t = &patt[i][(j * MAX_VOICES) + k];
if (t->instr > 31)
tooManyInstr = true;
if (t->effTyp > 15 || t->vol != 0)
incompatEfx = true;
// added security that wasn't present in FT2
if (t->ton > 0 && t->ton < 10)
noteUnderflow = true;
}
}
}
}
if (tooManyInstr) okBoxThreadSafe(0, "System message", "Instrument(s) above 31 was found in pattern data!");
if (incompatEfx) okBoxThreadSafe(0, "System message", "Incompatible effect(s) was found in pattern data!");
if (noteUnderflow) okBoxThreadSafe(0, "System message", "Note(s) below A-0 were found in pattern data!");
// setup header buffer
memset(&hm, 0, sizeof (hm));
memcpy(hm.name, song.name, sizeof (hm.name));
hm.len = (uint8_t)song.len;
if (hm.len > 128) hm.len = 128;
hm.repS = (uint8_t)song.repS;
if (hm.repS > 127) hm.repS = 0;
memcpy(hm.songTab, song.songTab, song.len);
// calculate number of patterns
ap = 0;
for (i = 0; i < song.len; i++)
{
if (song.songTab[i] > ap)
ap = song.songTab[i];
}
if (song.antChn == 4)
memcpy(hm.sig, (ap > 64) ? "M!K!" : "M.K.", 4);
else
memcpy(hm.sig, modSig[song.antChn-1], 4);
// read sample information into header buffer
for (i = 1; i <= 31; i++)
{
songMODInstrHeaderTyp *modIns = &hm.instr[i-1];
memcpy(modIns->name, song.instrName[i], sizeof (modIns->name));
if (instr[i] != NULL && getRealUsedSamples(i) != 0)
{
smp = &instr[i]->samp[0];
l1 = smp->len >> 1;
l2 = smp->repS >> 1;
l3 = smp->repL >> 1;
if (smp->typ & 16)
{
l1 >>= 1;
l2 >>= 1;
l3 >>= 1;
}
if (l1 > 32767)
l1 = 32767;
if (l2 > l1)
l2 = l1;
if (l2+l3 > l1)
l3 = l1 - l2;
// FT2 bug-fix
if (l3 < 1)
{
l2 = 0;
l3 = 1;
}
modIns->len = (uint16_t)(SWAP16(l1));
modIns->fine = ((smp->fine + 128) >> 4) ^ 8;
modIns->vol = smp->vol;
if ((smp->typ & 3) == 0)
{
modIns->repS = 0;
modIns->repL = SWAP16(1);
}
else
{
modIns->repS = (uint16_t)(SWAP16(l2));
modIns->repL = (uint16_t)(SWAP16(l3));
}
}
// FT2 bugfix: never allow replen being below 2 (1)
if (SWAP16(modIns->repL) < 1)
{
modIns->repS = SWAP16(0);
modIns->repL = SWAP16(1);
}
}
f = UNICHAR_FOPEN(filenameU, "wb");
if (f == NULL)
{
okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?");
return false;
}
// write header
if (fwrite(&hm, 1, sizeof (hm), f) != sizeof (hm))
{
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
goto modSaveError;
}
// write pattern data
for (i = 0; i <= ap; i++)
{
if (patt[i] == NULL)
{
// empty pattern
memset(pattBuff, 0, song.antChn * (64 * 4));
}
else
{
a = 0;
for (j = 0; j < 64; j++)
{
for (k = 0; k < song.antChn; k++)
{
t = &patt[i][(j * MAX_VOICES) + k];
inst = t->instr;
ton = t->ton;
// FT2 bugfix: prevent overflow
if (inst > 31)
inst = 0;
// FT2 bugfix: convert note-off into no note
if (ton == 97)
ton = 0;
// FT2 bugfix: clamp notes below 10 (A-0) to prevent 12-bit period overflow
if (ton > 0 && ton < 10)
ton = 10;
if (ton == 0)
{
pattBuff[a+0] = inst & 0xF0;
pattBuff[a+1] = 0;
}
else
{
pattBuff[a+0] = (inst & 0xF0) | ((amigaPeriod[ton-1] >> 8) & 0x0F);
pattBuff[a+1] = amigaPeriod[ton-1] & 0xFF;
}
// FT2 bugfix: if effect is overflowing (0xF in .MOD), set effect and param to 0
if (t->effTyp > 0x0F)
{
pattBuff[a+2] = (inst & 0x0F) << 4;
pattBuff[a+3] = 0;
}
else
{
pattBuff[a+2] = ((inst & 0x0F) << 4) | (t->effTyp & 0x0F);
pattBuff[a+3] = t->eff;
}
a += 4;
}
}
}
if (fwrite(pattBuff, 1, song.antChn * (64 * 4), f) != (size_t)(song.antChn * (64 * 4)))
{
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
goto modSaveError;
}
}
// write sample data
for (i = 0; i < 31; i++)
{
if (instr[1+i] == NULL || getRealUsedSamples(1+i) == 0)
continue;
smp = &instr[1+i]->samp[0];
if (smp->pek == NULL || smp->len <= 0)
continue;
restoreSample(smp);
l1 = smp->len >> 1;
if (smp->typ & 16) // 16-bit sample (convert to 8-bit)
{
if (l1 > 65534)
l1 = 65534;
// let's borrow "pattBuff" here
dstPtr = (int8_t *)pattBuff;
writeLen = l1;
bytesWritten = 0;
while (bytesWritten < writeLen) // write in 8K blocks
{
bytesToWrite = sizeof (pattBuff);
if (bytesWritten+bytesToWrite > writeLen)
bytesToWrite = writeLen - bytesWritten;
srcPtr = &smp->pek[(bytesWritten << 1) + 1]; // +1 to align to high byte
for (j = 0; j < bytesToWrite; j++)
dstPtr[j] = srcPtr[j << 1];
if (fwrite(dstPtr, 1, bytesToWrite, f) != (size_t)bytesToWrite)
{
fixSample(smp);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
goto modSaveError;
}
bytesWritten += bytesToWrite;
}
}
else
{
// 8-bit sample
if (l1 > 32767)
l1 = 32767;
l1 <<= 1;
if (fwrite(smp->pek, 1, l1, f) != (size_t)l1)
{
fixSample(smp);
okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
goto modSaveError;
}
}
fixSample(smp);
}
fclose(f);
removeSongModifiedFlag();
editor.diskOpReadDir = true; // force diskop re-read
setMouseBusy(false);
return true;
modSaveError:
fclose(f);
return false;
}
static int32_t SDLCALL saveMusicThread(void *ptr)
{
(void)ptr;
assert(editor.tmpFilenameU != NULL);
if (editor.tmpFilenameU == NULL)
return false;
pauseAudio();
if (editor.moduleSaveMode == 1)
saveXM(editor.tmpFilenameU);
else
saveMOD(editor.tmpFilenameU);
resumeAudio();
return true;
}
void saveMusic(UNICHAR *filenameU)
{
UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);
mouseAnimOn();
thread = SDL_CreateThread(saveMusicThread, NULL, NULL);
if (thread == NULL)
{
okBoxThreadSafe(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static uint16_t packPatt(uint8_t *writePtr, uint8_t *pattPtr, uint16_t numRows)
{
uint8_t bytes[5], packBits, *firstBytePtr;
uint16_t totalPackLen;
totalPackLen = 0;
if (pattPtr == NULL)
return 0;
for (uint16_t row = 0; row < numRows; row++)
{
for (uint16_t chn = 0; chn < song.antChn; chn++)
{
bytes[0] = *pattPtr++;
bytes[1] = *pattPtr++;
bytes[2] = *pattPtr++;
bytes[3] = *pattPtr++;
bytes[4] = *pattPtr++;
firstBytePtr = writePtr++;
packBits = 0;
if (bytes[0] > 0) { packBits |= 1; *writePtr++ = bytes[0]; } // note
if (bytes[1] > 0) { packBits |= 2; *writePtr++ = bytes[1]; } // instrument
if (bytes[2] > 0) { packBits |= 4; *writePtr++ = bytes[2]; } // volume column
if (bytes[3] > 0) { packBits |= 8; *writePtr++ = bytes[3]; } // effect
if (packBits == 15) // first four bits set?
{
// no packing needed, write pattern data as is
// point to first byte (and overwrite data)
writePtr = firstBytePtr;
*writePtr++ = bytes[0];
*writePtr++ = bytes[1];
*writePtr++ = bytes[2];
*writePtr++ = bytes[3];
*writePtr++ = bytes[4];
totalPackLen += 5;
continue;
}
if (bytes[4] > 0) { packBits |= 16; *writePtr++ = bytes[4]; } // effect parameter
*firstBytePtr = packBits | 128; // write pack bits byte
totalPackLen += (uint16_t)(writePtr - firstBytePtr); // bytes writen
}
// skip unused channels (unpacked patterns always have 32 channels)
pattPtr += sizeof (tonTyp) * (MAX_VOICES - song.antChn);
}
return totalPackLen;
}