ref: cf5d3958c18d6959e0ee6088ae9d7633973e9c42
dir: /src/ft2_module_loader.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>
#endif
#include "ft2_header.h"
#include "ft2_audio.h"
#include "ft2_config.h"
#include "ft2_scopes.h"
#include "ft2_trim.h"
#include "ft2_inst_ed.h"
#include "ft2_sample_ed.h"
#include "ft2_wav_renderer.h"
#include "ft2_pattern_ed.h"
#include "ft2_gui.h"
#include "ft2_diskop.h"
#include "ft2_sample_loader.h"
#include "ft2_mouse.h"
#include "ft2_scopes.h"
#include "ft2_midi.h"
#include "ft2_events.h"
#include "ft2_video.h"
/* This is a *HUGE* mess!
** I hope you never have to modify it, and you probably shouldn't either.
** You will experience a lot of headaches if you dig into this file...
** If something looks to be wrong, it's probably right!
**
** The actual module load routines are ported from FT2 and slightly modified. */
enum
{
FORMAT_NONE = 0,
FORMAT_XM = 1,
FORMAT_MOD = 2,
FORMAT_S3M = 3,
FORMAT_STM = 4
};
// DO NOT TOUCH THESE STRUCTS!
#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct songSTMinstrHeaderTyp_t
{
char name[12];
uint8_t nul, insDisk;
uint16_t reserved1, len, repS, repE;
uint8_t vol, reserved2;
uint16_t rate;
int32_t reserved3;
uint16_t paraLen;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songSTMinstrHeaderTyp;
typedef struct songSTMHeaderTyp_t
{
char name[20], sig[8];
uint8_t id1a, typ;
uint8_t verMajor, verMinor;
uint8_t tempo, ap, vol, reserved[13];
songSTMinstrHeaderTyp instr[31];
uint8_t songTab[128];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songSTMHeaderTyp;
typedef struct songS3MinstrHeaderTyp_t
{
uint8_t typ;
char dosName[12];
uint8_t memSegH;
uint16_t memSeg;
int32_t len, repS, repE;
uint8_t vol, dsk, pack, flags;
int32_t c2Spd, res1;
uint16_t gusPos;
uint8_t res2[6];
char name[28], id[4];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songS3MinstrHeaderTyp;
typedef struct songS3MHeaderTyp_t
{
char name[28];
uint8_t id1a, typ;
uint16_t res1;
int16_t songTabLen, antInstr, antPatt;
uint16_t flags, trackerID, ver;
char id[4];
uint8_t globalVol, defSpeed, defTempo, masterVol, res2[12], chanType[32];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songS3MHeaderTyp;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
static volatile uint8_t loadedFormat;
static volatile bool stereoSamplesWarn, linearFreqTable, musicIsLoading, moduleLoaded, moduleFailedToLoad;
static uint8_t oldPlayMode, pattBuff[12288];
static const uint8_t stmEff[16] = { 0, 0, 11, 0, 10, 2, 1, 3, 4, 7, 0, 5 ,6, 0, 0, 0 };
static SDL_Thread *thread;
// these temporarily read to, then copied to real struct if load was OK (should not need to be volatile'd)
static int16_t pattLensTmp[MAX_PATTERNS];
static tonTyp *pattTmp[MAX_PATTERNS];
static instrTyp *instrTmp[1 + MAX_INST];
static songTyp songTmp;
static void setupLoadedModule(void);
static void freeTmpModule(void);
static bool loadInstrHeader(FILE *f, uint16_t i);
static void checkSampleRepeat(sampleTyp *s);
static bool loadInstrSample(FILE *f, uint16_t i);
void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn);
static bool tmpPatternEmpty(uint16_t nr);
static bool loadPatterns(FILE *f, uint16_t antPtn);
// ft2_replayer.c
extern const char modSig[32][5];
extern const uint16_t amigaPeriod[12*8];
static bool allocateTmpInstr(int16_t nr)
{
instrTyp *p;
if (instrTmp[nr] != NULL)
return false; // already allocated
p = (instrTyp *)malloc(sizeof (instrTyp));
if (p == NULL)
return false;
memset(p, 0, sizeof (instrTyp));
for (int8_t i = 0; i < 16; i++) // set standard sample pan/vol
{
p->samp[i].pan = 128;
p->samp[i].vol = 64;
}
instrTmp[nr] = p;
return true;
}
static bool loadMusicMOD(FILE *f, uint32_t fileLength)
{
char ID[16];
bool modIsUST, modIsFEST, modIsNT;
uint8_t bytes[4];
int16_t i, j, k, ai;
uint16_t a, b, period, ciaPeriod;
tonTyp *ton;
sampleTyp *s;
songMOD31HeaderTyp h_MOD31;
songMOD15HeaderTyp h_MOD15;
// start loading MOD
loadedFormat = FORMAT_MOD;
rewind(f);
fread(ID, 1, 16, f);
fseek(f, 0x1D, SEEK_SET);
fread(bytes, 1, 1, f);
rewind(f);
// since .mod is the last format tested, check if the file is an .it module (Impulse Tracker)
if (!memcmp(ID, "IMPM", 4) && bytes[0] == 0)
{
okBoxThreadSafe(0, "System message", "Error: Impulse Tracker modules are not supported!");
goto modLoadError;
}
// check if the file to load is a WAV, if so reject it
if (!memcmp(ID, "RIFF", 4) && !memcmp(&ID[8], "WAVEfmt", 7))
{
okBoxThreadSafe(0, "System message", "Error: Can't load a .wav file as a module!");
goto modLoadError;
}
if (fileLength < 1596 || fileLength > 20842494) // minimum and maximum possible size for an FT2 .mod
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
if (fread(&h_MOD31, 1, sizeof (h_MOD31), f) != sizeof (h_MOD31))
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
modIsFEST = false;
modIsNT = false;
modIsUST = false;
if (!strncmp(h_MOD31.sig, "N.T.", 4))
{
j = 4;
modIsNT = true;
}
else if (!strncmp(h_MOD31.sig, "FEST", 4) || !strncmp(h_MOD31.sig, "M&K!", 4))
{
modIsFEST = true;
modIsNT = true;
j = 4;
}
else if (!strncmp(h_MOD31.sig, "M!K!", 4) || !strncmp(h_MOD31.sig, "M.K.", 4) || !strncmp(h_MOD31.sig, "FLT4", 4))
{
j = 4;
}
else if (!strncmp(h_MOD31.sig, "OCTA", 4) || !strncmp(h_MOD31.sig, "FLT8", 4) || !strncmp(h_MOD31.sig, "CD81", 4))
{
j = 8;
}
else
{
j = 0;
for (i = 0; i < 32; i++)
{
if (!strncmp(h_MOD31.sig, modSig[i], 4))
{
j = i + 1;
break;
}
else if (j == 31)
{
j = -1; // ID not recignized
}
}
}
// unsupported MOD
if (j == -1)
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
if (j > 0)
{
modIsUST = false;
if (fileLength < sizeof (h_MOD31))
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
songTmp.antChn = (uint8_t)j;
songTmp.len = h_MOD31.len;
songTmp.repS = h_MOD31.repS;
memcpy(songTmp.songTab, h_MOD31.songTab, 128);
ai = 31;
}
else
{
modIsUST = true;
if (fileLength < sizeof (h_MOD15))
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
fseek(f, 0, SEEK_SET);
if (fread(&h_MOD15, 1, sizeof (h_MOD15), f) != sizeof (h_MOD15))
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
songTmp.antChn = 4;
songTmp.len = h_MOD15.len;
songTmp.repS = h_MOD15.repS;
memcpy(songTmp.songTab, h_MOD15.songTab, 128);
ai = 15;
}
if (songTmp.antChn == 0 || songTmp.len < 1)
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
if (!strncmp(h_MOD31.sig, "M.K.", 4) && songTmp.len == 129)
songTmp.len = 127; // fixes a specific copy of beatwave.mod by Sidewinder
if (songTmp.len > 128 || (modIsUST && (songTmp.repS == 0 || songTmp.repS > 220)))
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
// trim off spaces at end of name
for (i = 19; i >= 0; i--)
{
if (h_MOD31.name[i] == ' ' || h_MOD31.name[i] == 0x1A)
h_MOD31.name[i] = '\0';
else
break;
}
memcpy(songTmp.name, h_MOD31.name, 20);
songTmp.name[20] = '\0';
for (a = 0; a < ai; a++)
{
// trim off spaces at end of name
for (i = 21; i >= 0; i--)
{
if (h_MOD31.instr[a].name[i] == ' ' || h_MOD31.instr[a].name[i] == 0x1A)
h_MOD31.instr[a].name[i] = '\0';
else
break;
}
memcpy(songTmp.instrName[1+a], h_MOD31.instr[a].name, 22);
songTmp.instrName[1+a][22] = '\0';
}
b = 0;
for (a = 0; a < 128; a++)
{
if (songTmp.songTab[a] > b)
b = songTmp.songTab[a];
}
if (songTmp.len < 255)
memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);
for (a = 0; a <= b; a++)
{
pattTmp[a] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
if (pattTmp[a] == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto modLoadError;
}
pattLensTmp[a] = 64;
for (j = 0; j < 64; j++)
{
for (k = 0; k < songTmp.antChn; k++)
{
ton = &pattTmp[a][(j * MAX_VOICES) + k];
if (fread(bytes, 1, 4, f) != 4)
{
okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
goto modLoadError;
}
// period to note
period = ((bytes[0] & 0x0F) << 8) | bytes[1];
for (i = 0; i < 8*12; i++)
{
if (period >= amigaPeriod[i])
{
ton->ton = (uint8_t)i + 1;
break;
}
}
ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
ton->effTyp = bytes[2] & 0x0F;
ton->eff = bytes[3];
if (ton->effTyp == 0xC)
{
if (ton->eff > 64)
ton->eff = 64;
}
else if (ton->effTyp == 0x1)
{
if (ton->eff == 0)
ton->effTyp = 0;
}
else if (ton->effTyp == 0x2)
{
if (ton->eff == 0)
ton->effTyp = 0;
}
else if (ton->effTyp == 0x5)
{
if (ton->eff == 0)
ton->effTyp = 0x3;
}
else if (ton->effTyp == 0x6)
{
if (ton->eff == 0)
ton->effTyp = 0x4;
}
else if (ton->effTyp == 0xA)
{
if (ton->eff == 0)
ton->effTyp = 0;
}
else if (ton->effTyp == 0xE)
{
// check if certain E commands are empty
if (ton->eff == 0x10 || ton->eff == 0x20 || ton->eff == 0xA0 || ton->eff == 0xB0)
{
ton->effTyp = 0;
ton->eff = 0;
}
}
if (modIsUST)
{
if (ton->effTyp == 0x01)
{
// arpeggio
ton->effTyp = 0x00;
}
else if (ton->effTyp == 0x02)
{
// pitch slide
if (ton->eff & 0xF0)
{
ton->effTyp = 0x02;
ton->eff >>= 4;
}
else if (ton->eff & 0x0F)
{
ton->effTyp = 0x01;
}
}
// I don't remember why I did this...
if (ton->effTyp == 0x0D && ton->eff > 0)
ton->effTyp = 0x0A;
}
if (modIsNT && ton->effTyp == 0x0D)
ton->eff = 0;
}
}
if (tmpPatternEmpty(a))
{
if (pattTmp[a] != NULL)
{
free(pattTmp[a]);
pattTmp[a] = NULL;
}
}
}
for (a = 0; a < ai; a++)
{
if (h_MOD31.instr[a].len == 0)
continue;
if (!allocateTmpInstr(1 + a))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto modLoadError;
}
setNoEnvelope(instrTmp[1 + a]);
s = &instrTmp[1+a]->samp[0];
s->len = 2 * SWAP16(h_MOD31.instr[a].len);
s->pek = (int8_t *)malloc(s->len + LOOP_FIX_LEN);
if (s->pek == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto modLoadError;
}
memcpy(s->name, songTmp.instrName[1+a], 22);
if (modIsFEST)
h_MOD31.instr[a].fine = (32 - (h_MOD31.instr[a].fine & 0x1F)) >> 1;
if (!modIsUST)
s->fine = 8 * ((2 * ((h_MOD31.instr[a].fine & 0x0F) ^ 8)) - 16);
else
s->fine = 0;
s->pan = 128;
s->vol = h_MOD31.instr[a].vol;
if (s->vol > 64) s->vol = 64;
s->repS = 2 * SWAP16(h_MOD31.instr[a].repS);
if (modIsUST)
s->repS /= 2;
s->repL = 2 * SWAP16(h_MOD31.instr[a].repL);
if (s->repL <= 2)
{
s->repS = 0;
s->repL = 0;
}
if (s->repS+s->repL > s->len)
{
if (s->repS >= s->len)
{
s->repS = 0;
s->repL = 0;
}
else
{
s->repL = s->len - s->repS;
}
}
if (s->repL > 2)
s->typ = 1;
else
s->typ = 0;
if (modIsUST && (s->repS > 2 && s->repS < s->len))
{
s->len -= s->repS;
fseek(f, s->repS, SEEK_CUR);
s->repS = 0;
}
if (fread(s->pek, s->len, 1, f) == 1)
{
fixSample(s);
}
else
{
free(s->pek);
s->pek = NULL;
s->len = 0;
}
}
songTmp.speed = 125;
if (modIsUST)
{
// repS is initialBPM in UST MODs
if (songTmp.repS != 120) // 120 is a special case and means 50Hz (125BPM)
{
if (songTmp.repS > 239)
songTmp.repS = 239;
// convert UST tempo to BPM
const double dPALAmigaCiaClk = 709379.0;
ciaPeriod = (240 - songTmp.repS) * 122;
songTmp.speed = (uint16_t)round((dPALAmigaCiaClk / ciaPeriod) * (125.0 / 50.0));
}
songTmp.repS = 0;
}
else if (songTmp.repS >= songTmp.len)
{
songTmp.repS = 0;
}
fclose(f);
songTmp.initialTempo = songTmp.tempo = 6;
moduleLoaded = true;
return true;
modLoadError:
fclose(f);
freeTmpModule();
moduleFailedToLoad = true;
return false;
}
static uint8_t stmTempoToBPM(uint8_t tempo) // ported from original ST2.3 replayer code
{
const uint8_t slowdowns[16] = { 140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1 };
uint32_t bpm;
uint16_t hz = 50;
hz -= ((slowdowns[tempo >> 4] * (tempo & 15)) >> 4); // can and will underflow
bpm = (uint32_t)round(hz * 2.5);
return (uint8_t)CLAMP(bpm, 32, 255); // result can be slightly off, but close enough...
}
static bool loadMusicSTM(FILE *f, uint32_t fileLength)
{
bool check3xx;
uint8_t typ, tmp8, tempo;
int16_t i, j, k, ai, ap, tmp;
uint16_t a;
int32_t len;
tonTyp *ton;
sampleTyp *s;
songSTMHeaderTyp h_STM;
rewind(f);
// start loading STM
if (fread(&h_STM, 1, sizeof (h_STM), f) != sizeof (h_STM))
return loadMusicMOD(f, fileLength); // file is not a .stm, try to load as .mod
if (memcmp(h_STM.sig, "!Scream!", 8) && memcmp(h_STM.sig, "BMOD2STM", 8) &&
memcmp(h_STM.sig, "WUZAMOD!", 8) && memcmp(h_STM.sig, "SWavePro", 8))
{
return loadMusicMOD(f, fileLength); // file is not a .stm, try to load as .mod
}
loadedFormat = FORMAT_STM;
if (h_STM.verMinor == 0 || h_STM.typ != 2)
{
okBoxThreadSafe(0, "System message", "Error loading .stm: Incompatible module!");
goto stmLoadError;
}
songTmp.antChn = 4;
memcpy(songTmp.songTab, h_STM.songTab, 128);
i = 0;
while (i < 128 && songTmp.songTab[i] < 99) i++;
songTmp.len = i + (i == 0);
if (songTmp.len < 255)
memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);
// trim off spaces at end of name
for (i = 19; i >= 0; i--)
{
if (h_STM.name[i] == ' ' || h_STM.name[i] == 0x1A)
h_STM.name[i] = '\0';
else
break;
}
memcpy(songTmp.name, h_STM.name, 20);
songTmp.name[20] = '\0';
tempo = h_STM.tempo;
if (h_STM.verMinor < 21)
tempo = ((tempo / 10) << 4) + (tempo % 10);
if (tempo == 0)
tempo = 96;
songTmp.initialTempo = songTmp.tempo = CLAMP(h_STM.tempo >> 4, 1, 31);
songTmp.speed = stmTempoToBPM(tempo);
if (h_STM.verMinor > 10)
songTmp.globVol = MIN(h_STM.vol, 64);
ap = h_STM.ap;
for (i = 0; i < ap; i++)
{
pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
if (pattTmp[i] == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto stmLoadError;
}
pattLensTmp[i] = 64;
if (fread(pattBuff, 64 * 4 * 4, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading!");
goto stmLoadError;
}
a = 0;
for (j = 0; j < 64; j++)
{
for (k = 0; k < 4; k++)
{
ton = &pattTmp[i][(j * MAX_VOICES) + k];
if (pattBuff[a] == 254)
{
ton->ton = 97;
}
else if (pattBuff[a] < 96)
{
ton->ton = (12 * (pattBuff[a] >> 4)) + (25 + (pattBuff[a] & 0x0F));
if (ton->ton > 96)
ton->ton = 0;
}
else
{
ton->ton = 0;
}
ton->instr = pattBuff[a + 1] >> 3;
typ = (pattBuff[a + 1] & 7) + ((pattBuff[a + 2] & 0xF0) >> 1);
if (typ <= 64)
ton->vol = typ + 0x10;
ton->eff = pattBuff[a + 3];
tmp = pattBuff[a + 2] & 0x0F;
if (tmp == 1)
{
ton->effTyp = 15;
if (h_STM.verMinor < 21)
ton->eff = ((ton->eff / 10) << 4) + (ton->eff % 10);
ton->eff >>= 4;
}
else if (tmp == 3)
{
ton->effTyp = 13;
ton->eff = 0;
}
else if (tmp == 2 || (tmp >= 4 && tmp <= 12))
{
ton->effTyp = stmEff[tmp];
if (ton->effTyp == 0xA)
{
if (ton->eff & 0x0F)
ton->eff &= 0x0F;
else
ton->eff &= 0xF0;
}
}
else
{
ton->eff = 0;
}
/* Remove any EDx with no note.
** SDx with no note in ST3 = does nothing
** EDx with no note in FT2 = still retriggers */
if (ton->effTyp == 0xE && (ton->eff & 0xF0) == 0xD0)
{
if (ton->ton == 0 || ton->ton == 97)
{
ton->eff = 0;
ton->effTyp = 0;
}
}
if (ton->effTyp > 35)
{
ton->effTyp = 0;
ton->eff = 0;
}
a += 4;
}
}
if (tmpPatternEmpty(i))
{
if (pattTmp[i] != NULL)
{
free(pattTmp[i]);
pattTmp[i] = NULL;
}
}
}
ai = 31;
for (i = 0; i < 31; i++)
{
// trim off spaces at end of name
for (j = 11; j >= 0; j--)
{
if (h_STM.instr[i].name[j] == ' ' || h_STM.instr[i].name[j] == 0x1A)
h_STM.instr[i].name[j] = '\0';
else
break;
}
memset(&songTmp.instrName[1+i], 0, sizeof (songTmp.instrName[1+i]));
memcpy(&songTmp.instrName[1+i], h_STM.instr[i].name, 12);
if (h_STM.instr[i].len != 0 && h_STM.instr[i].reserved1 != 0)
{
allocateTmpInstr(1 + i);
setNoEnvelope(instrTmp[i]);
s = &instrTmp[1+i]->samp[0];
s->pek = (int8_t *)malloc(h_STM.instr[i].len + LOOP_FIX_LEN);
if (s->pek == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto stmLoadError;
}
s->len = h_STM.instr[i].len;
tuneSample(s, h_STM.instr[i].rate);
s->vol = h_STM.instr[i].vol;
s->repS = h_STM.instr[i].repS;
s->repL = h_STM.instr[i].repE - h_STM.instr[i].repS;
s->pan = 128;
if (s->repS < s->len && h_STM.instr[i].repE > s->repS && h_STM.instr[i].repE != 0xFFFF)
{
if (s->repS+s->repL > s->len)
s->repL = s->len - s->repS;
s->typ = 1;
}
else
{
s->repS = 0;
s->repL = 0;
s->typ = 0;
}
if (s->vol > 64)
s->vol = 64;
if (fread(s->pek, s->len, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Possibly corrupt module?");
goto stmLoadError;
}
fixSample(s);
}
}
// non-FT2: fix overflown 9xx and illegal 3xx
for (i = 0; i < ap; i++)
{
if (pattTmp[i] == NULL)
continue;
for (k = 0; k < songTmp.antChn; k++)
{
check3xx = false;
for (j = 0; j < 64; j++)
{
ton = &pattTmp[i][(j * MAX_VOICES) + k];
if (ton->ton > 0 && ton->ton < 97 && ton->effTyp != 0x3)
check3xx = true;
if (ton->ton > 0 && ton->ton < 97 && ton->effTyp == 0x3)
check3xx = false;
if (check3xx && ton->effTyp == 0x3)
{
if (ton->ton == 0 || ton->ton == 97)
{
ton->effTyp = 0;
ton->eff = 0;
}
}
if (ton->effTyp == 0x9 && ton->eff > 0)
{
if (ton->instr != 0 && ton->instr <= ai)
{
s = &instrTmp[ton->instr]->samp[0];
len = s->len;
tmp8 = 0;
if (len > 0)
{
tmp8 = ton->eff;
if (tmp8 >= len/256)
{
if (len/256 < 1)
tmp8 = 0;
else
tmp8 = (uint8_t)((len/256) - 1);
}
}
if (tmp8 > 0)
{
ton->eff = tmp8;
}
else
{
ton->effTyp = 0;
ton->eff = 0;
}
}
else
{
ton->effTyp = 0;
ton->eff = 0;
}
}
}
}
}
fclose(f);
moduleLoaded = true;
return true;
stmLoadError:
fclose(f);
freeTmpModule();
moduleFailedToLoad = true;
return false;
}
static int8_t countS3MChannels(uint16_t antPtn)
{
uint8_t j, k, channels;
int16_t i;
tonTyp ton;
channels = 0;
for (i = 0; i < antPtn; i++)
{
if (pattTmp[i] == NULL)
continue;
for (j = 0; j < 64; j++)
{
for (k = 0; k < MAX_VOICES; k++)
{
ton = pattTmp[i][(j * MAX_VOICES) + k];
if (ton.eff == 0 && ton.effTyp == 0 && ton.instr == 0 && ton.ton == 0 && ton.vol == 0)
continue;
if (k > channels)
channels = k;
}
}
}
channels++;
return channels;
}
static bool loadMusicS3M(FILE *f, uint32_t dataLength)
{
int8_t *tmpSmp;
bool check3xx, illegalUxx;
uint8_t ha[2048];
uint8_t s3mLastDEff[32], s3mLastEEff[32], s3mLastFEff[32];
uint8_t s3mLastSEff[32], s3mLastJEff[32], s3mLastGInstr[32], tmp8, typ;
int16_t i, j, k, ai, ap, ver, ii, kk, tmp;
uint16_t ptnOfs[256];
int32_t len;
tonTyp ton, *pattTon;
sampleTyp *s;
songS3MHeaderTyp h_S3M;
songS3MinstrHeaderTyp h_S3MInstr;
stereoSamplesWarn = false;
rewind(f);
// start loading S3M
if (fread(&h_S3M, 1, sizeof (h_S3M), f) != sizeof (h_S3M))
return loadMusicSTM(f, dataLength); // not a .s3m, try loading as .stm
if (memcmp(h_S3M.id, "SCRM", 4))
return loadMusicSTM(f, dataLength); // not a .s3m, try loading as .stm
loadedFormat = FORMAT_S3M;
if (h_S3M.antInstr > MAX_INST || h_S3M.songTabLen > 256 || h_S3M.antPatt > 256 ||
h_S3M.typ != 16 || h_S3M.ver < 1 || h_S3M.ver > 2)
{
okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
goto s3mLoadError;
}
memset(songTmp.songTab, 255, sizeof (songTmp.songTab));
if (fread(songTmp.songTab, h_S3M.songTabLen, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
// count real song table entries
songTmp.len = 256;
while (songTmp.len > 0 && songTmp.songTab[songTmp.len-1] == 255)
songTmp.len--;
if (songTmp.len == 256)
songTmp.len = 255;
// remove pattern separators (254)
k = 0;
j = 0;
for (i = 0; i < songTmp.len; i++)
{
if (songTmp.songTab[i] != 254)
songTmp.songTab[j++] = songTmp.songTab[i];
else
k++;
}
if (k <= songTmp.len)
songTmp.len -= k;
else
songTmp.len = 0;
// clear unused song table entries
if (songTmp.len < 255)
memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);
songTmp.speed = h_S3M.defTempo;
if (songTmp.speed < 32)
songTmp.speed = 32;
songTmp.tempo = h_S3M.defSpeed;
if (songTmp.tempo == 0)
songTmp.tempo = 6;
if (songTmp.tempo > 31)
songTmp.tempo = 31;
songTmp.initialTempo = songTmp.tempo;
// trim off spaces at end of name
for (i = 19; i >= 0; i--)
{
if (h_S3M.name[i] == ' ' || h_S3M.name[i] == 0x1A)
h_S3M.name[i] = '\0';
else
break;
}
memcpy(songTmp.name, h_S3M.name, 20);
songTmp.name[20] = '\0';
ap = h_S3M.antPatt;
ai = h_S3M.antInstr;
ver = h_S3M.ver;
k = 31;
while (k >= 0 && h_S3M.chanType[k] >= 16) k--;
songTmp.antChn = (k + 2) & 254;
if (fread(ha, ai + ai, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
if (fread(ptnOfs, ap + ap, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
// *** PATTERNS ***
illegalUxx = false;
k = 0;
for (i = 0; i < ap; i++)
{
if (ptnOfs[i] == 0)
continue; // empty pattern
memset(s3mLastDEff, 0, sizeof (s3mLastDEff));
memset(s3mLastEEff, 0, sizeof (s3mLastEEff));
memset(s3mLastFEff, 0, sizeof (s3mLastFEff));
memset(s3mLastSEff, 0, sizeof (s3mLastSEff));
memset(s3mLastJEff, 0, sizeof (s3mLastJEff));
memset(s3mLastGInstr, 0, sizeof (s3mLastGInstr));
fseek(f, ptnOfs[i] << 4, SEEK_SET);
if (feof(f))
continue;
if (fread(&j, 2, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
if (j > 0 && j <= 12288)
{
pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
if (pattTmp[i] == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
pattLensTmp[i] = 64;
if (fread(pattBuff, j, 1, f) != 1)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
k = 0;
kk = 0;
while (k < j && kk < 64)
{
typ = pattBuff[k++];
if (typ == 0)
{
kk++;
}
else
{
ii = typ & 31;
memset(&ton, 0, sizeof (ton));
// note and sample
if (typ & 32)
{
ton.ton = pattBuff[k++];
ton.instr = pattBuff[k++];
if (ton.instr > MAX_INST)
ton.instr = 0;
if (ton.ton == 254) ton.ton = 97;
else if (ton.ton == 255) ton.ton = 0;
else
{
ton.ton = 1 + (ton.ton & 0xF) + (ton.ton >> 4) * 12;
if (ton.ton > 96)
ton.ton = 0;
}
}
// volume
if (typ & 64)
{
ton.vol = pattBuff[k++];
if (ton.vol <= 64)
ton.vol += 0x10;
else
ton.vol = 0;
}
// effect
if (typ & 128)
{
ton.effTyp = pattBuff[k++];
ton.eff = pattBuff[k++];
if (ton.eff == 0)
{
if (ton.effTyp == 4)
{
if ((s3mLastDEff[ii] & 0xF0) == 0xF0 || (s3mLastDEff[ii] & 0x0F) == 0x0F)
ton.eff = s3mLastDEff[ii];
}
else if (ton.effTyp == 5) ton.eff = s3mLastEEff[ii];
else if (ton.effTyp == 6) ton.eff = s3mLastFEff[ii];
else if (ton.effTyp == 10) ton.eff = s3mLastJEff[ii];
else if (ton.effTyp == 19) ton.eff = s3mLastSEff[ii];
}
if (ton.eff != 0)
{
if (ton.effTyp == 4) s3mLastDEff[ii] = ton.eff;
else if (ton.effTyp == 5) s3mLastEEff[ii] = ton.eff;
else if (ton.effTyp == 6) s3mLastFEff[ii] = ton.eff;
else if (ton.effTyp == 10) s3mLastJEff[ii] = ton.eff;
else if (ton.effTyp == 19) s3mLastSEff[ii] = ton.eff;
}
switch (ton.effTyp)
{
case 1: // A
{
ton.effTyp = 0xF;
if (ton.eff == 0 || ton.eff > 0x1F)
{
ton.effTyp = 0;
ton.eff = 0;
}
}
break;
case 2: ton.effTyp = 0xB; break; // B
case 3: ton.effTyp = 0xD; break; // C
case 4: // D
{
if ((ton.eff & 0xF0) == 0) ton.effTyp = 0xA;
else if ((ton.eff & 0x0F) == 0) ton.effTyp = 0xA;
else if ((ton.eff & 0xF0) == 0xF0)
{
ton.effTyp = 0xE;
ton.eff = 0xB0 | (ton.eff & 15);
}
else if ((ton.eff & 0x0F) == 0x0F)
{
ton.effTyp = 0xE;
ton.eff = 0xA0 | (ton.eff >> 4);
}
else
{
ton.effTyp = 0xA;
if (ton.eff & 0x0F)
ton.eff &= 0x0F;
else
ton.eff &= 0xF0;
}
}
break;
case 5: // E
case 6: // F
{
if ((ton.eff & 0xF0) >= 0xE0)
{
if ((ton.eff & 0xF0) == 0xE0)
tmp = 0x21;
else
tmp = 0xE;
ton.eff &= 0x0F;
if (ton.effTyp == 0x05)
ton.eff |= 0x20;
else
ton.eff |= 0x10;
ton.effTyp = (uint8_t)tmp;
}
else
{
ton.effTyp = 7 - ton.effTyp;
}
}
break;
case 7: // G
{
// fix illegal slides (to new instruments)
if (ton.instr != 0 && ton.instr != s3mLastGInstr[ii])
ton.instr = s3mLastGInstr[ii];
ton.effTyp = 0x03;
}
break;
case 8: ton.effTyp = 0x04; break; // H
case 9: ton.effTyp = 0x1D; break; // I
case 10: ton.effTyp = 0x00; break; // J
case 11: ton.effTyp = 0x06; break; // K
case 12: ton.effTyp = 0x05; break; // L
case 15: ton.effTyp = 0x09; break; // O
case 17: ton.effTyp = 0x1B; break; // Q
case 18: ton.effTyp = 0x07; break; // R
case 19: // S
{
ton.effTyp = 0xE;
tmp = ton.eff >> 4;
ton.eff &= 0x0F;
if (tmp == 0x1) ton.eff |= 0x30;
else if (tmp == 0x2) ton.eff |= 0x50;
else if (tmp == 0x3) ton.eff |= 0x40;
else if (tmp == 0x4) ton.eff |= 0x70;
else if (tmp == 0x08)
{
ton.effTyp = 0x8;
ton.eff <<= 4;
}
else if (tmp == 0xB) ton.eff |= 0x60;
else if (tmp == 0xC) ton.eff |= 0xC0;
else if (tmp == 0xD) ton.eff |= 0xD0;
else if (tmp == 0xE) ton.eff |= 0xE0;
else if (tmp == 0xF) ton.eff |= 0xF0;
else
{
ton.effTyp = 0;
ton.eff = 0;
}
}
break;
case 20: // T
{
ton.effTyp = 0x0F;
if (ton.eff < 0x20)
{
ton.effTyp = 0;
ton.eff = 0;
}
}
break;
case 21: // U
{
if ((ton.eff & 0x0F) != 0)
{
ton.eff = (ton.eff & 0xF0) | (((ton.eff & 15) + 1) / 4);
if ((ton.eff & 0x0F) == 0)
{
illegalUxx = true;
ton.effTyp = 0;
ton.eff = 0;
}
else
{
illegalUxx = false;
ton.effTyp = 0x04;
}
}
else
{
if (!illegalUxx)
{
ton.effTyp = 0x04;
}
else
{
ton.effTyp = 0;
ton.eff = 0;
}
}
}
break;
case 22: ton.effTyp = 0x10; break; // V
default:
{
ton.effTyp = 0;
ton.eff = 0;
}
break;
}
}
if (ton.instr != 0 && ton.effTyp != 0x3)
s3mLastGInstr[ii] = ton.instr;
/* Remove any EDx with no note.
** SDx with no note in ST3 = does nothing
** EDx with no note in FT2 = still retriggers */
if (ton.effTyp == 0xE && (ton.eff & 0xF0) == 0xD0)
{
if (ton.ton == 0 || ton.ton == 97)
{
ton.effTyp = 0;
ton.eff = 0;
}
}
pattTmp[i][(kk * MAX_VOICES) + ii] = ton;
}
}
if (tmpPatternEmpty(i))
{
if (pattTmp[i] != NULL)
{
free(pattTmp[i]);
pattTmp[i] = NULL;
}
}
}
}
// *** SAMPLES ***
memcpy(ptnOfs, ha, 512);
for (i = 0; i < ai; i++)
{
fseek(f, ptnOfs[i] << 4, SEEK_SET);
if (fread(&h_S3MInstr, 1, sizeof (h_S3MInstr), f) != sizeof (h_S3MInstr))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
// trim off spaces at end of name
for (j = 21; j >= 0; j--)
{
if (h_S3MInstr.name[j] == ' ' || h_S3MInstr.name[j] == 0x1A)
h_S3MInstr.name[j] = '\0';
else
break;
}
memcpy(songTmp.instrName[1+i], h_S3MInstr.name, 22);
songTmp.instrName[1+i][22] = '\0';
if (h_S3MInstr.typ > 1)
{
okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
goto s3mLoadError;
}
else if (h_S3MInstr.typ == 1)
{
if ((h_S3MInstr.flags & (255-1-2-4)) != 0 || h_S3MInstr.pack != 0)
{
okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
goto s3mLoadError;
}
else if (h_S3MInstr.memSeg > 0 && h_S3MInstr.len > 0)
{
if (!allocateTmpInstr(1 + i))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
setNoEnvelope(instrTmp[1 + i]);
s = &instrTmp[1+i]->samp[0];
len = h_S3MInstr.len;
if ((h_S3MInstr.flags & 2) != 0) // stereo
{
stereoSamplesWarn = false;
len *= 2;
}
if ((h_S3MInstr.flags & 4) != 0) // 16-bit
len *= 2;
tmpSmp = (int8_t *)malloc(len + LOOP_FIX_LEN);
if (tmpSmp == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
memcpy(s->name, h_S3MInstr.name, 21);
tuneSample(s, h_S3MInstr.c2Spd);
s->len = h_S3MInstr.len;
s->vol = h_S3MInstr.vol;
s->repS = h_S3MInstr.repS;
s->repL = h_S3MInstr.repE - h_S3MInstr.repS;
// non-FT2: fixes "miracle man.s3m"
if ((h_S3MInstr.memSeg<<4)+s->len > (int32_t)dataLength)
s->len = dataLength - (h_S3MInstr.memSeg << 4);
if (s->repL <= 2 || s->repS+s->repL > s->len)
{
s->repS = 0;
s->repL = 0;
}
s->typ = (h_S3MInstr.flags & 1) + ((h_S3MInstr.flags & 4) << 2);
if (s->repL == 0)
s->typ &= 16; // turn off loop, keep 16-bit flag only
if (s->vol > 64)
s->vol = 64;
s->pan = 128;
fseek(f, h_S3MInstr.memSeg << 4, SEEK_SET);
// non-FT2: fixes "miracle man.s3m"
if ((h_S3MInstr.memSeg<<4)+len > (int32_t)dataLength)
len = dataLength - (h_S3MInstr.memSeg << 4);
if (fread(tmpSmp, len, 1, f) != 1)
{
free(tmpSmp);
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
goto s3mLoadError;
}
if (ver != 1)
{
if ((h_S3MInstr.flags & 4) != 0)
{
conv16BitSample(tmpSmp, len, h_S3MInstr.flags & 2);
s->pek = (int8_t *)malloc((h_S3MInstr.len * 2) + LOOP_FIX_LEN);
if (s->pek == NULL)
{
free(tmpSmp);
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
memcpy(s->pek, tmpSmp, h_S3MInstr.len * 2);
s->len *= 2;
s->repS *= 2;
s->repL *= 2;
}
else
{
conv8BitSample(tmpSmp, len, h_S3MInstr.flags & 2);
s->pek = (int8_t *)malloc(h_S3MInstr.len + LOOP_FIX_LEN);
if (s->pek == NULL)
{
free(tmpSmp);
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto s3mLoadError;
}
memcpy(s->pek, tmpSmp, h_S3MInstr.len);
}
fixSample(s);
}
free(tmpSmp);
}
}
}
if (stereoSamplesWarn)
okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");
// non-FT2: fix overflown 9xx and illegal 3xx slides
for (i = 0; i < ap; i++)
{
if (pattTmp[i] == NULL)
continue;
for (k = 0; k < songTmp.antChn; k++)
{
check3xx = false;
for (j = 0; j < 64; j++)
{
pattTon = &pattTmp[i][(j * MAX_VOICES) + k];
if (pattTon->ton > 0 && pattTon->ton < 97)
check3xx = pattTon->effTyp != 0x3;
if (check3xx && pattTon->effTyp == 0x3)
{
if (pattTon->ton == 0 || pattTon->ton == 97)
{
pattTon->effTyp = 0;
pattTon->eff = 0;
}
}
if (pattTon->effTyp == 0x9 && pattTon->eff > 0)
{
if (pattTon->instr != 0 && pattTon->instr <= ai)
{
s = &instrTmp[pattTon->instr]->samp[0];
len = s->len;
tmp8 = 0;
if (len > 0)
{
tmp8 = pattTon->eff;
if (tmp8 >= len/256)
{
if (len/256 < 1)
tmp8 = 0;
else
tmp8 = (uint8_t)((len/256) - 1);
}
}
if (tmp8 > 0)
{
pattTon->eff = tmp8;
}
else
{
pattTon->effTyp = 0;
pattTon->eff = 0;
}
}
else
{
pattTon->effTyp = 0;
pattTon->eff = 0;
}
}
}
}
}
fclose(f);
songTmp.antChn = countS3MChannels(ap);
if (!(config.dontShowAgainFlags & DONT_SHOW_S3M_LOAD_WARNING_FLAG))
okBoxThreadSafe(6, "System message", "Warning: S3M channel panning is not compatible with FT2!");
moduleLoaded = true;
return true;
s3mLoadError:
fclose(f);
freeTmpModule();
moduleFailedToLoad = true;
return false;
}
static int32_t SDLCALL loadMusicThread(void *ptr)
{
char tmpText[128];
int16_t k;
uint16_t i;
uint32_t filelength;
songHeaderTyp h;
FILE *f;
(void)ptr;
stereoSamplesWarn = false;
linearFreqTable = false;
if (editor.tmpFilenameU == NULL)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
moduleFailedToLoad = true;
return false;
}
f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb");
if (f == NULL)
{
okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
moduleFailedToLoad = true;
return false;
}
fseek(f, 0, SEEK_END);
filelength = ftell(f);
rewind(f);
// start loading
if (fread(&h, 1, sizeof (h), f) != sizeof (h))
return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m
if (memcmp(h.sig, "Extended Module: ", 17))
return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m
loadedFormat = FORMAT_XM;
if (h.ver < 0x0102 || h.ver > 0x0104)
{
fclose(f);
sprintf(tmpText, "Error loading .xm: Unsupported XM version (v%1d.%1d%1d)",
'0' + (((h.ver >> 8) & 0x0F) % 10), '0' + (((h.ver >> 4) & 0x0F)) % 10, '0' + ((h.ver & 0x0F)) % 10);
okBoxThreadSafe(0, "System message", tmpText);
moduleFailedToLoad = true;
return false;
}
if (h.len > MAX_ORDERS)
{
okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 orders!");
goto xmLoadError;
}
if (h.antPtn > MAX_PATTERNS)
{
okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 patterns!");
goto xmLoadError;
}
if (h.antChn == 0 || h.antChn > MAX_VOICES)
{
okBoxThreadSafe(0, "System message", "Error loading .xm: Incompatible amount of channels!");
goto xmLoadError;
}
if (h.antInstrs > MAX_INST)
okBoxThreadSafe(0, "System message", "This module has over 128 instruments! Only the first 128 will be loaded.");
fseek(f, 60 + h.headerSize, SEEK_SET);
if (filelength != 336 && feof(f)) // 336 in length at this point = empty XM
{
okBoxThreadSafe(0, "System message", "Error loading .xm: The module is empty!");
goto xmLoadError;
}
// trim off spaces at end of name
for (k = 19; k >= 0; k--)
{
if (h.name[k] == ' ' || h.name[k] == 0x1A)
h.name[k] = '\0';
else
break;
}
memcpy(songTmp.name, h.name, 20);
songTmp.name[20] = '\0';
songTmp.len = h.len;
songTmp.repS = h.repS;
songTmp.antChn = (uint8_t)h.antChn;
songTmp.speed = h.defSpeed ? h.defSpeed : 125;
songTmp.tempo = h.defTempo ? h.defTempo : 6;
songTmp.ver = h.ver;
linearFreqTable = h.flags & 1;
songTmp.speed = CLAMP(songTmp.speed, 32, 255);
if (songTmp.tempo > 31)
songTmp.tempo = 31;
songTmp.initialTempo = songTmp.tempo;
if (songTmp.globVol > 64)
songTmp.globVol = 64;
if (songTmp.len == 0)
songTmp.len = 1; // songTmp.songTab is already empty
else
memcpy(songTmp.songTab, h.songTab, songTmp.len);
if (songTmp.ver < 0x0104)
{
// old FT2 format
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrHeader(f, i))
{
okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
goto xmLoadError;
}
}
if (!loadPatterns(f, h.antPtn))
{
// error message is shown inside loadPattern()
goto xmLoadError;
}
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrSample(f, i))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto xmLoadError;
}
}
}
else
{
// current FT2 format
if (!loadPatterns(f, h.antPtn))
{
// error message is shown inside loadPattern()
goto xmLoadError;
}
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrHeader(f, i))
{
okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
goto xmLoadError;
}
if (!loadInstrSample(f, i))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
goto xmLoadError;
}
}
}
if (stereoSamplesWarn)
okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");
fclose(f);
moduleLoaded = true;
return true;
xmLoadError:
fclose(f);
freeTmpModule();
moduleFailedToLoad = true;
return false;
}
void loadMusic(UNICHAR *filenameU)
{
if (musicIsLoading)
return;
mouseAnimOn();
musicIsLoading = true;
moduleLoaded = false;
moduleFailedToLoad = false;
loadedFormat = FORMAT_NONE;
UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);
// clear deprecated pointers from possible last loading session (super important)
memset(pattTmp, 0, sizeof (pattTmp));
memset(instrTmp, 0, sizeof (instrTmp));
// prevent stuck instrument names from previous module
memset(&songTmp, 0, sizeof (songTmp));
for (uint32_t i = 0; i < MAX_PATTERNS; i++)
pattLensTmp[i] = 64;
thread = SDL_CreateThread(loadMusicThread, NULL, NULL);
if (thread == NULL)
{
editor.loadMusicEvent = EVENT_NONE;
okBox(0, "System message", "Couldn't create thread!");
musicIsLoading = false;
return;
}
SDL_DetachThread(thread);
}
bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing
{
if (editor.tmpFilenameU == NULL)
return false;
// clear deprecated pointers from possible last loading session (super important)
memset(pattTmp, 0, sizeof (pattTmp));
memset(instrTmp, 0, sizeof (instrTmp));
// prevent stuck instrument names from previous module
memset(&songTmp, 0, sizeof (songTmp));
for (uint32_t i = 0; i < MAX_PATTERNS; i++)
pattLensTmp[i] = 64;
UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);
loadMusicThread(NULL);
editor.loadMusicEvent = EVENT_NONE;
if (moduleLoaded)
{
setupLoadedModule();
return true;
}
return false;
}
static void freeTmpModule(void)
{
uint16_t i;
// free all patterns
for (i = 0; i < MAX_PATTERNS; i++)
{
if (pattTmp[i] != NULL)
{
free(pattTmp[i]);
pattTmp[i] = NULL;
}
}
// free all samples
for (i = 1; i <= MAX_INST; i++)
{
if (instrTmp[i] != NULL)
{
for (uint8_t j = 0; j < MAX_SMP_PER_INST; j++)
{
if (instrTmp[i]->samp[j].pek != NULL)
free(instrTmp[i]->samp[j].pek);
}
free(instrTmp[i]);
instrTmp[i] = NULL;
}
}
}
static bool loadInstrHeader(FILE *f, uint16_t i)
{
int8_t k;
uint8_t j;
uint32_t readSize;
instrHeaderTyp ih;
sampleTyp *s;
memset(&ih, 0, INSTR_HEADER_SIZE);
fread(&ih.instrSize, 4, 1, f);
readSize = ih.instrSize;
if (readSize < 4 || readSize > INSTR_HEADER_SIZE)
readSize = INSTR_HEADER_SIZE;
// load instrument data into temp buffer
fread(ih.name, readSize - 4, 1, f); // -4 = skip ih.instrSize
// FT2 bugfix: skip instrument header data if instrSize is above INSTR_HEADER_SIZE
if (ih.instrSize > INSTR_HEADER_SIZE)
fseek(f, ih.instrSize - INSTR_HEADER_SIZE, SEEK_CUR);
if (ih.antSamp > MAX_SMP_PER_INST)
return false;
if (i <= MAX_INST)
{
// trim off spaces at end of name
for (k = 21; k >= 0; k--)
{
if (ih.name[k] == ' ' || ih.name[k] == 0x1A)
ih.name[k] = '\0';
else
break;
}
memcpy(songTmp.instrName[i], ih.name, 22);
songTmp.instrName[i][22] = '\0';
}
if (ih.antSamp > 0)
{
if (i <= MAX_INST)
{
if (!allocateTmpInstr(i))
return false;
// sanitize stuff for broken/unsupported instruments
ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
ih.midiBend = CLAMP(ih.midiBend, 0, 36);
if (ih.midiChannel > 15) ih.midiChannel = 15;
if (ih.mute != 1) ih.mute = 0;
if (ih.midiOn != 1) ih.midiOn = 0;
if (ih.vibDepth > 0x0F) ih.vibDepth = 0x0F;
if (ih.vibRate > 0x3F) ih.vibRate = 0x3F;
if (ih.vibTyp > 3) ih.vibTyp = 0;
for (j = 0; j < 96; j++)
{
if (ih.ta[j] > 15)
ih.ta[j] = 15;
}
if (ih.envVPAnt > 12) ih.envVPAnt = 12;
if (ih.envVRepS > 11) ih.envVRepS = 11;
if (ih.envVRepE > 11) ih.envVRepE = 11;
if (ih.envVSust > 11) ih.envVSust = 11;
if (ih.envPPAnt > 12) ih.envPPAnt = 12;
if (ih.envPRepS > 11) ih.envPRepS = 11;
if (ih.envPRepE > 11) ih.envPRepE = 11;
if (ih.envPSust > 11) ih.envPSust = 11;
for (j = 0; j < 12; j++)
{
if ((uint16_t)ih.envVP[j][0] > 32767) ih.envVP[j][0] = 32767;
if ((uint16_t)ih.envPP[j][0] > 32767) ih.envPP[j][0] = 32767;
if ((uint16_t)ih.envVP[j][1] > 64) ih.envVP[j][1] = 64;
if ((uint16_t)ih.envPP[j][1] > 63) ih.envPP[j][1] = 63;
}
// ----------------------------------------
// copy over final instrument data from temp buffer
memcpy(instrTmp[i], ih.ta, INSTR_SIZE);
instrTmp[i]->antSamp = ih.antSamp;
}
if (fread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
return false;
if (i <= MAX_INST)
{
for (j = 0; j < ih.antSamp; j++)
{
s = &instrTmp[i]->samp[j];
memcpy(s, &ih.samp[j], 12+4+24);
// s->pek is set up later
// trim off spaces at end of name
for (k = 21; k >= 0; k--)
{
if (s->name[k] == ' ' || s->name[k] == 0x1A)
s->name[k] = '\0';
else
break;
}
// sanitize stuff for malicious modules
if (s->vol > 64)
s->vol = 64;
s->relTon = CLAMP(s->relTon, -48, 71);
}
}
}
return true;
}
static void checkSampleRepeat(sampleTyp *s)
{
if (s->repS < 0) s->repS = 0;
if (s->repL < 0) s->repL = 0;
if (s->repS > s->len) s->repS = s->len;
if (s->repS+s->repL > s->len) s->repL = s->len - s->repS;
if (s->repL == 0) s->typ &= ~3; // non-FT2 fix: force loop off if looplen is 0
}
static bool loadInstrSample(FILE *f, uint16_t i)
{
int8_t *newPtr;
uint16_t j, k;
int32_t l, bytesToSkip;
sampleTyp *s;
if (i > MAX_INST || instrTmp[i] == NULL)
return true; // yes, let's just pretend they got loaded
k = instrTmp[i]->antSamp;
for (j = 0; j < k; j++)
{
s = &instrTmp[i]->samp[j];
// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior)
if ((s->typ & 3) == 3)
s->typ &= 0xFE;
l = s->len;
if (l <= 0)
{
s->pek = NULL;
s->len = 0;
s->repL = 0;
s->repS = 0;
if (s->typ & 32)
s->typ &= ~32; // remove stereo flag
}
else
{
bytesToSkip = 0;
if (l > MAX_SAMPLE_LEN)
{
bytesToSkip = l - MAX_SAMPLE_LEN;
l = MAX_SAMPLE_LEN;
}
s->pek = (int8_t *)malloc(l + LOOP_FIX_LEN);
if (s->pek == NULL)
return false;
if (fread(s->pek, l, 1, f) != 1)
return false;
if (bytesToSkip > 0)
fseek(f, bytesToSkip, SEEK_CUR);
delta2Samp(s->pek, l, s->typ);
if (s->typ & 32) // stereo sample - already downmixed to mono in delta2samp()
{
s->typ &= ~32; // remove stereo flag
s->len /= 2;
s->repL /= 2;
s->repS /= 2;
newPtr = (int8_t *)realloc(s->pek, s->len + LOOP_FIX_LEN);
if (newPtr != NULL)
s->pek = newPtr;
stereoSamplesWarn = true;
}
}
// NON-FT2 FIX: Align to 2-byte if 16-bit sample
if (s->typ & 16)
{
s->repL &= 0xFFFFFFFE;
s->repS &= 0xFFFFFFFE;
s->len &= 0xFFFFFFFE;
}
checkSampleRepeat(s);
fixSample(s);
}
return true;
}
void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn)
{
uint8_t note, data, *src;
int32_t srcEnd, srcIdx;
if (dst == NULL)
return;
src = dst + inn;
srcEnd = len * TRACK_WIDTH;
srcIdx = 0;
for (int32_t i = 0; i < len; i++)
{
for (int32_t j = 0; j < antChn; j++)
{
if (srcIdx >= srcEnd)
return; // error!
note = *src++;
if (note & 0x80)
{
data = 0; if (note & 0x01) data = *src++; *dst++ = data;
data = 0; if (note & 0x02) data = *src++; *dst++ = data;
data = 0; if (note & 0x04) data = *src++; *dst++ = data;
data = 0; if (note & 0x08) data = *src++; *dst++ = data;
data = 0; if (note & 0x10) data = *src++; *dst++ = data;
}
else
{
*dst++ = note;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
}
// if note is overflowing (>97), remove it
if (*(dst-5) > 97)
*(dst-5) = 0;
// non-FT2 security fix: if effect is above 35 (Z), clear effect and parameter
if (*(dst-2) > 35)
{
*(dst-2) = 0;
*(dst-1) = 0;
}
srcIdx += sizeof (tonTyp);
}
// skip unused channels
dst += sizeof (tonTyp) * (MAX_VOICES - antChn);
}
}
static bool tmpPatternEmpty(uint16_t nr)
{
uint8_t *scanPtr;
uint32_t scanLen;
if (pattTmp[nr] == NULL)
return true;
scanPtr = (uint8_t *)pattTmp[nr];
scanLen = pattLensTmp[nr] * TRACK_WIDTH;
for (uint32_t i = 0; i < scanLen; i++)
{
if (scanPtr[i] != 0)
return false;
}
return true;
}
void clearUnusedChannels(tonTyp *p, int16_t pattLen, uint8_t antChn)
{
if (p == NULL || antChn >= MAX_VOICES)
return;
for (int32_t i = 0; i < pattLen; i++)
memset(&p[(i * MAX_VOICES) + antChn], 0, sizeof (tonTyp) * (MAX_VOICES - antChn));
}
static bool loadPatterns(FILE *f, uint16_t antPtn)
{
bool pattLenWarn;
uint8_t tmpLen, *pattPtr;
uint16_t i, a;
patternHeaderTyp ph;
pattLenWarn = false;
for (i = 0; i < antPtn; i++)
{
if (fread(&ph.patternHeaderSize, 4, 1, f) != 1)
goto pattCorrupt;
if (fread(&ph.typ, 1, 1, f) != 1)
goto pattCorrupt;
ph.pattLen = 0;
if (songTmp.ver == 0x0102)
{
if (fread(&tmpLen, 1, 1, f) != 1)
goto pattCorrupt;
if (fread(&ph.dataLen, 2, 1, f) != 1)
goto pattCorrupt;
ph.pattLen = tmpLen + 1; // +1 in v1.02
if (ph.patternHeaderSize > 8)
fseek(f, ph.patternHeaderSize - 8, SEEK_CUR);
}
else
{
if (fread(&ph.pattLen, 2, 1, f) != 1)
goto pattCorrupt;
if (fread(&ph.dataLen, 2, 1, f) != 1)
goto pattCorrupt;
if (ph.patternHeaderSize > 9)
fseek(f, ph.patternHeaderSize - 9, SEEK_CUR);
}
if (feof(f))
goto pattCorrupt;
pattLensTmp[i] = ph.pattLen;
if (ph.dataLen > 0)
{
pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
if (pattTmp[i] == NULL)
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return false;
}
a = ph.pattLen * TRACK_WIDTH;
pattPtr = (uint8_t *)pattTmp[i];
memset(pattPtr, 0, a);
if (fread(&pattPtr[a - ph.dataLen], 1, ph.dataLen, f) != ph.dataLen)
goto pattCorrupt;
unpackPatt(pattPtr, a - ph.dataLen, ph.pattLen, songTmp.antChn);
clearUnusedChannels(pattTmp[i], pattLensTmp[i], songTmp.antChn);
}
if (tmpPatternEmpty(i))
{
if (pattTmp[i] != NULL)
{
free(pattTmp[i]);
pattTmp[i] = NULL;
}
pattLensTmp[i] = 64;
}
if (pattLensTmp[i] > 256)
{
pattLensTmp[i] = 64;
pattLenWarn = true;
}
}
if (pattLenWarn)
okBoxThreadSafe(0, "System message", "The module contains pattern lengths above 256! They will be set to 64.");
return true;
pattCorrupt:
okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
return false;
}
// called from input/video thread after the module was done loading
static void setupLoadedModule(void)
{
int16_t i;
lockMixerCallback();
freeAllInstr();
freeAllPatterns();
oldPlayMode = playMode;
playMode = PLAYMODE_IDLE;
songPlaying = false;
editor.currVolEnvPoint = 0;
editor.currPanEnvPoint = 0;
#ifdef HAS_MIDI
midi.currMIDIVibDepth = 0;
midi.currMIDIPitch = 0;
#endif
memset(editor.keyOnTab, 0, sizeof (editor.keyOnTab));
// copy over new pattern pointers and lengths
for (i = 0; i < MAX_PATTERNS; i++)
{
patt[i] = pattTmp[i];
pattLens[i] = pattLensTmp[i];
}
// copy over new instruments (includes sample pointers)
for (i = 1; i <= MAX_INST; i++)
{
instr[i] = instrTmp[i];
fixSampleName(i);
}
// copy over song struct
memcpy(&song, &songTmp, sizeof (songTyp));
fixSongName();
// we are the owners of the allocated memory ptrs set by the loader thread now
// support non-even channel numbers
if (song.antChn & 1)
{
if (++song.antChn > MAX_VOICES)
song.antChn = MAX_VOICES;
}
if (song.repS > song.len)
song.repS = 0;
song.globVol = 64;
song.timer = 1;
setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
setScrollBarPos(SB_POS_ED, 0, false);
resetChannels();
refreshScopes();
setPos(0, 0, false);
setSpeed(song.speed);
editor.tmpPattern = editor.editPattern; // set kludge variable
editor.speed = song.speed;
editor.tempo = song.tempo;
editor.timer = 1;
editor.globalVol = song.globVol;
setFrqTab((loadedFormat == FORMAT_XM) ? linearFreqTable : false);
unlockMixerCallback();
exitTextEditing();
updateTextBoxPointers();
resetChannelOffset();
updateChanNums();
resetWavRenderer();
clearPattMark();
song.musicTime = 0;
resetTrimSizes();
diskOpSetFilename(DISKOP_ITEM_MODULE, editor.tmpFilenameU);
// redraw top part of screen
if (editor.ui.extended)
{
// first exit extended mode, then re-enter
togglePatternEditorExtended();
togglePatternEditorExtended();
}
else
{
// redraw top screen
hideTopScreen();
showTopScreen(true);
}
updateSampleEditorSample();
showBottomScreen(); // redraw bottom screen (also redraws pattern editor)
if (editor.ui.instEditorShown)
drawPiano(); // redraw piano now (since if playing = wait for next tick update)
removeSongModifiedFlag();
moduleFailedToLoad = false;
moduleLoaded = false;
editor.loadMusicEvent = EVENT_NONE;
}
bool handleModuleLoadFromArg(int argc, char **argv)
{
int32_t filesize;
uint32_t filenameLen;
UNICHAR *filenameU, tmpPathU[PATH_MAX + 2];
// this is crude, we always expect only one parameter, and that it is the module.
if (argc != 2)
return false;
#ifdef __APPLE__
if (argc == 2 && !strncmp(argv[1], "-psn_", 5))
return false; // OS X < 10.9 passes a -psn_x_xxxxx parameter on double-click launch
#endif
filenameLen = (uint32_t)strlen(argv[1]);
filenameU = (UNICHAR *)calloc((filenameLen + 1), sizeof (UNICHAR));
if (filenameU == NULL)
{
okBox(0, "System message", "Not enough memory!");
return false;
}
#ifdef _WIN32
MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, filenameU, filenameLen);
#else
strcpy(filenameU, argv[1]);
#endif
// store old path
UNICHAR_GETCWD(tmpPathU, PATH_MAX);
// set binary path
UNICHAR_CHDIR(editor.binaryPathU);
filesize = getFileSize(filenameU);
if (filesize == -1) // >2GB
{
okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
goto argLoadErr;
}
if (filesize >= 128L*1024*1024) // 128MB
{
if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1)
goto argLoadErr;
}
editor.loadMusicEvent = EVENT_LOADMUSIC_ARGV;
loadMusic(filenameU);
UNICHAR_CHDIR(tmpPathU); // set old path back
free(filenameU);
return true;
argLoadErr:
UNICHAR_CHDIR(tmpPathU); // set old path back
free(filenameU);
return false;
}
void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck)
{
int32_t fullPathLen, filesize;
UNICHAR *fullPathU;
if (editor.ui.sysReqShown || fullPathUTF8 == NULL)
return;
fullPathLen = (int32_t)strlen(fullPathUTF8);
if (fullPathLen == 0)
return;
fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR));
if (fullPathU == NULL)
{
okBox(0, "System message", "Not enough memory!");
return;
}
#ifdef _WIN32
MultiByteToWideChar(CP_UTF8, 0, fullPathUTF8, -1, fullPathU, fullPathLen);
#else
strcpy(fullPathU, fullPathUTF8);
#endif
filesize = getFileSize(fullPathU);
if (filesize == -1) // >2GB
{
okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
free(fullPathU);
return;
}
if (filesize >= 128L*1024*1024) // 128MB
{
if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1)
{
free(fullPathU);
return;
}
}
// pass UTF8 to these tests so that we can test file ending in ASCII/ANSI
if (fileIsInstrument(fullPathUTF8))
{
loadInstr(fullPathU);
}
else if (fileIsSample(fullPathUTF8))
{
loadSample(fullPathU, editor.curSmp, false);
}
else
{
SDL_RestoreWindow(video.window);
if (songModifiedCheck && song.isModified)
{
// de-minimize window and set focus so that the user sees the message box
SDL_RestoreWindow(video.window);
SDL_RaiseWindow(video.window);
if (!askUnsavedChanges(ASK_TYPE_LOAD_SONG))
{
free(fullPathU);
return;
}
}
editor.loadMusicEvent = EVENT_LOADMUSIC_DRAGNDROP;
loadMusic(fullPathU);
}
free(fullPathU);
}
static void handleOldPlayMode(void)
{
playMode = oldPlayMode;
if (oldPlayMode != PLAYMODE_IDLE && oldPlayMode != PLAYMODE_EDIT)
startPlaying(oldPlayMode, 0);
songPlaying = (playMode >= PLAYMODE_SONG);
}
// called from input/video thread after module load thread was finished
void handleLoadMusicEvents(void)
{
if (!moduleLoaded && !moduleFailedToLoad)
return; // no event to handle
if (moduleFailedToLoad)
{
// module failed to load from loading thread
musicIsLoading = false;
moduleFailedToLoad = false;
moduleLoaded = false;
editor.loadMusicEvent = EVENT_NONE;
setMouseBusy(false);
return;
}
if (moduleLoaded)
{
// module was successfully loaded from loading thread
switch (editor.loadMusicEvent)
{
// module dragged and dropped *OR* user double clicked a file associated with FT2 clone
case EVENT_LOADMUSIC_DRAGNDROP:
{
setupLoadedModule();
if (editor.autoPlayOnDrop)
startPlaying(PLAYMODE_SONG, 0);
else
handleOldPlayMode();
}
break;
// filename passed as an exe argument *OR* user double clicked a file associated with FT2 clone
case EVENT_LOADMUSIC_ARGV:
{
setupLoadedModule();
startPlaying(PLAYMODE_SONG, 0);
}
break;
// module filename pressed in Disk Op.
case EVENT_LOADMUSIC_DISKOP:
{
setupLoadedModule();
handleOldPlayMode();
}
break;
default: break;
}
moduleLoaded = false;
editor.loadMusicEvent = EVENT_NONE;
musicIsLoading = false;
mouseAnimOff();
}
}