ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

commit 898c82f5468058e6d8fdb7d99c6ce9720046871b
parent a2f19de82ff56c52d445e88a802bee4df1c161e5
Author: Olav Sørensen <olav.sorensen@live.no>
Date:   Sat,  8 Feb 2020 14:41:28 +0100

Pushed v1.08 code

- Critical bugfix: Saved instruments (.xi) would end up being broken!
- Linux bugfix: Loading a song by passing it to the executable's argument from a
  terminal wouldn't work in most cases...
- macOS/Linux bugfix: Don't show ".." directory when you are in root
- Code fix: We don't want our main instrument/sample structs to be packed, only
  the ones used during saving/loading of songs/instruments. This doesn't change
  the behavior of the FT2 clone, but it prevents unaligned pointer access in the
  replayer and other routines.
- Small optimizations to pattern data rendering, those routines are quite slow!
- Updated HOW-TO-COMPILE.txt

Diffstat:
MHOW-TO-COMPILE.txt | 7+++++--
Msrc/ft2_diskop.c | 154++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/ft2_diskop.h | 2+-
Msrc/ft2_gui.c | 143++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/ft2_header.h | 3++-
Msrc/ft2_inst_ed.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/ft2_main.c | 1+
Msrc/ft2_module_loader.c | 293++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/ft2_module_loader.h | 3++-
Msrc/ft2_module_saver.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/ft2_pattern_draw.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/ft2_pattern_ed.c | 18++++++++++--------
Msrc/ft2_replayer.c | 6+++---
Msrc/ft2_replayer.h | 69+++++++++++++++++++++++++--------------------------------------------
Msrc/ft2_sysreqs.c | 3+++
Msrc/ft2_wav_renderer.c | 10----------
Mvs2019_project/ft2-clone/ft2-clone.vcxproj | 2--
17 files changed, 717 insertions(+), 490 deletions(-)

