// 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 "scopes/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_midi.h"#include "ft2_events.h"#include "ft2_video.h"#include "ft2_structs.h"#include "ft2_sysreqs.h" bool loadDIGI(FILE *f, uint32_t filesize);bool loadMOD(FILE *f, uint32_t filesize);bool loadS3M(FILE *f, uint32_t filesize);bool loadSTK(FILE *f, uint32_t filesize);bool loadSTM(FILE *f, uint32_t filesize);bool loadXM(FILE *f, uint32_t filesize); enum{ FORMAT_UNKNOWN = 0, FORMAT_POSSIBLY_STK = 1, FORMAT_XM = 2, FORMAT_MOD = 3, FORMAT_S3M = 4, FORMAT_STM = 5, FORMAT_DIGI = 6}; // file extensions accepted by Disk Op. in module modechar *supportedModExtensions[] ={ "xm", "ft", "nst", "stk", "mod", "s3m", "stm", "fst", "digi", // IMPORTANT: Remember comma after last entry above "END_OF_LIST" // do NOT move, remove or edit this line!}; // globals for module loadersvolatile bool tmpLinearPeriodsFlag;int16_t patternNumRowsTmp[MAX_PATTERNS];note_t *patternTmp[MAX_PATTERNS];instr_t *instrTmp[1+256];song_t songTmp;// -------------------------- static volatile bool musicIsLoading, moduleLoaded, moduleFailedToLoad;static SDL_Thread *thread;static uint8_t oldPlayMode;static void setupLoadedModule(void);static void freeTmpModule(void); // Crude module detection routine. These aren't always accurate detections!static int8_t detectModule(FILE *f){ uint8_t D[256], I[4]; fseek(f, 0, SEEK_END); uint32_t fileLength = (uint32_t)ftell(f); rewind(f); memset(D, 0, sizeof (D)); fread(D, 1, sizeof (D), f); fseek(f, 1080, SEEK_SET); // MOD ID I[0] = I[1] = I[2] = I[3] = 0; fread(I, 1, 4, f); rewind(f); // DIGI Booster (non-Pro) if (!memcmp("DIGI Booster module", &D[0x00], 19+1) && D[0x19] >= 1 && D[0x19] <= 8) return FORMAT_DIGI; // Scream Tracker 3 S3M (and compatible trackers) if (!memcmp("SCRM", &D[0x2C], 4) && D[0x1D] == 16) // XXX: byte=16 in all cases? return FORMAT_S3M; // Scream Tracker 2 STM if ((!memcmp("!Scream!", &D[0x14], 8) || !memcmp("BMOD2STM", &D[0x14], 8) || !memcmp("WUZAMOD!", &D[0x14], 8) || !memcmp("SWavePro", &D[0x14], 8)) && D[0x1D] == 2) // XXX: byte=2 for "WUZAMOD!"/"SWavePro" ? { return FORMAT_STM; } // Generic multi-channel MOD (1..9 channels) if (isdigit(I[0]) && I[0] != '0' && I[1] == 'C' && I[2] == 'H' && I[3] == 'N') // xCHN return FORMAT_MOD; // Digital Tracker (Atari Falcon) if (I[0] == 'F' && I[1] == 'A' && I[2] == '0' && I[3] >= '4' && I[3] <= '8') // FA0x (x=4..8) return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'H') // xxCH return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'N') // xxCN (same as xxCH) return FORMAT_MOD; // ProTracker and generic MOD formats if (!memcmp("M.K.", I, 4) || !memcmp("M!K!", I, 4) || !memcmp("NSMS", I, 4) || !memcmp("LARD", I, 4) || !memcmp("PATT", I, 4) || !memcmp("FLT4", I, 4) || !memcmp("FLT8", I, 4) || !memcmp("EXO4", I, 4) || !memcmp("EXO8", I, 4) || !memcmp("N.T.", I, 4) || !memcmp("M&K!", I, 4) || !memcmp("FEST", I, 4) || !memcmp("CD61", I, 4) || !memcmp("CD81", I, 4) || !memcmp("OKTA", I, 4) || !memcmp("OCTA", I, 4)) { return FORMAT_MOD; } /* Check if the file is a .it module (Impulse Tracker, not supported). ** Some people may attempt to load .IT files in the FT2 clone, so ** reject them here instead of accidentally loading them as .STK */ if (!memcmp("IMPM", D, 4) && D[0x16] == 0) return FORMAT_UNKNOWN; /* Fasttracker II XM and compatible trackers. ** Note: This test can falsely be true for STK modules (and non-supported files) where the ** first 17 bytes start with "Extended Module: ". This is unlikely to happen. */ if (!memcmp("Extended Module: ", &D[0x00], 17)) return FORMAT_XM; /* Lastly, we assume that the file is either a 15-sample STK or an unsupported file. ** Let's assume it's an STK and do some sanity checks. If they fail, we have an ** unsupported file. */ // minimum and maximum (?) possible size for a supported STK if (fileLength < 1624 || fileLength > 984634) return FORMAT_UNKNOWN; // test STK numOrders+BPM for illegal values fseek(f, 470, SEEK_SET); D[0] = D[1] = 0; fread(D, 1, 2, f); rewind(f); if (D[0] <= 128 && D[1] <= 220) return FORMAT_POSSIBLY_STK; return FORMAT_UNKNOWN;} static bool doLoadMusic(bool externalThreadFlag){ // setup message box functions loaderMsgBox = externalThreadFlag ? myLoaderMsgBoxThreadSafe : myLoaderMsgBox; loaderSysReq = externalThreadFlag ? okBoxThreadSafe : okBox; if (editor.tmpFilenameU == NULL) { loaderMsgBox("Generic memory fault during loading!"); goto loadError; } FILE *f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb"); if (f == NULL) { loaderMsgBox("General I/O error during loading! Is the file in use? Does it exist?"); goto loadError; } int8_t format = detectModule(f); fseek(f, 0, SEEK_END); uint32_t filesize = ftell(f); rewind(f); switch (format) { case FORMAT_XM: moduleLoaded = loadXM(f, filesize); break; case FORMAT_S3M: moduleLoaded = loadS3M(f, filesize); break; case FORMAT_STM: moduleLoaded = loadSTM(f, filesize); break; case FORMAT_MOD: moduleLoaded = loadMOD(f, filesize); break; case FORMAT_POSSIBLY_STK: moduleLoaded = loadSTK(f, filesize); break; case FORMAT_DIGI: moduleLoaded = loadDIGI(f, filesize); break; default: loaderMsgBox("This file is not a supported module!"); break; } fclose(f); if (!moduleLoaded) goto loadError; moduleLoaded = true; return true; loadError: freeTmpModule(); moduleFailedToLoad = true; return false;} static void clearTmpModule(void){ memset(patternTmp, 0, sizeof (patternTmp)); memset(instrTmp, 0, sizeof (instrTmp)); memset(&songTmp, 0, sizeof (songTmp)); for (uint32_t i = 0; i < MAX_PATTERNS; i++) patternNumRowsTmp[i] = 64;} static int32_t SDLCALL loadMusicThread(void *ptr){ return doLoadMusic(true); (void)ptr;} void loadMusic(UNICHAR *filenameU){ if (musicIsLoading || filenameU == NULL) return; mouseAnimOn(); musicIsLoading = true; moduleLoaded = false; moduleFailedToLoad = false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); 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, bool autoPlay){ if (filenameU == NULL) return false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); editor.loadMusicEvent = EVENT_NONE; doLoadMusic(false); if (moduleLoaded) { setupLoadedModule(); if (autoPlay) startPlaying(PLAYMODE_SONG, 0); return true; } return false;} bool allocateTmpPatt(int32_t pattNum, uint16_t numRows){ patternTmp[pattNum] = (note_t *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (patternTmp[pattNum] == NULL) return false; patternNumRowsTmp[pattNum] = numRows; return true;} bool allocateTmpInstr(int16_t insNum){ if (instrTmp[insNum] != NULL) return false; // already allocated instr_t *ins = (instr_t *)calloc(1, sizeof (instr_t)); if (ins == NULL) return false; sample_t *s = ins->smp; for (int32_t i = 0; i < MAX_SMP_PER_INST; i++, s++) { s->panning = 128; s->volume = 64; } instrTmp[insNum] = ins; return true;} static void freeTmpModule(void) // called on module load error{ // free all patterns for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (patternTmp[i] != NULL) { free(patternTmp[i]); patternTmp[i] = NULL; } } // free all instruments and samples for (int32_t i = 1; i <= 256; i++) // if >128 instruments, we fake-load up to 128 extra (and discard them later) { if (instrTmp[i] == NULL) continue; sample_t *s = instrTmp[i]->smp; for (int32_t j = 0; j < MAX_SMP_PER_INST; j++, s++) freeSmpData(s); free(instrTmp[i]); instrTmp[i] = NULL; }} bool tmpPatternEmpty(uint16_t pattNum){ if (patternTmp[pattNum] == NULL) return true; uint8_t *scanPtr = (uint8_t *)patternTmp[pattNum]; const uint32_t scanLen = patternNumRowsTmp[pattNum] * TRACK_WIDTH; for (uint32_t i = 0; i < scanLen; i++) { if (scanPtr[i] != 0) return false; } return true;} void clearUnusedChannels(note_t *pattPtr, int16_t numRows, int32_t numChannels){ if (pattPtr == NULL || numChannels >= MAX_CHANNELS) return; const int32_t width = sizeof (note_t) * (MAX_CHANNELS - numChannels); note_t *p = &pattPtr[numChannels]; for (int32_t i = 0; i < numRows; i++, p += MAX_CHANNELS) memset(p, 0, width);} // called from input/video thread after the module was done loadingstatic void setupLoadedModule(void){ lockMixerCallback(); freeAllInstr(); freeAllPatterns(); oldPlayMode = playMode; playMode = PLAYMODE_IDLE; songPlaying = false; #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 (int32_t i = 0; i < MAX_PATTERNS; i++) { pattern[i] = patternTmp[i]; patternNumRows[i] = patternNumRowsTmp[i]; } // copy over song struct memcpy(&song, &songTmp, sizeof (song_t)); fixSongName(); // copy over new instruments (includes sample pointers) for (int16_t i = 1; i <= MAX_INST; i++) { instr[i] = instrTmp[i]; fixInstrAndSampleNames(i); if (instr[i] != NULL) { sanitizeInstrument(instr[i]); for (int32_t j = 0; j < MAX_SMP_PER_INST; j++) { sample_t *s = &instr[i]->smp[j]; sanitizeSample(s); if (s->dataPtr != NULL) fixSample(s); // prepare sample for branchless linear interpolation } } } // we are the owners of the allocated memory ptrs set by the loader thread now // support non-even channel numbers if (song.numChannels & 1) { song.numChannels++; if (song.numChannels > MAX_CHANNELS) song.numChannels = MAX_CHANNELS; } song.numChannels = CLAMP(song.numChannels, 2, MAX_CHANNELS); song.songLength = CLAMP(song.songLength, 1, MAX_ORDERS); song.BPM = CLAMP(song.BPM, MIN_BPM, MAX_BPM); song.initialSpeed = song.speed = CLAMP(song.speed, 1, MAX_SPEED); if (song.songLoopStart >= song.songLength) song.songLoopStart = 0; song.globalVolume = 64; // remove overflown stuff in pattern data (FT2 doesn't do this) for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (patternNumRows[i] <= 0) patternNumRows[i] = 64; if (patternNumRows[i] > MAX_PATT_LEN) patternNumRows[i] = MAX_PATT_LEN; if (pattern[i] == NULL) continue; note_t *p = pattern[i]; for (int32_t j = 0; j < MAX_PATT_LEN * MAX_CHANNELS; j++, p++) { if (p->note > 97) p->note = 0; if (p->instr > 128) p->instr = 0; if (p->efx > 35) { p->efx = 0; p->efxData = 0; } } } setScrollBarEnd(SB_POS_ED, (song.songLength - 1) + 5); setScrollBarPos(SB_POS_ED, 0, false); resetChannels(); setPos(0, 0, true); setMixerBPM(song.BPM); editor.tmpPattern = editor.editPattern; // set kludge variable editor.BPM = song.BPM; editor.speed = song.speed; editor.tick = song.tick; editor.globalVolume = song.globalVolume; setLinearPeriods(tmpLinearPeriodsFlag); unlockMixerCallback(); editor.currVolEnvPoint = 0; editor.currPanEnvPoint = 0; refreshScopes(); exitTextEditing(); updateTextBoxPointers(); resetChannelOffset(); updateChanNums(); resetWavRenderer(); clearPattMark(); resetTrimSizes(); resetPlaybackTime(); diskOpSetFilename(DISKOP_ITEM_MODULE, editor.tmpFilenameU); // redraw top part of screen if (ui.extended) { togglePatternEditorExtended(); // exitLines 1–500 of 790. Load more
// 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 "scopes/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_midi.h" #include "ft2_events.h" #include "ft2_video.h" #include "ft2_structs.h" #include "ft2_sysreqs.h" bool loadDIGI(FILE *f, uint32_t filesize); bool loadMOD(FILE *f, uint32_t filesize); bool loadS3M(FILE *f, uint32_t filesize); bool loadSTK(FILE *f, uint32_t filesize); bool loadSTM(FILE *f, uint32_t filesize); bool loadXM(FILE *f, uint32_t filesize); enum { FORMAT_UNKNOWN = 0, FORMAT_POSSIBLY_STK = 1, FORMAT_XM = 2, FORMAT_MOD = 3, FORMAT_S3M = 4, FORMAT_STM = 5, FORMAT_DIGI = 6 }; // file extensions accepted by Disk Op. in module mode char *supportedModExtensions[] = { "xm", "ft", "nst", "stk", "mod", "s3m", "stm", "fst", "digi", // IMPORTANT: Remember comma after last entry above "END_OF_LIST" // do NOT move, remove or edit this line! }; // globals for module loaders volatile bool tmpLinearPeriodsFlag; int16_t patternNumRowsTmp[MAX_PATTERNS]; note_t *patternTmp[MAX_PATTERNS]; instr_t *instrTmp[1+256]; song_t songTmp; // -------------------------- static volatile bool musicIsLoading, moduleLoaded, moduleFailedToLoad; static SDL_Thread *thread; static uint8_t oldPlayMode; static void setupLoadedModule(void); static void freeTmpModule(void); // Crude module detection routine. These aren't always accurate detections! static int8_t detectModule(FILE *f) { uint8_t D[256], I[4]; fseek(f, 0, SEEK_END); uint32_t fileLength = (uint32_t)ftell(f); rewind(f); memset(D, 0, sizeof (D)); fread(D, 1, sizeof (D), f); fseek(f, 1080, SEEK_SET); // MOD ID I[0] = I[1] = I[2] = I[3] = 0; fread(I, 1, 4, f); rewind(f); // DIGI Booster (non-Pro) if (!memcmp("DIGI Booster module", &D[0x00], 19+1) && D[0x19] >= 1 && D[0x19] <= 8) return FORMAT_DIGI; // Scream Tracker 3 S3M (and compatible trackers) if (!memcmp("SCRM", &D[0x2C], 4) && D[0x1D] == 16) // XXX: byte=16 in all cases? return FORMAT_S3M; // Scream Tracker 2 STM if ((!memcmp("!Scream!", &D[0x14], 8) || !memcmp("BMOD2STM", &D[0x14], 8) || !memcmp("WUZAMOD!", &D[0x14], 8) || !memcmp("SWavePro", &D[0x14], 8)) && D[0x1D] == 2) // XXX: byte=2 for "WUZAMOD!"/"SWavePro" ? { return FORMAT_STM; } // Generic multi-channel MOD (1..9 channels) if (isdigit(I[0]) && I[0] != '0' && I[1] == 'C' && I[2] == 'H' && I[3] == 'N') // xCHN return FORMAT_MOD; // Digital Tracker (Atari Falcon) if (I[0] == 'F' && I[1] == 'A' && I[2] == '0' && I[3] >= '4' && I[3] <= '8') // FA0x (x=4..8) return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'H') // xxCH return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'N') // xxCN (same as xxCH) return FORMAT_MOD; // ProTracker and generic MOD formats if (!memcmp("M.K.", I, 4) || !memcmp("M!K!", I, 4) || !memcmp("NSMS", I, 4) || !memcmp("LARD", I, 4) || !memcmp("PATT", I, 4) || !memcmp("FLT4", I, 4) || !memcmp("FLT8", I, 4) || !memcmp("EXO4", I, 4) || !memcmp("EXO8", I, 4) || !memcmp("N.T.", I, 4) || !memcmp("M&K!", I, 4) || !memcmp("FEST", I, 4) || !memcmp("CD61", I, 4) || !memcmp("CD81", I, 4) || !memcmp("OKTA", I, 4) || !memcmp("OCTA", I, 4)) { return FORMAT_MOD; } /* Check if the file is a .it module (Impulse Tracker, not supported). ** Some people may attempt to load .IT files in the FT2 clone, so ** reject them here instead of accidentally loading them as .STK */ if (!memcmp("IMPM", D, 4) && D[0x16] == 0) return FORMAT_UNKNOWN; /* Fasttracker II XM and compatible trackers. ** Note: This test can falsely be true for STK modules (and non-supported files) where the ** first 17 bytes start with "Extended Module: ". This is unlikely to happen. */ if (!memcmp("Extended Module: ", &D[0x00], 17)) return FORMAT_XM; /* Lastly, we assume that the file is either a 15-sample STK or an unsupported file. ** Let's assume it's an STK and do some sanity checks. If they fail, we have an ** unsupported file. */ // minimum and maximum (?) possible size for a supported STK if (fileLength < 1624 || fileLength > 984634) return FORMAT_UNKNOWN; // test STK numOrders+BPM for illegal values fseek(f, 470, SEEK_SET); D[0] = D[1] = 0; fread(D, 1, 2, f); rewind(f); if (D[0] <= 128 && D[1] <= 220) return FORMAT_POSSIBLY_STK; return FORMAT_UNKNOWN; } static bool doLoadMusic(bool externalThreadFlag) { // setup message box functions loaderMsgBox = externalThreadFlag ? myLoaderMsgBoxThreadSafe : myLoaderMsgBox; loaderSysReq = externalThreadFlag ? okBoxThreadSafe : okBox; if (editor.tmpFilenameU == NULL) { loaderMsgBox("Generic memory fault during loading!"); goto loadError; } FILE *f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb"); if (f == NULL) { loaderMsgBox("General I/O error during loading! Is the file in use? Does it exist?"); goto loadError; } int8_t format = detectModule(f); fseek(f, 0, SEEK_END); uint32_t filesize = ftell(f); rewind(f); switch (format) { case FORMAT_XM: moduleLoaded = loadXM(f, filesize); break; case FORMAT_S3M: moduleLoaded = loadS3M(f, filesize); break; case FORMAT_STM: moduleLoaded = loadSTM(f, filesize); break; case FORMAT_MOD: moduleLoaded = loadMOD(f, filesize); break; case FORMAT_POSSIBLY_STK: moduleLoaded = loadSTK(f, filesize); break; case FORMAT_DIGI: moduleLoaded = loadDIGI(f, filesize); break; default: loaderMsgBox("This file is not a supported module!"); break; } fclose(f); if (!moduleLoaded) goto loadError; moduleLoaded = true; return true; loadError: freeTmpModule(); moduleFailedToLoad = true; return false; } static void clearTmpModule(void) { memset(patternTmp, 0, sizeof (patternTmp)); memset(instrTmp, 0, sizeof (instrTmp)); memset(&songTmp, 0, sizeof (songTmp)); for (uint32_t i = 0; i < MAX_PATTERNS; i++) patternNumRowsTmp[i] = 64; } static int32_t SDLCALL loadMusicThread(void *ptr) { return doLoadMusic(true); (void)ptr; } void loadMusic(UNICHAR *filenameU) { if (musicIsLoading || filenameU == NULL) return; mouseAnimOn(); musicIsLoading = true; moduleLoaded = false; moduleFailedToLoad = false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); 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, bool autoPlay) { if (filenameU == NULL) return false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); editor.loadMusicEvent = EVENT_NONE; doLoadMusic(false); if (moduleLoaded) { setupLoadedModule(); if (autoPlay) startPlaying(PLAYMODE_SONG, 0); return true; } return false; } bool allocateTmpPatt(int32_t pattNum, uint16_t numRows) { patternTmp[pattNum] = (note_t *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (patternTmp[pattNum] == NULL) return false; patternNumRowsTmp[pattNum] = numRows; return true; } bool allocateTmpInstr(int16_t insNum) { if (instrTmp[insNum] != NULL) return false; // already allocated instr_t *ins = (instr_t *)calloc(1, sizeof (instr_t)); if (ins == NULL) return false; sample_t *s = ins->smp; for (int32_t i = 0; i < MAX_SMP_PER_INST; i++, s++) { s->panning = 128; s->volume = 64; } instrTmp[insNum] = ins; return true; } static void freeTmpModule(void) // called on module load error { // free all patterns for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (patternTmp[i] != NULL) { free(patternTmp[i]); patternTmp[i] = NULL; } } // free all instruments and samples for (int32_t i = 1; i <= 256; i++) // if >128 instruments, we fake-load up to 128 extra (and discard them later) { if (instrTmp[i] == NULL) continue; sample_t *s = instrTmp[i]->smp; for (int32_t j = 0; j < MAX_SMP_PER_INST; j++, s++) freeSmpData(s); free(instrTmp[i]); instrTmp[i] = NULL; } } bool tmpPatternEmpty(uint16_t pattNum) { if (patternTmp[pattNum] == NULL) return true; uint8_t *scanPtr = (uint8_t *)patternTmp[pattNum]; const uint32_t scanLen = patternNumRowsTmp[pattNum] * TRACK_WIDTH; for (uint32_t i = 0; i < scanLen; i++) { if (scanPtr[i] != 0) return false; } return true; } void clearUnusedChannels(note_t *pattPtr, int16_t numRows, int32_t numChannels) { if (pattPtr == NULL || numChannels >= MAX_CHANNELS) return; const int32_t width = sizeof (note_t) * (MAX_CHANNELS - numChannels); note_t *p = &pattPtr[numChannels]; for (int32_t i = 0; i < numRows; i++, p += MAX_CHANNELS) memset(p, 0, width); } // called from input/video thread after the module was done loading static void setupLoadedModule(void) { lockMixerCallback(); freeAllInstr(); freeAllPatterns(); oldPlayMode = playMode; playMode = PLAYMODE_IDLE; songPlaying = false; #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 (int32_t i = 0; i < MAX_PATTERNS; i++) { pattern[i] = patternTmp[i]; patternNumRows[i] = patternNumRowsTmp[i]; } // copy over song struct memcpy(&song, &songTmp, sizeof (song_t)); fixSongName(); // copy over new instruments (includes sample pointers) for (int16_t i = 1; i <= MAX_INST; i++) { instr[i] = instrTmp[i]; fixInstrAndSampleNames(i); if (instr[i] != NULL) { sanitizeInstrument(instr[i]); for (int32_t j = 0; j < MAX_SMP_PER_INST; j++) { sample_t *s = &instr[i]->smp[j]; sanitizeSample(s); if (s->dataPtr != NULL) fixSample(s); // prepare sample for branchless linear interpolation } } } // we are the owners of the allocated memory ptrs set by the loader thread now // support non-even channel numbers if (song.numChannels & 1) { song.numChannels++; if (song.numChannels > MAX_CHANNELS) song.numChannels = MAX_CHANNELS; } song.numChannels = CLAMP(song.numChannels, 2, MAX_CHANNELS); song.songLength = CLAMP(song.songLength, 1, MAX_ORDERS); song.BPM = CLAMP(song.BPM, MIN_BPM, MAX_BPM); song.initialSpeed = song.speed = CLAMP(song.speed, 1, MAX_SPEED); if (song.songLoopStart >= song.songLength) song.songLoopStart = 0; song.globalVolume = 64; // remove overflown stuff in pattern data (FT2 doesn't do this) for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (patternNumRows[i] <= 0) patternNumRows[i] = 64; if (patternNumRows[i] > MAX_PATT_LEN) patternNumRows[i] = MAX_PATT_LEN; if (pattern[i] == NULL) continue; note_t *p = pattern[i]; for (int32_t j = 0; j < MAX_PATT_LEN * MAX_CHANNELS; j++, p++) { if (p->note > 97) p->note = 0; if (p->instr > 128) p->instr = 0; if (p->efx > 35) { p->efx = 0; p->efxData = 0; } } } setScrollBarEnd(SB_POS_ED, (song.songLength - 1) + 5); setScrollBarPos(SB_POS_ED, 0, false); resetChannels(); setPos(0, 0, true); setMixerBPM(song.BPM); editor.tmpPattern = editor.editPattern; // set kludge variable editor.BPM = song.BPM; editor.speed = song.speed; editor.tick = song.tick; editor.globalVolume = song.globalVolume; setLinearPeriods(tmpLinearPeriodsFlag); unlockMixerCallback(); editor.currVolEnvPoint = 0; editor.currPanEnvPoint = 0; refreshScopes(); exitTextEditing(); updateTextBoxPointers(); resetChannelOffset(); updateChanNums(); resetWavRenderer(); clearPattMark(); resetTrimSizes(); resetPlaybackTime(); diskOpSetFilename(DISKOP_ITEM_MODULE, editor.tmpFilenameU); // redraw top part of screen if (ui.extended) { togglePatternEditorExtended(); // exit