diff --git a/HOW-TO-COMPILE.txt b/HOW-TO-COMPILE.txt @@ -22,9 +22,12 @@ Compiled Windows/macOS binaries are always available at 16-bits.org/ft2.php chmod +x make-linux.sh (only needed once) ./make-linux.sh + Note: If you don't have libstdc++ and/or can't compile rtmidi, try running + make-linux-nomidi.sh instead. + Note: If you want faster audio mixing (for SLOW devices), pass -DLERPMIX - to the GCC command line (edit make-linux.sh). This will yield slightly - more blurry sound when interpolation is activated, though... + to the GCC command line (edit make-linux.sh/make-linux-nomidi.sh). This + will lower the resampling interpolation quality, though... Known issues: Audio recording (sampling) can update VERY slowly or not work at all... I have no idea why, it works really well on Windows/maCOS. diff --git a/src/ft2_diskop.c b/src/ft2_diskop.c @@ -83,7 +83,7 @@ static SDL_Thread *thread; static void setDiskOpItem(uint8_t item); -int32_t getFileSize(UNICHAR *fileName) // returning -1 = filesize over 2GB +int32_t getFileSize(UNICHAR *fileNameU) // returning -1 = filesize over 2GB { #ifdef _WIN32 FILE *f; @@ -93,7 +93,7 @@ int32_t getFileSize(UNICHAR *fileName) // returning -1 = filesize over 2GB int64_t fSize; #ifdef _WIN32 - f = UNICHAR_FOPEN(fileName, "rb"); + f = UNICHAR_FOPEN(fileNameU, "rb"); if (f == NULL) return 0; @@ -101,7 +101,7 @@ int32_t getFileSize(UNICHAR *fileName) // returning -1 = filesize over 2GB fSize = _ftelli64(f); fclose(f); #else - if (stat(fileName, &st) != 0) + if (stat(fileNameU, &st) != 0) return 0; fSize = (int64_t)(st.st_size); @@ -273,10 +273,10 @@ bool setupDiskOp(void) FReq_PatCurPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR)); FReq_TrkCurPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR)); - if (modTmpFName == NULL || insTmpFName == NULL || smpTmpFName == NULL || - patTmpFName == NULL || trkTmpFName == NULL || FReq_NameTemp == NULL || - FReq_ModCurPathU == NULL || FReq_InsCurPathU == NULL || FReq_SmpCurPathU == NULL || - FReq_PatCurPathU == NULL || FReq_TrkCurPathU == NULL) + if (modTmpFName == NULL || insTmpFName == NULL || smpTmpFName == NULL || patTmpFName == NULL || + trkTmpFName == NULL || FReq_NameTemp == NULL || FReq_ModCurPathU == NULL || + FReq_InsCurPathU == NULL || FReq_SmpCurPathU == NULL || FReq_PatCurPathU == NULL || + FReq_TrkCurPathU == NULL) { // allocated memory is free'd lateron showErrorMsgBox("Not enough memory!"); @@ -349,9 +349,9 @@ static bool deleteDirRecursive(UNICHAR *strU) SHFILEOPSTRUCTW shfo; memset(&shfo, 0, sizeof (shfo)); - shfo.wFunc = FO_DELETE; + shfo.wFunc = FO_DELETE; shfo.fFlags = FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION; - shfo.pFrom = strU; + shfo.pFrom = strU; return (SHFileOperationW(&shfo) == 0); } @@ -568,12 +568,12 @@ static char *getFilenameFromPath(char *p) { int32_t i, len; - if (p == NULL) - return (p); + if (p == NULL || p[0] == '\0') + return p; len = (int32_t)strlen(p); if (len < 2 || p[len-1] == DIR_DELIMITER) - return (p); + return p; // search for last directory delimiter for (i = len - 1; i >= 0; i--) @@ -583,7 +583,7 @@ static char *getFilenameFromPath(char *p) } if (i != 0) - p += i + 1; // we found a directory delimiter - skip to the last one + p += i+1; // we found a directory delimiter - skip to the last one return p; } @@ -594,7 +594,7 @@ void sanitizeFilename(const char *src) const char illegalChars[] = "\\/:*?\"<>|"; char *ptr; - if (src == NULL || *src == '\0') + if (src == NULL || src[0] == '\0') return; // convert illegal characters to space (for making a filename the OS will accept) @@ -624,7 +624,6 @@ void diskOpSetFilename(uint8_t type, UNICHAR *pathU) default: case DISKOP_ITEM_MODULE: { - memset(modTmpFName, 0, PATH_MAX + 1); strcpy(modTmpFName, filename); updateCurrSongFilename(); // for window title @@ -633,31 +632,19 @@ void diskOpSetFilename(uint8_t type, UNICHAR *pathU) break; case DISKOP_ITEM_INSTR: - { - memset(insTmpFName, 0, PATH_MAX + 1); strcpy(insTmpFName, filename); - } break; case DISKOP_ITEM_SAMPLE: - { - memset(smpTmpFName, 0, PATH_MAX + 1); strcpy(smpTmpFName, filename); - } break; case DISKOP_ITEM_PATTERN: - { - memset(patTmpFName, 0, PATH_MAX + 1); strcpy(patTmpFName, filename); - } break; case DISKOP_ITEM_TRACK: - { - memset(trkTmpFName, 0, PATH_MAX + 1); strcpy(trkTmpFName, filename); - } break; } @@ -715,10 +702,21 @@ static void openFile(UNICHAR *filenameU, bool songModifiedCheck) } break; - case DISKOP_ITEM_INSTR: loadInstr(filenameU); break; - case DISKOP_ITEM_SAMPLE: loadSample(filenameU, editor.curSmp, false); break; - case DISKOP_ITEM_PATTERN: loadPattern(filenameU); break; - case DISKOP_ITEM_TRACK: loadTrack(filenameU); break; + case DISKOP_ITEM_INSTR: + loadInstr(filenameU); + break; + + case DISKOP_ITEM_SAMPLE: + loadSample(filenameU, editor.curSmp, false); + break; + + case DISKOP_ITEM_PATTERN: + loadPattern(filenameU); + break; + + case DISKOP_ITEM_TRACK: + loadTrack(filenameU); + break; } } @@ -1205,45 +1203,40 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) name = unicharToCp437(nameU, false); if (name == NULL) return true; + + if (name[0] == '\0') + goto skipEntry; nameLen = (int32_t)strlen(name); - if (nameLen == 0) - { - free(name); - return true; - } // skip ".name" dirs/files if (nameLen >= 2 && name[0] == '.' && name[1] != '.') - { - free(name); - return true; - } + goto skipEntry; if (isDir) { // skip '.' directory if (nameLen == 1 && name[0] == '.') + goto skipEntry; + + // macOS/Linux: skip '..' directory if we're in root +#ifndef _WIN32 + if (nameLen == 2 && name[0] == '.' && name[1] == '.') { - free(name); - return true; + if (FReq_CurPathU[0] == '/' && FReq_CurPathU[1] == '\0') + goto skipEntry; } +#endif } else if (!FReq_ShowAllFiles) { extOffset = getExtOffset(name, nameLen); if (extOffset == -1) - { - free(name); - return true; - } + goto skipEntry; extLen = (int32_t)strlen(&name[extOffset]); if (extLen < 3 || extLen > 5) - { - free(name); - return true; // no possibly known extensions to filter out - } + goto skipEntry; // no possibly known extensions to filter out extPtr = &name[extOffset]; @@ -1256,10 +1249,7 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) if (extLen == 3) { if (_stricmp(".xm", extPtr) && _stricmp(".ft", extPtr)) - { - free(name); - return true; // skip, none of the extensions above - } + goto skipEntry; // skip, none of the extensions above } else if (extLen == 4) { @@ -1267,14 +1257,12 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) _stricmp(".s3m", extPtr) && _stricmp(".stm", extPtr) && _stricmp(".fst", extPtr) && _stricmp(".wav", extPtr)) { - free(name); - return true; // skip, none of the extensions above + goto skipEntry; // skip, none of the extensions above } } else { - free(name); - return true; + goto skipEntry; } } break; @@ -1284,10 +1272,7 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) if (extLen == 3) { if (_stricmp(".xi", extPtr)) - { - free(name); - return true; // skip, none of the extensions above - } + goto skipEntry; // skip, none of the extensions above } else if (extLen == 4) { @@ -1296,22 +1281,17 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) _stricmp(".smp", extPtr) && _stricmp(".sam", extPtr) && _stricmp(".aif", extPtr) && _stricmp(".pat", extPtr)) { - free(name); - return true; // skip, none of the extensions above + goto skipEntry; // skip, none of the extensions above } } else if (extLen == 5) { if (_stricmp(".aiff", extPtr)) - { - free(name); - return true; // skip, not the extension above - } + goto skipEntry; // skip, not the extension above } else { - free(name); - return true; + goto skipEntry; } } break; @@ -1325,22 +1305,17 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) _stricmp(".smp", extPtr) && _stricmp(".sam", extPtr) && _stricmp(".aif", extPtr)) { - free(name); - return true; // skip, none of the extensions above + goto skipEntry; // skip, none of the extensions above } } else if (extLen == 5) { if (_stricmp(".aiff", extPtr)) - { - free(name); - return true; // skip, not the extension above - } + goto skipEntry; // skip, not the extension above } else { - free(name); - return true; + goto skipEntry; } } break; @@ -1350,15 +1325,11 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) if (extLen == 3) { if (_stricmp(".xp", extPtr)) - { - free(name); - return true; // skip, not the extension above - } + goto skipEntry; // skip, not the extension above } else { - free(name); - return true; + goto skipEntry; } } break; @@ -1368,15 +1339,11 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) if (extLen == 3) { if (_stricmp(".xt", extPtr)) - { - free(name); - return true; // skip, not the extension above - } + goto skipEntry; // skip, not the extension above } else { - free(name); - return true; + goto skipEntry; } } break; @@ -1385,6 +1352,10 @@ static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir) free(name); return false; // "Show All Files" mode is enabled, don't skip entry + +skipEntry: + free(name); + return true; } static int8_t findFirst(DirRec *searchRec) @@ -1927,6 +1898,8 @@ static int32_t SDLCALL diskOp_ReadDirectoryThread(void *ptr) // free old buffer freeDirRecBuffer(); + UNICHAR_GETCWD(FReq_CurPathU, PATH_MAX); + // read first file lastFindFileFlag = findFirst(&tmpBuffer); if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP) @@ -1989,7 +1962,6 @@ static int32_t SDLCALL diskOp_ReadDirectoryThread(void *ptr) okBoxThreadSafe(0, "System message", "Not enough memory!"); } - UNICHAR_GETCWD(FReq_CurPathU, PATH_MAX); editor.diskOpReadDone = true; setMouseBusy(false); diff --git a/src/ft2_diskop.h b/src/ft2_diskop.h @@ -21,7 +21,7 @@ enum SMP_SAVE_MODE_WAV = 2 }; -int32_t getFileSize(UNICHAR *fileName); +int32_t getFileSize(UNICHAR *fileNameU); uint8_t getDiskOpItem(void); void updateCurrSongFilename(void); // for window title char *getCurrSongFilename(void); // for window title diff --git a/src/ft2_gui.c b/src/ft2_gui.c @@ -49,7 +49,8 @@ void unstuckLastUsedGUIElement(void) if (mouse.lastUsedObjectID == OBJECT_ID_NONE) { /* if last object ID is OBJECT_ID_NONE, check if we moved the - ** sample data loop pins, and unstuck them if so */ + ** sample data loop pins, and unstuck them if so + */ if (editor.ui.leftLoopPinMoving) { @@ -311,11 +312,11 @@ uint16_t textWidth16(const char *textPtr) void charOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr) { const uint8_t *srcPtr; - uint32_t *dstPtr, pixVal; + uint32_t *dstPtr, pixVal, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; @@ -327,8 +328,10 @@ void charOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr) { for (uint32_t x = 0; x < FONT1_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = pixVal; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = pixVal; + dstPtr[x] = tmp; } srcPtr += FONT1_WIDTH; @@ -343,7 +346,7 @@ void charOutBg(uint16_t xPos, uint16_t yPos, uint8_t fgPalette, uint8_t bgPalett assert(xPos < SCREEN_W && yPos < SCREEN_H); - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; @@ -355,8 +358,8 @@ void charOutBg(uint16_t xPos, uint16_t yPos, uint8_t fgPalette, uint8_t bgPalett for (uint32_t y = 0; y < FONT1_CHAR_H; y++) { - for (uint32_t x = 0; x < 7; x++) - dstPtr[x] = srcPtr[x] ? fg : bg; + for (uint32_t x = 0; x < FONT1_CHAR_W-1; x++) + dstPtr[x] = srcPtr[x] ? fg : bg; // compiles nicely into conditional move instructions srcPtr += FONT1_WIDTH; dstPtr += SCREEN_W; @@ -376,32 +379,37 @@ void charOutOutlined(uint16_t x, uint16_t y, uint8_t paletteIndex, char chr) void charOutShadow(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint8_t shadowPaletteIndex, char chr) { const uint8_t *srcPtr; - uint32_t *dstPtr, pixVal1, pixVal2; + uint32_t *dstPtr1, *dstPtr2, pixVal1, pixVal2, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; pixVal1 = video.palette[paletteIndex]; pixVal2 = video.palette[shadowPaletteIndex]; - srcPtr = &font1Data[chr * FONT1_CHAR_W]; - dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; + srcPtr = &font1Data[chr * FONT1_CHAR_W]; + dstPtr1 = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; + dstPtr2 = dstPtr1 + (SCREEN_W+1); for (uint32_t y = 0; y < FONT1_CHAR_H; y++) { for (uint32_t x = 0; x < FONT1_CHAR_W; x++) { - if (srcPtr[x]) - { - dstPtr[x+(SCREEN_W+1)] = pixVal2; - dstPtr[x] = pixVal1; - } + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr2[x]; + if (srcPtr[x] != 0) tmp = pixVal2; + dstPtr2[x] = tmp; + + tmp = dstPtr1[x]; + if (srcPtr[x] != 0) tmp = pixVal1; + dstPtr1[x] = tmp; } srcPtr += FONT1_WIDTH; - dstPtr += SCREEN_W; + dstPtr1 += SCREEN_W; + dstPtr2 += SCREEN_W; } } @@ -409,14 +417,14 @@ void charOutClipX(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr, { const uint8_t *srcPtr; uint16_t width; - uint32_t *dstPtr, pixVal; + uint32_t *dstPtr, pixVal, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); if (xPos > clipX) return; - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; @@ -432,8 +440,10 @@ void charOutClipX(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr, { for (uint32_t x = 0; x < width; x++) { - if (srcPtr[x]) - dstPtr[x] = pixVal; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = pixVal; + dstPtr[x] = tmp; } srcPtr += FONT1_WIDTH; @@ -444,11 +454,11 @@ void charOutClipX(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr, void bigCharOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr) { const uint8_t *srcPtr; - uint32_t *dstPtr, pixVal; + uint32_t *dstPtr, pixVal, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; @@ -460,8 +470,10 @@ void bigCharOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr) { for (uint32_t x = 0; x < FONT2_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = pixVal; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = pixVal; + dstPtr[x] = tmp; } srcPtr += FONT2_WIDTH; @@ -472,32 +484,37 @@ void bigCharOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr) static void bigCharOutShadow(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint8_t shadowPaletteIndex, char chr) { const uint8_t *srcPtr; - uint32_t *dstPtr, pixVal1, pixVal2; + uint32_t *dstPtr1, *dstPtr2, pixVal1, pixVal2, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); - chr &= 0x7F; + chr &= 0x7F; // this is important to get the nordic glyphs in the font if (chr == ' ') return; pixVal1 = video.palette[paletteIndex]; pixVal2 = video.palette[shadowPaletteIndex]; - srcPtr = &font2Data[chr * FONT2_CHAR_W]; - dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; + srcPtr = &font2Data[chr * FONT2_CHAR_W]; + dstPtr1 = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; + dstPtr2 = dstPtr1 + (SCREEN_W+1); for (uint32_t y = 0; y < FONT2_CHAR_H; y++) { for (uint32_t x = 0; x < FONT2_CHAR_W; x++) { - if (srcPtr[x]) - { - dstPtr[x+(SCREEN_W+1)] = pixVal2; - dstPtr[x] = pixVal1; - } + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr2[x]; + if (srcPtr[x] != 0) tmp = pixVal2; + dstPtr2[x] = tmp; + + tmp = dstPtr1[x]; + if (srcPtr[x] != 0) tmp = pixVal1; + dstPtr1[x] = tmp; } srcPtr += FONT2_WIDTH; - dstPtr += SCREEN_W; + dstPtr1 += SCREEN_W; + dstPtr2 += SCREEN_W; } } @@ -632,7 +649,7 @@ void textOutClipX(uint16_t x, uint16_t y, uint8_t paletteIndex, const char *text void hexOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint32_t val, uint8_t numDigits) { const uint8_t *srcPtr; - uint32_t *dstPtr, pixVal; + uint32_t *dstPtr, pixVal, tmp; assert(xPos < SCREEN_W && yPos < SCREEN_H); @@ -648,8 +665,10 @@ void hexOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint32_t val, ui { for (uint32_t x = 0; x < FONT6_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = pixVal; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = pixVal; + dstPtr[x] = tmp; } srcPtr += FONT6_WIDTH; @@ -680,7 +699,7 @@ void hexOutBg(uint16_t xPos, uint16_t yPos, uint8_t fgPalette, uint8_t bgPalette for (uint32_t y = 0; y < FONT6_CHAR_H; y++) { for (uint32_t x = 0; x < FONT6_CHAR_W; x++) - dstPtr[x] = srcPtr[x] ? fg : bg; + dstPtr[x] = srcPtr[x] ? fg : bg; // compiles nicely into conditional move instructions srcPtr += FONT6_WIDTH; dstPtr += SCREEN_W; @@ -935,13 +954,13 @@ void drawFramework(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t type) void showTopLeftMainScreen(bool restoreScreens) { - editor.ui.diskOpShown = false; + editor.ui.diskOpShown = false; editor.ui.sampleEditorExtShown = false; - editor.ui.instEditorExtShown = false; - editor.ui.transposeShown = false; - editor.ui.advEditShown = false; - editor.ui.wavRendererShown = false; - editor.ui.trimScreenShown = false; + editor.ui.instEditorExtShown = false; + editor.ui.transposeShown = false; + editor.ui.advEditShown = false; + editor.ui.wavRendererShown = false; + editor.ui.trimScreenShown = false; editor.ui.scopesShown = true; if (restoreScreens) @@ -949,13 +968,13 @@ void showTopLeftMainScreen(bool restoreScreens) switch (editor.ui.oldTopLeftScreen) { default: break; - case 1: editor.ui.diskOpShown = true; break; + case 1: editor.ui.diskOpShown = true; break; case 2: editor.ui.sampleEditorExtShown = true; break; - case 3: editor.ui.instEditorExtShown = true; break; - case 4: editor.ui.transposeShown = true; break; - case 5: editor.ui.advEditShown = true; break; - case 6: editor.ui.wavRendererShown = true; break; - case 7: editor.ui.trimScreenShown = true; break; + case 3: editor.ui.instEditorExtShown = true; break; + case 4: editor.ui.transposeShown = true; break; + case 5: editor.ui.advEditShown = true; break; + case 6: editor.ui.wavRendererShown = true; break; + case 7: editor.ui.trimScreenShown = true; break; } if (editor.ui.oldTopLeftScreen > 0) @@ -1205,10 +1224,22 @@ void showTopScreen(bool restoreScreens) { editor.ui.scopesShown = false; - if (editor.ui.aboutScreenShown) showAboutScreen(); - else if (editor.ui.configScreenShown) showConfigScreen(); - else if (editor.ui.helpScreenShown) showHelpScreen(); - else if (editor.ui.nibblesShown) showNibblesScreen(); + if (editor.ui.aboutScreenShown) + { + showAboutScreen(); + } + else if (editor.ui.configScreenShown) + { + showConfigScreen(); + } + else if (editor.ui.helpScreenShown) + { + showHelpScreen(); + } + else if (editor.ui.nibblesShown) + { + showNibblesScreen(); + } else { showTopLeftMainScreen(restoreScreens); // updates editor.ui.scopesShown diff --git a/src/ft2_header.h b/src/ft2_header.h @@ -12,7 +12,7 @@ #endif #include "ft2_replayer.h" -#define PROG_VER_STR "1.07" +#define PROG_VER_STR "1.08" // do NOT change these! It will only mess things up... @@ -122,6 +122,7 @@ struct editor_t UNICHAR *tmpFilenameU, *tmpInstrFilenameU; // used by saving/loading threads UNICHAR *configFileLocation, *audioDevConfigFileLocation, *midiConfigFileLocation; + volatile bool mainLoopOngoing; volatile bool busy, scopeThreadMutex, programRunning, wavIsRendering, wavReachedEndFlag; volatile bool updateCurSmp, updateCurInstr, diskOpReadDir, diskOpReadDone, updateWindowTitle; volatile uint8_t loadMusicEvent; diff --git a/src/ft2_inst_ed.c b/src/ft2_inst_ed.c @@ -18,6 +18,7 @@ #include "ft2_video.h" #include "ft2_sample_loader.h" #include "ft2_diskop.h" +#include "ft2_module_loader.h" #ifdef _MSC_VER #pragma pack(push) @@ -71,7 +72,7 @@ typedef struct instrXIHeaderTyp_t uint8_t ta[96]; int16_t envVP[12][2], envPP[12][2]; uint8_t envVPAnt, envPPAnt, envVSust, envVRepS, envVRepE, envPSust, envPRepS; - uint8_t envPRepE, envVtyp, envPtyp, vibTyp, vibSweep, vibDepth, vibRate; + uint8_t envPRepE, envVTyp, envPTyp, vibTyp, vibSweep, vibDepth, vibRate; uint16_t fadeOut; uint8_t midiOn, midiChannel; int16_t midiProgram, midiBend; @@ -2004,7 +2005,7 @@ static void writeEnvelope(int32_t nr) le = -1; } - curEnvP = ins->envVP; + curEnvP = ins->envVP; selected = editor.currVolEnvPoint; } else @@ -2026,7 +2027,7 @@ static void writeEnvelope(int32_t nr) le = -1; } - curEnvP = ins->envPP; + curEnvP = ins->envPP; selected = editor.currPanEnvPoint; } @@ -2933,11 +2934,13 @@ bool testInstrSwitcherMouseDown(void) static int32_t SDLCALL saveInstrThread(void *ptr) { int16_t n; + int32_t i; size_t result; FILE *f; instrXIHeaderTyp ih; - sampleTyp *srcSmp; - sampleHeaderTyp *dstSmpHdr; + instrTyp *ins; + sampleTyp *s; + sampleHeaderTyp *dst; (void)ptr; @@ -2948,7 +2951,7 @@ static int32_t SDLCALL saveInstrThread(void *ptr) } n = getUsedSamples(saveInstrNr); - if (n == 0) + if (n == 0 || instr[saveInstrNr] == NULL) { okBoxThreadSafe(0, "System message", "Instrument is empty!"); return false; @@ -2961,7 +2964,8 @@ static int32_t SDLCALL saveInstrThread(void *ptr) return false; } - memset(&ih, 0, sizeof (ih)); + memset(&ih, 0, sizeof (ih)); // important, also clears reserved stuff + memcpy(ih.sig, "Extended Instrument: ", 21); memset(ih.name, ' ', 22); memcpy(ih.name, song.instrName[saveInstrNr], strlen(song.instrName[saveInstrNr])); @@ -2969,17 +2973,53 @@ static int32_t SDLCALL saveInstrThread(void *ptr) memcpy(ih.progName, PROG_NAME_STR, 20); ih.ver = 0x0102; - memcpy(ih.ta, &instr[saveInstrNr], INSTR_SIZE); + // copy over instrument struct data to instrument header + ins = instr[saveInstrNr]; + 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.antSamp = n; - for (int16_t i = 0; i < n; i++) + // copy over sample struct datas to sample headers + for (i = 0; i < n; i++) { - srcSmp = &instr[saveInstrNr]->samp[i]; - dstSmpHdr = &ih.samp[i]; + s = &instr[saveInstrNr]->samp[i]; + dst = &ih.samp[i]; - memcpy(&dstSmpHdr->len, &srcSmp->len, 12+4+2 + strlen(srcSmp->name)); - if (srcSmp->pek == NULL) - dstSmpHdr->len = 0; + 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; + + dst->nameLen = (uint8_t)strlen(s->name); + memcpy(dst->name, s->name, 22); + + if (s->pek == NULL) + dst->len = 0; } result = fwrite(&ih, INSTR_XI_HEADER_SIZE + (ih.antSamp * sizeof (sampleHeaderTyp)), 1, f); @@ -2991,20 +3031,20 @@ static int32_t SDLCALL saveInstrThread(void *ptr) } pauseAudio(); - for (int16_t i = 0; i < n; i++) + for (i = 0; i < n; i++) { - srcSmp = &instr[saveInstrNr]->samp[i]; - if (srcSmp->pek != NULL) + s = &instr[saveInstrNr]->samp[i]; + if (s->pek != NULL && s->len > 0) { - restoreSample(srcSmp); - samp2Delta(srcSmp->pek, srcSmp->len, srcSmp->typ); + restoreSample(s); + samp2Delta(s->pek, s->len, s->typ); - result = fwrite(srcSmp->pek, 1, srcSmp->len, f); + result = fwrite(s->pek, 1, s->len, f); - delta2Samp(srcSmp->pek, srcSmp->len, srcSmp->typ); - fixSample(srcSmp); + delta2Samp(s->pek, s->len, s->typ); + fixSample(s); - if (result != (size_t)srcSmp->len) // write not OK + if (result != (size_t)s->len) // write not OK { resumeAudio(); fclose(f); @@ -3044,10 +3084,7 @@ void saveInstr(UNICHAR *filenameU, int16_t nr) static int16_t getPATNote(int32_t freq) { - double dFreq; - - dFreq = ((log(freq / (440.0 * 1000.0)) / M_LN2) * 12.0) + 48.0 + 9.0; - return (int16_t)round(dFreq); + return (int16_t)round(((log(freq / (440.0 * 1000.0)) / M_LN2) * 12.0) + 48.0 + 9.0); } static int32_t SDLCALL loadInstrThread(void *ptr) @@ -3055,11 +3092,13 @@ static int32_t SDLCALL loadInstrThread(void *ptr) bool stereoWarning; int8_t *newPtr; int16_t a, b; + int32_t i, j; double dFreq; FILE *f; instrXIHeaderTyp ih; instrPATHeaderTyp ih_PAT; instrPATWaveHeaderTyp ih_PATWave; + sampleHeaderTyp *src; sampleTyp *s; instrTyp *ins; @@ -3093,14 +3132,16 @@ static int32_t SDLCALL loadInstrThread(void *ptr) goto loadDone; } - if (ih.ver == 0x0101) + if (ih.ver == 0x0101) // not even FT2.01 can save old v1.01 .XI files, so I have no way to verify this. { - fseek(f, -2 - 15 - 1 - 2, SEEK_CUR); + fseek(f, -20, SEEK_CUR); ih.antSamp = ih.midiProgram; - memset(&ih.midiProgram, 0, 2+15+1+2); + ih.midiProgram = 0; + ih.midiBend = 0; + ih.mute = false; } - if (ih.antSamp > 16) + if (ih.antSamp > MAX_SMP_PER_INST) { okBoxThreadSafe(0, "System message", "Incompatible instrument!"); goto loadDone; @@ -3122,44 +3163,67 @@ static int32_t SDLCALL loadInstrThread(void *ptr) goto loadDone; } + // copy instrument header elements to our instrument struct + + ins = instr[editor.curInstr]; + memcpy(ins->ta, ih.ta, 96); + memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t)); + memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t)); + ins->envVPAnt = ih.envVPAnt; + ins->envPPAnt = ih.envPPAnt; + ins->envVSust = ih.envVSust; + ins->envVRepS = ih.envVRepS; + ins->envVRepE = ih.envVRepE; + ins->envPSust = ih.envPSust; + ins->envPRepS = ih.envPRepS; + ins->envPRepE = ih.envPRepE; + ins->envVTyp = ih.envVTyp; + ins->envPTyp = ih.envPTyp; + ins->vibTyp = ih.vibTyp; + ins->vibSweep = ih.vibSweep; + ins->vibDepth = ih.vibDepth; + ins->vibRate = ih.vibRate; + ins->fadeOut = ih.fadeOut; + ins->midiOn = (ih.midiOn > 0) ? true : false; + ins->midiChannel = ih.midiChannel; + ins->midiProgram = ih.midiProgram; + ins->midiBend = ih.midiBend; + ins->mute = (ih.mute > 0) ? true : false; + ins->antSamp = ih.antSamp; // used in loadInstrSample() + // sanitize stuff for broken/unsupported instruments - ih.midiProgram = CLAMP(ih.midiProgram, 0, 127); - ih.midiBend = CLAMP(ih.midiBend, 0, 36); + ins->midiProgram = CLAMP(ins->midiProgram, 0, 127); + ins->midiBend = CLAMP(ins->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; + if (ins->midiChannel > 15) ins->midiChannel = 15; + if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F; + if (ins->vibRate > 0x3F) ins->vibRate = 0x3F; + if (ins->vibTyp > 3) ins->vibTyp = 0; - for (int16_t i = 0; i < 96; i++) + for (i = 0; i < 96; i++) { - if (ih.ta[i] > 15) - ih.ta[i] = 15; + if (ins->ta[i] > 15) + ins->ta[i] = 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; + if (ins->envVPAnt > 12) ins->envVPAnt = 12; + if (ins->envVRepS > 11) ins->envVRepS = 11; + if (ins->envVRepE > 11) ins->envVRepE = 11; + if (ins->envVSust > 11) ins->envVSust = 11; + if (ins->envPPAnt > 12) ins->envPPAnt = 12; + if (ins->envPRepS > 11) ins->envPRepS = 11; + if (ins->envPRepE > 11) ins->envPRepE = 11; + if (ins->envPSust > 11) ins->envPSust = 11; - for (int16_t i = 0; i < 12; i++) + for (i = 0; i < 12; i++) { - if ((uint16_t)ih.envVP[i][0] > 32767) ih.envVP[i][0] = 32767; - if ((uint16_t)ih.envPP[i][0] > 32767) ih.envPP[i][0] = 32767; - if ((uint16_t)ih.envVP[i][1] > 64) ih.envVP[i][1] = 64; - if ((uint16_t)ih.envPP[i][1] > 63) ih.envPP[i][1] = 63; + if ((uint16_t)ins->envVP[i][0] > 32767) ins->envVP[i][0] = 32767; + if ((uint16_t)ins->envPP[i][0] > 32767) ins->envPP[i][0] = 32767; + if ((uint16_t)ins->envVP[i][1] > 64) ins->envVP[i][1] = 64; + if ((uint16_t)ins->envPP[i][1] > 63) ins->envPP[i][1] = 63; + } - // ---------------------------------------- - - memcpy(instr[editor.curInstr]->ta, ih.ta, INSTR_SIZE); - if (fread(ih.samp, sizeof (sampleHeaderTyp) * ih.antSamp, 1, f) != 1) { freeInstr(editor.curInstr); @@ -3168,23 +3232,50 @@ static int32_t SDLCALL loadInstrThread(void *ptr) goto loadDone; } - for (int16_t i = 0; i < ih.antSamp; i++) - memcpy(&instr[editor.curInstr]->samp[i], &ih.samp[i], 12 + 4 + 24); + for (i = 0; i < ih.antSamp; i++) + { + s = &instr[editor.curInstr]->samp[i]; + src = &ih.samp[i]; + + // copy sample header elements to our sample struct + + s->len = src->len; + s->repS = src->repS; + s->repL = src->repL; + s->vol = src->vol; + s->fine = src->fine; + s->typ = src->typ; + s->pan = src->pan; + s->relTon = src->relTon; + memcpy(s->name, src->name, 22); + s->name[22] = '\0'; + + // dst->pek is set up later + + // trim off spaces at end of name + for (j = 21; j >= 0; j--) + { + if (s->name[j] == ' ' || s->name[j] == 0x1A) + s->name[j] = '\0'; + else + break; + } + + // sanitize stuff broken/unsupported samples + if (s->vol > 64) + s->vol = 64; + + s->relTon = CLAMP(s->relTon, -48, 71); + } } - for (int16_t i = 0; i < ih.antSamp; i++) + for (i = 0; i < ih.antSamp; i++) { s = &instr[editor.curInstr]->samp[i]; - // sanitize stuff for malicious modules - if (s->vol > 64) - s->vol = 64; - - s->relTon = CLAMP(s->relTon, -48, 71); - - // if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior) + // if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 mixer behavior) if ((s->typ & 3) == 3) - s->typ = 2; + s->typ &= 0xFE; if (s->len > 0) { @@ -3223,6 +3314,7 @@ static int32_t SDLCALL loadInstrThread(void *ptr) stereoWarning = true; } + checkSampleRepeat(s); fixSample(s); } } @@ -3256,7 +3348,7 @@ static int32_t SDLCALL loadInstrThread(void *ptr) memset(song.instrName[editor.curInstr], 0, 22 + 1); memcpy(song.instrName[editor.curInstr], ih_PAT.instrName, 16); - for (int16_t i = 0; i < ih_PAT.antSamp; i++) + for (i = 0; i < ih_PAT.antSamp; i++) { s = &instr[editor.curInstr]->samp[i]; ins = instr[editor.curInstr]; @@ -3281,8 +3373,14 @@ static int32_t SDLCALL loadInstrThread(void *ptr) if (i == 0) { ins->vibSweep = ih_PATWave.vibSweep; - ins->vibRate = ih_PATWave.vibRate / 2; if (ins->vibRate > 0x3F) ins->vibRate = 0x3F; - ins->vibDepth = ih_PATWave.vibDepth / 2; if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F; + + ins->vibRate = ih_PATWave.vibRate / 2; + if (ins->vibRate > 0x3F) + ins->vibRate = 0x3F; + + ins->vibDepth = ih_PATWave.vibDepth / 2; + if (ins->vibDepth > 0x0F) + ins->vibDepth = 0x0F; } s = &instr[editor.curInstr]->samp[i]; @@ -3298,7 +3396,7 @@ static int32_t SDLCALL loadInstrThread(void *ptr) s->typ |= 1; // forward loop } - s->pan = (ih_PATWave.pan == 8) ? 128 : (ih_PATWave.pan * 17); // FT2 does <<4 here, I don't like that! + s->pan = ih_PATWave.pan << 4; s->len = ih_PATWave.waveSize; s->repS = ih_PATWave.repS; @@ -3323,13 +3421,16 @@ static int32_t SDLCALL loadInstrThread(void *ptr) dFreq = round((1.0 + ih_PATWave.fineTune / 512.0) * ih_PATWave.sampleRate); tuneSample(s, (int32_t)dFreq); - s->relTon -= (int8_t)(getPATNote(ih_PATWave.rootFrq) - (12 * 3)); - s->relTon = CLAMP(s->relTon, -48, 71); + s->relTon -= (int8_t)(getPATNote(ih_PATWave.rootFrq) - (12*3)); + s->relTon = CLAMP(s->relTon, -48, 71); + + a = getPATNote(ih_PATWave.lowFrq); + a = CLAMP(a, 0, 95); - a = getPATNote(ih_PATWave.lowFrq); a = CLAMP(a, 0, 95); - b = getPATNote(ih_PATWave.highFreq); b = CLAMP(b, 0, 95); + b = getPATNote(ih_PATWave.highFreq); + b = CLAMP(b, 0, 95); - for (int16_t j = a; j <= b; j++) + for (j = a; j <= b; j++) ins->ta[j] = (uint8_t)i; if (fread(s->pek, ih_PATWave.waveSize, 1, f) != 1) diff --git a/src/ft2_main.c b/src/ft2_main.c @@ -221,6 +221,7 @@ int main(int argc, char *argv[]) setupWaitVBL(); handleModuleLoadFromArg(argc, argv); + editor.mainLoopOngoing = true; while (editor.programRunning) { beginFPSCounter(); diff --git a/src/ft2_module_loader.c b/src/ft2_module_loader.c @@ -10,7 +10,6 @@ #include <unistd.h> #endif #include "ft2_header.h" -#include "ft2_audio.h" #include "ft2_config.h" #include "ft2_scopes.h" #include "ft2_trim.h" @@ -22,7 +21,6 @@ #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" @@ -129,12 +127,13 @@ 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); +void checkSampleRepeat(sampleTyp *s); + // ft2_replayer.c extern const char modSig[32][5]; extern const uint16_t amigaPeriod[12*8]; @@ -162,7 +161,7 @@ static bool allocateTmpInstr(int16_t nr) return true; } -static bool loadMusicMOD(FILE *f, uint32_t fileLength) +static bool loadMusicMOD(FILE *f, uint32_t fileLength, bool fromExternalThread) { char ID[16]; bool modIsUST, modIsFEST, modIsNT; @@ -173,6 +172,9 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) sampleTyp *s; songMOD31HeaderTyp h_MOD31; songMOD15HeaderTyp h_MOD15; + int16_t (*showMsg)(int16_t, const char *, const char *); + + showMsg = fromExternalThread ? okBoxThreadSafe : okBox; // start loading MOD @@ -187,26 +189,26 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) // 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!"); + showMsg(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!"); + showMsg(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."); + showMsg(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."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -253,7 +255,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) // unsupported MOD if (j == -1) { - okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -262,7 +264,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) modIsUST = false; if (fileLength < sizeof (h_MOD31)) { - okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -277,14 +279,14 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) modIsUST = true; if (fileLength < sizeof (h_MOD15)) { - okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported."); + showMsg(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."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -297,7 +299,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) if (songTmp.antChn == 0 || songTmp.len < 1) { - okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -306,7 +308,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) 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."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -352,7 +354,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) pattTmp[a] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (pattTmp[a] == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto modLoadError; } @@ -365,7 +367,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) if (fread(bytes, 1, 4, f) != 4) { - okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported."); + showMsg(0, "System message", "Error: This file is either not a module, or is not supported."); goto modLoadError; } @@ -472,7 +474,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) if (!allocateTmpInstr(1 + a)) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto modLoadError; } @@ -485,7 +487,7 @@ static bool loadMusicMOD(FILE *f, uint32_t fileLength) s->pek = (int8_t *)malloc(s->len + LOOP_FIX_LEN); if (s->pek == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto modLoadError; } @@ -604,7 +606,7 @@ static uint8_t stmTempoToBPM(uint8_t tempo) // ported from original ST2.3 replay return (uint8_t)CLAMP(bpm, 32, 255); // result can be slightly off, but close enough... } -static bool loadMusicSTM(FILE *f, uint32_t fileLength) +static bool loadMusicSTM(FILE *f, uint32_t fileLength, bool fromExternalThread) { bool check3xx; uint8_t typ, tmp8, tempo; @@ -614,25 +616,28 @@ static bool loadMusicSTM(FILE *f, uint32_t fileLength) tonTyp *ton; sampleTyp *s; songSTMHeaderTyp h_STM; + int16_t (*showMsg)(int16_t, const char *, const char *); + + showMsg = fromExternalThread ? okBoxThreadSafe : okBox; 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 + return loadMusicMOD(f, fileLength, fromExternalThread); // 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 + return loadMusicMOD(f, fileLength, fromExternalThread); // 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!"); + showMsg(0, "System message", "Error loading .stm: Incompatible module!"); goto stmLoadError; } @@ -677,14 +682,14 @@ static bool loadMusicSTM(FILE *f, uint32_t fileLength) pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (pattTmp[i] == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(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!"); + showMsg(0, "System message", "General I/O error during loading!"); goto stmLoadError; } @@ -805,7 +810,7 @@ static bool loadMusicSTM(FILE *f, uint32_t fileLength) s->pek = (int8_t *)malloc(h_STM.instr[i].len + LOOP_FIX_LEN); if (s->pek == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto stmLoadError; } @@ -835,7 +840,7 @@ static bool loadMusicSTM(FILE *f, uint32_t fileLength) if (fread(s->pek, s->len, 1, f) != 1) { - okBoxThreadSafe(0, "System message", "General I/O error during loading! Possibly corrupt module?"); + showMsg(0, "System message", "General I/O error during loading! Possibly corrupt module?"); goto stmLoadError; } @@ -954,7 +959,7 @@ static int8_t countS3MChannels(uint16_t antPtn) return channels; } -static bool loadMusicS3M(FILE *f, uint32_t dataLength) +static bool loadMusicS3M(FILE *f, uint32_t dataLength, bool fromExternalThread) { int8_t *tmpSmp; bool check3xx, illegalUxx; @@ -968,6 +973,9 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) sampleTyp *s; songS3MHeaderTyp h_S3M; songS3MinstrHeaderTyp h_S3MInstr; + int16_t (*showMsg)(int16_t, const char *, const char *); + + showMsg = fromExternalThread ? okBoxThreadSafe : okBox; stereoSamplesWarn = false; @@ -976,24 +984,24 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) // 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 + return loadMusicSTM(f, dataLength, fromExternalThread); // 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 + return loadMusicSTM(f, dataLength, fromExternalThread); // 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!"); + showMsg(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?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use?"); goto s3mLoadError; } @@ -1060,13 +1068,13 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (fread(ha, ai + ai, 1, f) != 1) { - okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?"); + showMsg(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?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use?"); goto s3mLoadError; } @@ -1093,7 +1101,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (fread(&j, 2, 1, f) != 1) { - okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use?"); goto s3mLoadError; } @@ -1102,14 +1110,14 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (pattTmp[i] == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(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?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use?"); goto s3mLoadError; } @@ -1394,7 +1402,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (fread(&h_S3MInstr, 1, sizeof (h_S3MInstr), f) != sizeof (h_S3MInstr)) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto s3mLoadError; } @@ -1412,21 +1420,21 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (h_S3MInstr.typ > 1) { - okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!"); + showMsg(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!"); + showMsg(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!"); + showMsg(0, "System message", "Not enough memory!"); goto s3mLoadError; } @@ -1447,7 +1455,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) tmpSmp = (int8_t *)malloc(len + LOOP_FIX_LEN); if (tmpSmp == NULL) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto s3mLoadError; } @@ -1489,7 +1497,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (fread(tmpSmp, len, 1, f) != 1) { free(tmpSmp); - okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use?"); goto s3mLoadError; } @@ -1503,7 +1511,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (s->pek == NULL) { free(tmpSmp); - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto s3mLoadError; } @@ -1521,7 +1529,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) if (s->pek == NULL) { free(tmpSmp); - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto s3mLoadError; } @@ -1537,7 +1545,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) } if (stereoSamplesWarn) - okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono."); + showMsg(0, "System message", "Stereo samples were found and will be converted to mono."); // non-FT2: fix overflown 9xx and illegal 3xx slides @@ -1611,7 +1619,7 @@ static bool loadMusicS3M(FILE *f, uint32_t dataLength) 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!"); + showMsg(6, "System message", "Warning: S3M channel panning is not compatible with FT2!"); moduleLoaded = true; return true; @@ -1623,7 +1631,7 @@ s3mLoadError: return false; } -static int32_t SDLCALL loadMusicThread(void *ptr) +bool doLoadMusic(bool fromExternalThread) { char tmpText[128]; int16_t k; @@ -1631,15 +1639,16 @@ static int32_t SDLCALL loadMusicThread(void *ptr) uint32_t filelength; songHeaderTyp h; FILE *f; + int16_t (*showMsg)(int16_t, const char *, const char *); - (void)ptr; + showMsg = fromExternalThread ? okBoxThreadSafe : okBox; stereoSamplesWarn = false; linearFreqTable = false; if (editor.tmpFilenameU == NULL) { - okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?"); + showMsg(0, "System message", "Generic memory fault during loading!"); moduleFailedToLoad = true; return false; } @@ -1647,7 +1656,7 @@ static int32_t SDLCALL loadMusicThread(void *ptr) f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb"); if (f == NULL) { - okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?"); + showMsg(0, "System message", "General I/O error during loading! Is the file in use? Does it exist?"); moduleFailedToLoad = true; return false; } @@ -1658,10 +1667,10 @@ static int32_t SDLCALL loadMusicThread(void *ptr) // start loading if (fread(&h, 1, sizeof (h), f) != sizeof (h)) - return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m + return loadMusicS3M(f, filelength, fromExternalThread); // 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 + return loadMusicS3M(f, filelength, fromExternalThread); // not a .xm file, try to load as .s3m loadedFormat = FORMAT_XM; @@ -1671,7 +1680,7 @@ static int32_t SDLCALL loadMusicThread(void *ptr) 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); + showMsg(0, "System message", tmpText); moduleFailedToLoad = true; return false; @@ -1679,29 +1688,29 @@ static int32_t SDLCALL loadMusicThread(void *ptr) if (h.len > MAX_ORDERS) { - okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 orders!"); + showMsg(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!"); + showMsg(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!"); + showMsg(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."); + showMsg(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!"); + showMsg(0, "System message", "Error loading .xm: The module is empty!"); goto xmLoadError; } @@ -1747,7 +1756,7 @@ static int32_t SDLCALL loadMusicThread(void *ptr) { if (!loadInstrHeader(f, i)) { - okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!"); + showMsg(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!"); goto xmLoadError; } } @@ -1762,7 +1771,7 @@ static int32_t SDLCALL loadMusicThread(void *ptr) { if (!loadInstrSample(f, i)) { - okBoxThreadSafe(0, "System message", "Not enough memory!"); + showMsg(0, "System message", "Not enough memory!"); goto xmLoadError; } } @@ -1781,20 +1790,20 @@ static int32_t SDLCALL loadMusicThread(void *ptr) { if (!loadInstrHeader(f, i)) { - okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!"); + showMsg(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!"); + showMsg(0, "System message", "Not enough memory!"); goto xmLoadError; } } } if (stereoSamplesWarn) - okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono."); + showMsg(0, "System message", "Stereo samples were found and will be converted to mono."); fclose(f); @@ -1808,6 +1817,12 @@ xmLoadError: return false; } +static int32_t SDLCALL loadMusicThread(void *ptr) +{ + (void)ptr; + return doLoadMusic(true); +} + void loadMusic(UNICHAR *filenameU) { if (musicIsLoading) @@ -1844,13 +1859,13 @@ void loadMusic(UNICHAR *filenameU) SDL_DetachThread(thread); } -bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing +bool loadMusicUnthreaded(UNICHAR *filenameU, bool autoPlay) { - if (editor.tmpFilenameU == NULL) + if (filenameU == NULL || editor.tmpFilenameU == NULL) return false; // clear deprecated pointers from possible last loading session (super important) - memset(pattTmp, 0, sizeof (pattTmp)); + memset(pattTmp, 0, sizeof (pattTmp)); memset(instrTmp, 0, sizeof (instrTmp)); // prevent stuck instrument names from previous module @@ -1861,12 +1876,15 @@ bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); - loadMusicThread(NULL); editor.loadMusicEvent = EVENT_NONE; + doLoadMusic(false); if (moduleLoaded) { setupLoadedModule(); + if (autoPlay) + startPlaying(PLAYMODE_SONG, 0); + return true; } @@ -1910,6 +1928,8 @@ static bool loadInstrHeader(FILE *f, uint16_t i) uint8_t j; uint32_t readSize; instrHeaderTyp ih; + instrTyp *ins; + sampleHeaderTyp *src; sampleTyp *s; memset(&ih, 0, INSTR_HEADER_SIZE); @@ -1921,7 +1941,7 @@ static bool loadInstrHeader(FILE *f, uint16_t i) readSize = INSTR_HEADER_SIZE; // load instrument data into temp buffer - fread(ih.name, readSize - 4, 1, f); // -4 = skip ih.instrSize + 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) @@ -1952,45 +1972,66 @@ static bool loadInstrHeader(FILE *f, uint16_t i) if (!allocateTmpInstr(i)) return false; + // copy instrument header elements to our instrument struct + + ins = instrTmp[i]; + memcpy(ins->ta, ih.ta, 96); + memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t)); + memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t)); + ins->envVPAnt = ih.envVPAnt; + ins->envPPAnt = ih.envPPAnt; + ins->envVSust = ih.envVSust; + ins->envVRepS = ih.envVRepS; + ins->envVRepE = ih.envVRepE; + ins->envPSust = ih.envPSust; + ins->envPRepS = ih.envPRepS; + ins->envPRepE = ih.envPRepE; + ins->envVTyp = ih.envVTyp; + ins->envPTyp = ih.envPTyp; + ins->vibTyp = ih.vibTyp; + ins->vibSweep = ih.vibSweep; + ins->vibDepth = ih.vibDepth; + ins->vibRate = ih.vibRate; + ins->fadeOut = ih.fadeOut; + ins->midiOn = (ih.midiOn > 0) ? true : false; + ins->midiChannel = ih.midiChannel; + ins->midiProgram = ih.midiProgram; + ins->midiBend = ih.midiBend; + ins->mute = (ih.mute > 0) ? true : false; + ins->antSamp = ih.antSamp; // used in loadInstrSample() + // sanitize stuff for broken/unsupported instruments - ih.midiProgram = CLAMP(ih.midiProgram, 0, 127); - ih.midiBend = CLAMP(ih.midiBend, 0, 36); + ins->midiProgram = CLAMP(ins->midiProgram, 0, 127); + ins->midiBend = CLAMP(ins->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; + if (ins->midiChannel > 15) ins->midiChannel = 15; + if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F; + if (ins->vibRate > 0x3F) ins->vibRate = 0x3F; + if (ins->vibTyp > 3) ins->vibTyp = 0; for (j = 0; j < 96; j++) { - if (ih.ta[j] > 15) - ih.ta[j] = 15; + if (ins->ta[j] > 15) + ins->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; + if (ins->envVPAnt > 12) ins->envVPAnt = 12; + if (ins->envVRepS > 11) ins->envVRepS = 11; + if (ins->envVRepE > 11) ins->envVRepE = 11; + if (ins->envVSust > 11) ins->envVSust = 11; + if (ins->envPPAnt > 12) ins->envPPAnt = 12; + if (ins->envPRepS > 11) ins->envPRepS = 11; + if (ins->envPRepE > 11) ins->envPRepE = 11; + if (ins->envPSust > 11) ins->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; + if ((uint16_t)ins->envVP[j][0] > 32767) ins->envVP[j][0] = 32767; + if ((uint16_t)ins->envPP[j][0] > 32767) ins->envPP[j][0] = 32767; + if ((uint16_t)ins->envVP[j][1] > 64) ins->envVP[j][1] = 64; + if ((uint16_t)ins->envPP[j][1] > 63) ins->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) @@ -2001,8 +2042,22 @@ static bool loadInstrHeader(FILE *f, uint16_t i) 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 + src = &ih.samp[j]; + + // copy sample header elements to our sample struct + + s->len = src->len; + s->repS = src->repS; + s->repL = src->repL; + s->vol = src->vol; + s->fine = src->fine; + s->typ = src->typ; + s->pan = src->pan; + s->relTon = src->relTon; + memcpy(s->name, src->name, 22); + s->name[22] = '\0'; + + // dst->pek is set up later // trim off spaces at end of name for (k = 21; k >= 0; k--) @@ -2013,7 +2068,7 @@ static bool loadInstrHeader(FILE *f, uint16_t i) break; } - // sanitize stuff for malicious modules + // sanitize stuff broken/unsupported samples if (s->vol > 64) s->vol = 64; @@ -2025,7 +2080,7 @@ static bool loadInstrHeader(FILE *f, uint16_t i) return true; } -static void checkSampleRepeat(sampleTyp *s) +void checkSampleRepeat(sampleTyp *s) { if (s->repS < 0) s->repS = 0; if (s->repL < 0) s->repL = 0; @@ -2049,7 +2104,7 @@ static bool loadInstrSample(FILE *f, uint16_t i) { s = &instrTmp[i]->samp[j]; - // if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior) + // if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 mixer behavior) if ((s->typ & 3) == 3) s->typ &= 0xFE; @@ -2414,11 +2469,11 @@ bool handleModuleLoadFromArg(int argc, char **argv) { int32_t filesize; uint32_t filenameLen; - UNICHAR *filenameU, tmpPathU[PATH_MAX + 2]; + UNICHAR *filenameU, tmpPathU[PATH_MAX+2]; // this is crude, we always expect only one parameter, and that it is the module. - if (argc != 2) + if (argc != 2 || argv[1] == NULL || argv[1][0] == '\0') return false; #ifdef __APPLE__ @@ -2428,7 +2483,7 @@ bool handleModuleLoadFromArg(int argc, char **argv) filenameLen = (uint32_t)strlen(argv[1]); - filenameU = (UNICHAR *)calloc((filenameLen + 1), sizeof (UNICHAR)); + filenameU = (UNICHAR *)calloc(filenameLen+1, sizeof (UNICHAR)); if (filenameU == NULL) { okBox(0, "System message", "Not enough memory!"); @@ -2444,34 +2499,28 @@ bool handleModuleLoadFromArg(int argc, char **argv) // store old path UNICHAR_GETCWD(tmpPathU, PATH_MAX); - // set binary path + // set path to where the main executable is UNICHAR_CHDIR(editor.binaryPathU); filesize = getFileSize(filenameU); - - if (filesize == -1) // >2GB + if (filesize == -1 || filesize >= 512L*1024*1024) // >=2GB or >=512MB { - okBox(0, "System message", "The file is too big and can't be loaded (over 2GB)."); - goto argLoadErr; - } + okBox(0, "System message", "Error: The module is too big to be loaded!"); + /* This is not really true, but let's add this check to prevent accidentally + ** passing really big files to the program. And how often do you really + ** see a >=512MB .XM/.S3M module? + */ - 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; + free(filenameU); + UNICHAR_CHDIR(tmpPathU); // set old path back + return false; } - editor.loadMusicEvent = EVENT_LOADMUSIC_ARGV; - loadMusic(filenameU); - - UNICHAR_CHDIR(tmpPathU); // set old path back - free(filenameU); - return true; + bool result = loadMusicUnthreaded(filenameU, true); -argLoadErr: - UNICHAR_CHDIR(tmpPathU); // set old path back free(filenameU); - return false; + UNICHAR_CHDIR(tmpPathU); // set old path back + return result; } void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck) diff --git a/src/ft2_module_loader.h b/src/ft2_module_loader.h @@ -5,9 +5,10 @@ #include "ft2_unicode.h" void loadMusic(UNICHAR *filenameU); -bool loadMusicUnthreaded(UNICHAR *filenameU); // for development testing +bool loadMusicUnthreaded(UNICHAR *filenameU, bool autoPlay); bool handleModuleLoadFromArg(int argc, char **argv); void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck); void handleLoadMusicEvents(void); void clearUnusedChannels(tonTyp *p, int16_t pattLen, uint8_t antChn); void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn); +void checkSampleRepeat(sampleTyp *s); diff --git a/src/ft2_module_saver.c b/src/ft2_module_saver.c @@ -13,7 +13,8 @@ #include "ft2_module_loader.h" /* These savers are directly ported, so they should act identical to FT2 -** except for some very minor changes. */ +** except for some very minor changes. +*/ static SDL_Thread *thread; @@ -31,9 +32,10 @@ bool saveXM(UNICHAR *filenameU) size_t result; songHeaderTyp h; patternHeaderTyp ph; + instrTyp *ins; instrHeaderTyp ih; - sampleTyp *srcSmp; - sampleHeaderTyp *dstSmp; + sampleTyp *s; + sampleHeaderTyp *dst; FILE *f; f = UNICHAR_FOPEN(filenameU, "wb"); @@ -135,6 +137,8 @@ bool saveXM(UNICHAR *filenameU) } } + memset(&ih, 0, sizeof (ih)); // important, clears reserved stuff + for (i = 1; i <= ai; i++) { if (instr[i] == NULL) @@ -153,19 +157,55 @@ bool saveXM(UNICHAR *filenameU) if (a > 0) { - memcpy(ih.ta, instr[j], INSTR_SIZE); + 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 = 1; k <= a; k++) + for (k = 0; k < a; k++) { - srcSmp = &instr[j]->samp[k-1]; - dstSmp = &ih.samp[k-1]; - - memset(dstSmp->name, ' ', 22); - - memcpy(dstSmp, srcSmp, 12+4+2 + strlen(srcSmp->name)); - if (srcSmp->pek == NULL) - dstSmp->len = 0; + 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 @@ -182,18 +222,18 @@ bool saveXM(UNICHAR *filenameU) for (k = 1; k <= a; k++) { - srcSmp = &instr[j]->samp[k-1]; - if (srcSmp->pek != NULL) + s = &instr[j]->samp[k-1]; + if (s->pek != NULL) { - restoreSample(srcSmp); - samp2Delta(srcSmp->pek, srcSmp->len, srcSmp->typ); + restoreSample(s); + samp2Delta(s->pek, s->len, s->typ); - result = fwrite(srcSmp->pek, 1, srcSmp->len, f); + result = fwrite(s->pek, 1, s->len, f); - delta2Samp(srcSmp->pek, srcSmp->len, srcSmp->typ); - fixSample(srcSmp); + delta2Samp(s->pek, s->len, s->typ); + fixSample(s); - if (result != (size_t)srcSmp->len) // write not OK + if (result != (size_t)s->len) // write not OK { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); diff --git a/src/ft2_pattern_draw.c b/src/ft2_pattern_draw.c @@ -560,18 +560,18 @@ static void drawRowNums(int32_t yPos, uint8_t row, bool selectedRowFlag) for (uint32_t y = 0; y < FONT4_CHAR_H; y++) { - for (uint32_t x1 = 0; x1 < FONT4_CHAR_W; x1++) + for (uint32_t x = 0; x < FONT4_CHAR_W; x++) { - if (src1Ptr[x1]) + if (src1Ptr[x]) { - dst1Ptr[x1] = pixVal; // left side - dst2Ptr[x1] = pixVal; // right side + dst1Ptr[x] = pixVal; // left side + dst2Ptr[x] = pixVal; // right side } - if (src2Ptr[x1]) + if (src2Ptr[x]) { - dst1Ptr[FONT4_CHAR_W + x1] = pixVal; // left side - dst2Ptr[FONT4_CHAR_W + x1] = pixVal; // right side + dst1Ptr[FONT4_CHAR_W+x] = pixVal; // left side + dst2Ptr[FONT4_CHAR_W+x] = pixVal; // right side } } @@ -898,6 +898,7 @@ void writePattern(int32_t currRow, int32_t pattern) noteTextColors[0] = video.palette[PAL_PATTEXT]; // not selected noteTextColors[1] = video.palette[PAL_FORGRND]; // selected + // increment pattern data pointer by horizontal scrollbar offset/channel if (pattPtr != NULL) pattPtr += editor.ui.channelOffset; @@ -976,7 +977,7 @@ void writePattern(int32_t currRow, int32_t pattern) void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color) { const uint8_t *ch1Ptr, *ch2Ptr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; ch1Ptr = &font4Ptr[(val >> 4) * FONT4_CHAR_W]; ch2Ptr = &font4Ptr[(val & 0x0F) * FONT4_CHAR_W]; @@ -986,8 +987,14 @@ void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color) { for (uint32_t x = 0; x < FONT4_CHAR_W; x++) { - if (ch1Ptr[x]) dstPtr[x] = color; - if (ch2Ptr[x]) dstPtr[FONT4_CHAR_W + x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (ch1Ptr[x] != 0) tmp = color; + dstPtr[x] = tmp; + + tmp = dstPtr[FONT4_CHAR_W+x]; + if (ch2Ptr[x] != 0) tmp = color; + dstPtr[FONT4_CHAR_W+x] = tmp; } ch1Ptr += FONT4_WIDTH; @@ -999,7 +1006,7 @@ void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color) static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color) { const uint8_t *srcPtr; - uint32_t x, y, *dstPtr; + uint32_t x, y, *dstPtr, tmp; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; @@ -1010,8 +1017,10 @@ static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontT { for (x = 0; x < FONT3_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT3_WIDTH; @@ -1025,8 +1034,10 @@ static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontT { for (x = 0; x < FONT4_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT4_WIDTH; @@ -1040,8 +1051,10 @@ static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontT { for (x = 0; x < FONT5_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT5_WIDTH; @@ -1055,8 +1068,10 @@ static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontT { for (x = 0; x < FONT7_CHAR_W; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT7_WIDTH; @@ -1068,7 +1083,7 @@ static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontT static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; srcPtr = &font7Data[18 * FONT7_CHAR_W]; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; @@ -1077,8 +1092,10 @@ static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT7_CHAR_W*3; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT7_WIDTH; @@ -1089,7 +1106,7 @@ static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color) static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; srcPtr = &font7Data[21 * FONT7_CHAR_W]; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + (xPos + 2)]; @@ -1098,8 +1115,10 @@ static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT7_CHAR_W*2; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT7_WIDTH; @@ -1111,7 +1130,7 @@ static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t co { const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr; uint8_t note; - uint32_t *dstPtr, char1, char2, char3; + uint32_t *dstPtr, char1, char2, char3, tmp; assert(ton >= 1 && ton <= 97); @@ -1140,9 +1159,18 @@ static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t co { for (uint32_t x = 0; x < FONT7_CHAR_W; x++) { - if (ch1Ptr[x]) dstPtr[ (FONT7_CHAR_W * 0) + x] = color; - if (ch2Ptr[x]) dstPtr[ (FONT7_CHAR_W * 1) + x] = color; - if (ch3Ptr[x]) dstPtr[((FONT7_CHAR_W * 2) - 2) + x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (ch1Ptr[x] != 0) tmp = color; + dstPtr[x] = tmp; + + tmp = dstPtr[FONT7_CHAR_W+x]; + if (ch2Ptr[x] != 0) tmp = color; + dstPtr[FONT7_CHAR_W+x] = tmp; + + tmp = dstPtr[((FONT7_CHAR_W*2)-2)+x]; // -2 to get correct alignment for ending glyph + if (ch3Ptr[x] != 0) tmp = color; + dstPtr[((FONT7_CHAR_W*2)-2)+x] = tmp; } ch1Ptr += FONT7_WIDTH; @@ -1155,7 +1183,7 @@ static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t co static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; srcPtr = &font4Ptr[43 * FONT4_CHAR_W]; @@ -1164,8 +1192,10 @@ static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT4_WIDTH; @@ -1176,7 +1206,7 @@ static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color) static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; srcPtr = &font4Ptr[40 * FONT4_CHAR_W]; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; @@ -1185,8 +1215,10 @@ static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT4_WIDTH; @@ -1197,7 +1229,7 @@ static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color) static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color) { const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr; - uint32_t note, *dstPtr, char1, char2, char3; + uint32_t note, *dstPtr, char1, char2, char3, tmp; ton--; @@ -1224,9 +1256,18 @@ static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t c { for (uint32_t x = 0; x < FONT4_CHAR_W; x++) { - if (ch1Ptr[x]) dstPtr[(FONT4_CHAR_W * 0) + x] = color; - if (ch2Ptr[x]) dstPtr[(FONT4_CHAR_W * 1) + x] = color; - if (ch3Ptr[x]) dstPtr[(FONT4_CHAR_W * 2) + x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (ch1Ptr[x] != 0) tmp = color; + dstPtr[x] = tmp; + + tmp = dstPtr[FONT4_CHAR_W+x]; + if (ch2Ptr[x] != 0) tmp = color; + dstPtr[FONT4_CHAR_W+x] = tmp; + + tmp = dstPtr[(FONT4_CHAR_W*2)+x]; + if (ch3Ptr[x] != 0) tmp = color; + dstPtr[(FONT4_CHAR_W*2)+x] = tmp; } ch1Ptr += FONT4_WIDTH; @@ -1239,7 +1280,7 @@ static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t c static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; srcPtr = &font4Ptr[67 * FONT4_CHAR_W]; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; @@ -1248,8 +1289,10 @@ static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT4_WIDTH; @@ -1260,7 +1303,7 @@ static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color) static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color) { const uint8_t *srcPtr; - uint32_t *dstPtr; + uint32_t *dstPtr, tmp; srcPtr = &font4Data[61 * FONT4_CHAR_W]; dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos]; @@ -1269,8 +1312,10 @@ static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color) { for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++) { - if (srcPtr[x]) - dstPtr[x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (srcPtr[x] != 0) tmp = color; + dstPtr[x] = tmp; } srcPtr += FONT4_WIDTH; @@ -1282,7 +1327,7 @@ static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t colo { const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr; uint8_t note; - uint32_t *dstPtr, char1, char2, char3; + uint32_t *dstPtr, char1, char2, char3, tmp; ton--; @@ -1309,9 +1354,18 @@ static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t colo { for (uint32_t x = 0; x < FONT5_CHAR_W; x++) { - if (ch1Ptr[x]) dstPtr[(FONT5_CHAR_W * 0) + x] = color; - if (ch2Ptr[x]) dstPtr[(FONT5_CHAR_W * 1) + x] = color; - if (ch3Ptr[x]) dstPtr[(FONT5_CHAR_W * 2) + x] = color; + // carefully written like this to generate conditional move instructions (font data is hard to predict) + tmp = dstPtr[x]; + if (ch1Ptr[x] != 0) tmp = color; + dstPtr[x] = tmp; + + tmp = dstPtr[FONT5_CHAR_W+x]; + if (ch2Ptr[x] != 0) tmp = color; + dstPtr[FONT5_CHAR_W+x] = tmp; + + tmp = dstPtr[(FONT5_CHAR_W*2)+x]; + if (ch3Ptr[x] != 0) tmp = color; + dstPtr[(FONT5_CHAR_W*2)+x] = tmp; } ch1Ptr += FONT5_WIDTH; diff --git a/src/ft2_pattern_ed.c b/src/ft2_pattern_ed.c @@ -59,7 +59,8 @@ bool allocatePattern(uint16_t nr) // for tracker use only, not in loader! * do that to avoid out of bondary row look-up between out-of-sync replayer * state and tracker state (yes it used to happen, rarely). We're not wasting * too much RAM for a modern computer anyway. Worst case: 256 allocated - * patterns would be ~10MB. */ + * patterns would be ~10MB. + */ patt[nr] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (patt[nr] == NULL) @@ -891,7 +892,7 @@ void handlePatternDataMouseDown(bool mouseButtonHeld) { if (mouse.x < 29) { - scrollBarScrollUp(SB_CHAN_SCROLL, 1); + scrollBarScrollUp(SB_CHAN_SCROLL, 1); forceMarking = true; } else if (mouse.x > 604) @@ -1517,6 +1518,7 @@ void jumpToChannel(uint8_t channel) // for ALT+q..i ALT+a..k if (editor.ui.pattChanScrollShown) { assert(song.antChn > editor.ui.numChannelsShown); + if (channel >= editor.ui.channelOffset+editor.ui.numChannelsShown) scrollBarScrollDown(SB_CHAN_SCROLL, (channel - (editor.ui.channelOffset + editor.ui.numChannelsShown)) + 1); else if (channel < editor.ui.channelOffset) @@ -2307,12 +2309,12 @@ void changeLogoType(uint8_t logoType) if (logoType == 0) { pushButtons[PB_LOGO].bitmapUnpressed = &ft2LogoBadges[(154 * 32) * 0]; - pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 1]; + pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 1]; } else { pushButtons[PB_LOGO].bitmapUnpressed = &ft2LogoBadges[(154 * 32) * 2]; - pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 3]; + pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 3]; } drawPushButton(PB_LOGO); @@ -2325,12 +2327,12 @@ void changeBadgeType(uint8_t badgeType) if (badgeType == 0) { pushButtons[PB_BADGE].bitmapUnpressed = &ft2InfoBadges[(25 * 32) * 0]; - pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 1]; + pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 1]; } else { pushButtons[PB_BADGE].bitmapUnpressed = &ft2InfoBadges[(25 * 32) * 2]; - pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 3]; + pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 3]; } drawPushButton(PB_BADGE); @@ -2701,7 +2703,7 @@ static void zapSong(void) song.songPos = 0; song.globVol = 64; - memset(song.name, 0, sizeof (song.name)); + memset(song.name, 0, sizeof (song.name)); memset(song.songTab, 0, sizeof (song.songTab)); // zero all pattern data and reset pattern lengths @@ -2916,7 +2918,7 @@ void expandPattern(void) editor.pattPos = song.pattPos; editor.ui.updatePatternEditor = true; - editor.ui.updatePosSections = true; + editor.ui.updatePosSections = true; unlockMixerCallback(); setSongModifiedFlag(); diff --git a/src/ft2_replayer.c b/src/ft2_replayer.c @@ -419,7 +419,7 @@ static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch) ch->instrSeg = ins; ch->mute = ins->mute; - if (ton > 96) // non-FT2 security (should never happen because I clamp in the patt loaders now) + if (ton > 96) // non-FT2 security (should never happen because I clamp in the patt. loader now) ton = 96; smp = ins->ta[ton-1] & 0xF; @@ -1114,7 +1114,7 @@ static void getNewNote(stmTyp *ch, tonTyp *p) ch->eff = p->eff; ch->tonTyp = (p->instr << 8) | p->ton; - if (ch->stOff == 1) + if (ch->stOff) { checkMoreEffects(ch); return; @@ -1233,7 +1233,7 @@ static void fixaEnvelopeVibrato(stmTyp *ch) } } - if (ch->mute != 1) + if (!ch->mute) { // *** VOLUME ENVELOPE *** envVal = 0; diff --git a/src/ft2_replayer.h b/src/ft2_replayer.h @@ -34,8 +34,8 @@ enum // DO NOT TOUCH! #define MIN_BPM 32 -#define TRACK_WIDTH (5 * MAX_VOICES) #define MAX_VOICES 32 +#define TRACK_WIDTH (5 * MAX_VOICES) #define MAX_NOTES ((12 * 10 * 16) + 16) #define MAX_PATTERNS 256 #define MAX_PATT_LEN 256 @@ -43,7 +43,6 @@ enum #define MAX_SMP_PER_INST 16 #define MAX_ORDERS 256 #define STD_ENV_SIZE ((6*2*12*2*2) + (6*8*2) + (6*5*2) + (6*2*2)) -#define INSTR_SIZE 232 #define INSTR_HEADER_SIZE 263 #define INSTR_XI_HEADER_SIZE 298 #define MAX_SAMPLE_LEN 0x3FFFFFFF @@ -123,7 +122,7 @@ typedef struct sampleHeaderTyp_t // DO NOT TOUCH! int8_t fine; uint8_t typ, pan; int8_t relTon; - uint8_t reserved; + uint8_t nameLen; char name[22]; } #ifdef __GNUC__ @@ -157,67 +156,51 @@ __attribute__ ((packed)) #endif instrHeaderTyp; -typedef struct sampleTyp_t // DO NOT TOUCH! -{ - int32_t len, repS, repL; - uint8_t vol; - int8_t fine; - uint8_t typ, pan; - int8_t relTon; - uint8_t reserved; - char name[22 + 1]; // +1 for tracker NUL termination, not present in sample header +#ifdef _MSC_VER +#pragma pack(pop) +#endif - // stuff from now on can be touched - int8_t *pek; - uint8_t fixed; +typedef struct sampleTyp_t +{ + char name[22+1]; + bool fixed; + int8_t fine, relTon, *pek; + uint8_t vol, typ, pan; int16_t fixedSmp1; #ifndef LERPMIX int16_t fixedSmp2; #endif - int32_t fixedPos; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -sampleTyp; + int32_t fixedPos, len, repS, repL; +} sampleTyp; -typedef struct instrTyp_t // DO NOT TOUCH! +typedef struct instrTyp_t { - uint8_t ta[96]; - int16_t envVP[12][2], envPP[12][2]; + bool midiOn, mute; + uint8_t midiChannel, ta[96]; uint8_t envVPAnt, envPPAnt; uint8_t envVSust, envVRepS, envVRepE; uint8_t envPSust, envPRepS, envPRepE; uint8_t envVTyp, envPTyp; uint8_t vibTyp, vibSweep, vibDepth, vibRate; uint16_t fadeOut; - uint8_t midiOn, midiChannel; - int16_t midiProgram, midiBend; - uint8_t mute, reserved[15]; - int16_t antSamp; + int16_t envVP[12][2], envPP[12][2], midiProgram, midiBend; + int16_t antSamp; // used by loader only sampleTyp samp[16]; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -instrTyp; -#ifdef _MSC_VER -#pragma pack(pop) -#endif +} instrTyp; typedef struct stmTyp_t { + bool envSustainActive, stOff, mute; volatile uint8_t status, tmpStatus; int8_t relTonNr, fineTune; - uint8_t sampleNr, instrNr, stOff, effTyp, eff, smpOffset, tremorSave, tremorPos; - uint8_t globVolSlideSpeed, panningSlideSpeed, mute, waveCtrl, portaDir; + uint8_t sampleNr, instrNr, effTyp, eff, smpOffset, tremorSave, tremorPos; + uint8_t globVolSlideSpeed, panningSlideSpeed, waveCtrl, portaDir; uint8_t glissFunk, vibPos, tremPos, vibSpeed, vibDepth, tremSpeed, tremDepth; uint8_t pattPos, loopCnt, volSlideSpeed, fVolSlideUpSpeed, fVolSlideDownSpeed; uint8_t fPortaUpSpeed, fPortaDownSpeed, ePortaUpSpeed, ePortaDownSpeed; uint8_t portaUpSpeed, portaDownSpeed, retrigSpeed, retrigCnt, retrigVol; uint8_t volKolVol, tonNr, envPPos, eVibPos, envVPos, realVol, oldVol, outVol; uint8_t oldPan, outPan, finalPan; - bool envSustainActive; int16_t midiCurChannel, midiCurTone, midiCurVibDepth, midiCurPeriod, midiCurPitch; int16_t midiBend, midiPortaPeriod, midiPitch, realPeriod, envVIPValue, envPIPValue; uint16_t finalVol, outPeriod, finalPeriod, tonTyp, wantPeriod, portaSpeed; @@ -230,15 +213,13 @@ typedef struct stmTyp_t typedef struct songTyp_t { - uint8_t antChn, pattDelTime, pattDelTime2, pBreakPos, songTab[MAX_ORDERS]; bool pBreakFlag, posJumpFlag, isModified; + char name[20+1], instrName[1+MAX_INST][22+1]; + uint8_t curReplayerTimer, curReplayerPattPos, curReplayerSongPos, curReplayerPattNr; // used for audio/video sync queue + uint8_t antChn, pattDelTime, pattDelTime2, pBreakPos, songTab[MAX_ORDERS]; int16_t songPos, pattNr, pattPos, pattLen; uint16_t len, repS, speed, tempo, globVol, timer, ver, initialTempo; - char name[20 + 1], instrName[1 + MAX_INST][22 + 1]; uint32_t musicTime; - - // used for audio/video sync queue - uint8_t curReplayerTimer, curReplayerPattPos, curReplayerSongPos, curReplayerPattNr; } songTyp; typedef struct tonTyp_t diff --git a/src/ft2_sysreqs.c b/src/ft2_sysreqs.c @@ -612,6 +612,9 @@ int16_t inputBox(int16_t typ, const char *headline, char *edText, uint16_t maxSt // WARNING: This routine must NOT be called from the main input/video thread! int16_t okBoxThreadSafe(int16_t typ, const char *headline, const char *text) { + if (!editor.mainLoopOngoing) + return 0; // main loop was not even started yet, bail out. + // block multiple calls before they are completed (for safety) while (okBoxData.active) SDL_Delay(1000 / VBLANK_HZ); diff --git a/src/ft2_wav_renderer.c b/src/ft2_wav_renderer.c @@ -156,7 +156,6 @@ void exitWavRenderer(void) static bool dump_Init(uint32_t frq, int16_t amp, int16_t songPos) { - uint8_t i, oldMuteFlags[MAX_VOICES]; uint32_t maxSamplesPerTick, sampleSize; maxSamplesPerTick = ((frq * 5) / 2) / MIN_BPM; // absolute max samples per tidck @@ -173,16 +172,7 @@ static bool dump_Init(uint32_t frq, int16_t amp, int16_t songPos) playMode = PLAYMODE_SONG; songPlaying = true; - // store mute states - for (i = 0; i < MAX_VOICES; i++) - oldMuteFlags[i] = stm[i].stOff; - resetChannels(); - - // restore mute states - for (i = 0; i < MAX_VOICES; i++) - stm[i].stOff = oldMuteFlags[i]; - setNewAudioFreq(frq); setAudioAmp(amp, config.masterVol, (WDBitDepth == 32)); diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj b/vs2019_project/ft2-clone/ft2-clone.vcxproj @@ -26,13 +26,11 @@ </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <UseDebugLibraries>true</UseDebugLibraries> <PlatformToolset>v142</PlatformToolset> <CharacterSet>MultiByte</CharacterSet> <WholeProgramOptimization>true</WholeProgramOptimization> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> - <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>MultiByte</CharacterSet> <WholeProgramOptimization>true</WholeProgramOptimization> <PlatformToolset>v142</PlatformToolset>