ref: 8391cc5ac8ad74fa3d08c7228cb4a30ac657c56f
parent: 1533d894234ecef766ee830d923c58bf0c8cda9b
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Feb 23 12:25:13 EST 2024
Add support for loading .BEM files (UN05, XM only)
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -26,6 +26,9 @@
#include "ft2_structs.h"
#include "ft2_sysreqs.h"
+bool detectBEM(FILE* f);
+bool loadBEM(FILE* f, uint32_t filesize);
+
bool loadDIGI(FILE *f, uint32_t filesize);
bool loadMOD(FILE *f, uint32_t filesize);
bool loadS3M(FILE *f, uint32_t filesize);
@@ -41,7 +44,8 @@
FORMAT_MOD = 3,
FORMAT_S3M = 4,
FORMAT_STM = 5,
- FORMAT_DIGI = 6
+ FORMAT_DIGI = 6,
+ FORMAT_BEM = 7
};
// file extensions accepted by Disk Op. in module mode
@@ -48,7 +52,7 @@
char *supportedModExtensions[] =
{
"xm", "ft", "nst", "stk", "mod", "s3m", "stm", "fst",
- "digi",
+ "digi", "bem",
// IMPORTANT: Remember comma after last entry above
"END_OF_LIST" // do NOT move, remove or edit this line!
@@ -84,6 +88,10 @@
fread(I, 1, 4, f);
rewind(f);
+ // BEM ("UN05", from XM only, MikMod)
+ if (detectBEM(f))
+ return FORMAT_BEM;
+
// DIGI Booster (non-Pro)
if (!memcmp("DIGI Booster module", &D[0x00], 19+1) && D[0x19] >= 1 && D[0x19] <= 8)
return FORMAT_DIGI;
@@ -193,6 +201,7 @@
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;
+ case FORMAT_BEM: moduleLoaded = loadBEM(f, filesize); break;
default:
loaderMsgBox("This file is not a supported module!");
--- /dev/null
+++ b/src/modloaders/ft2_load_bem.c
@@ -1,0 +1,404 @@
+/* BEM (UN05, MikMod) loader. Supports modules converted from XM only!
+**
+** Note: Data sanitation is done in the last stage
+** of module loading, so you don't need to do that here.
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "../ft2_header.h"
+#include "../ft2_module_loader.h"
+#include "../ft2_sample_ed.h"
+#include "../ft2_sysreqs.h"
+
+#define MAX_TRACKS (256*32)
+
+#define FLAG_XMPERIODS 1
+#define FLAG_LINEARSLIDES 2
+
+#ifdef _MSC_VER // please don't mess with these structs!
+#pragma pack(push)
+#pragma pack(1)
+#endif
+typedef struct bemHdr_t
+{
+ char id[4];
+ uint8_t numchn;
+ uint16_t numpos;
+ uint16_t reppos;
+ uint16_t numpat;
+ uint16_t numtrk;
+ uint16_t numins;
+ uint8_t initspeed;
+ uint8_t inittempo;
+ uint8_t positions[256];
+ uint8_t panning[32];
+ uint8_t flags;
+}
+#ifdef __GNUC__
+__attribute__((packed))
+#endif
+bemHdr_t;
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif
+
+enum
+{
+ UNI_NOTE = 1,
+ UNI_INSTRUMENT,
+ UNI_PTEFFECT0,
+ UNI_PTEFFECT1,
+ UNI_PTEFFECT2,
+ UNI_PTEFFECT3,
+ UNI_PTEFFECT4,
+ UNI_PTEFFECT5,
+ UNI_PTEFFECT6,
+ UNI_PTEFFECT7,
+ UNI_PTEFFECT8,
+ UNI_PTEFFECT9,
+ UNI_PTEFFECTA,
+ UNI_PTEFFECTB,
+ UNI_PTEFFECTC,
+ UNI_PTEFFECTD,
+ UNI_PTEFFECTE,
+ UNI_PTEFFECTF,
+ UNI_S3MEFFECTA,
+ UNI_S3MEFFECTD,
+ UNI_S3MEFFECTE,
+ UNI_S3MEFFECTF,
+ UNI_S3MEFFECTI,
+ UNI_S3MEFFECTQ,
+ UNI_S3MEFFECTT,
+ UNI_XMEFFECTA,
+ UNI_XMEFFECTG,
+ UNI_XMEFFECTH,
+ UNI_XMEFFECTP,
+
+ UNI_LAST
+};
+
+static const uint8_t xmEfxTab[] = { 10, 16, 17, 25 }; // A, G, H, P
+
+static char *readString(FILE *f)
+{
+ uint16_t length;
+ fread(&length, 2, 1, f);
+
+ char *out = (char *)malloc(length+1);
+ if (out == NULL)
+ return NULL;
+
+ fread(out, 1, length, f);
+ out[length] = '\0';
+
+ return out;
+}
+
+bool detectBEM(FILE *f)
+{
+ if (f == NULL) return false;
+
+ uint32_t oldPos = (uint32_t)ftell(f);
+
+ fseek(f, 0, SEEK_SET);
+ char ID[64];
+ memset(ID, 0, sizeof (ID));
+ fread(ID, 1, 4, f);
+ if (memcmp(ID, "UN05", 4) != 0)
+ goto error;
+
+ fseek(f, 0x131, SEEK_SET);
+ if (feof(f))
+ goto error;
+
+ uint8_t flags = (uint8_t)fgetc(f);
+ if ((flags & FLAG_XMPERIODS) == 0)
+ goto error;
+
+ fseek(f, 0x132, SEEK_SET);
+ if (feof(f))
+ goto error;
+
+ uint16_t strLength = 0;
+ fread(&strLength, 2, 1, f);
+ if (strLength == 0 || strLength > 512)
+ goto error;
+
+ fseek(f, strLength+2, SEEK_CUR);
+ if (feof(f))
+ goto error;
+
+ fread(ID, 1, 64, f);
+ if (memcmp(ID, "FastTracker v2.00", 17) != 0)
+ goto error;
+
+ fseek(f, oldPos, SEEK_SET);
+ return true;
+
+error:
+ fseek(f, oldPos, SEEK_SET);
+ return false;
+}
+
+bool loadBEM(FILE *f, uint32_t filesize)
+{
+ bemHdr_t h;
+
+ if (filesize < sizeof (h))
+ {
+ loaderMsgBox("Error: This file is either not a module, or is not supported.");
+ return false;
+ }
+
+ fread(&h, 1, sizeof (bemHdr_t), f);
+
+ char *songName = readString(f);
+ if (songName == NULL)
+ return false;
+
+ strcpy(songTmp.name, songName);
+ free(songName);
+ uint16_t strLength;
+ fread(&strLength, 2, 1, f);
+ fseek(f, strLength, SEEK_CUR);
+ fread(&strLength, 2, 1, f);
+ fseek(f, strLength, SEEK_CUR);
+
+ if (h.numpos > 256 || h.numpat > 256 || h.numchn > 32 || h.numtrk > MAX_TRACKS)
+ {
+ loaderMsgBox("Error loading BEM: The module is corrupt!");
+ return false;
+ }
+
+ tmpLinearPeriodsFlag = !!(h.flags & FLAG_LINEARSLIDES);
+
+ songTmp.numChannels = h.numchn;
+ songTmp.songLength = h.numpos;
+ songTmp.songLoopStart = h.reppos;
+ songTmp.BPM = h.inittempo;
+ songTmp.speed = h.initspeed;
+
+ memcpy(songTmp.orders, h.positions, 256);
+
+ // load instruments
+ for (int16_t i = 0; i < h.numins; i++)
+ {
+ if (!allocateTmpInstr(1 + i))
+ {
+ loaderMsgBox("Not enough memory!");
+ return false;
+ }
+
+ instr_t *ins = instrTmp[1 + i];
+
+ ins->numSamples = (uint8_t)fgetc(f);
+ fread(ins->note2SampleLUT, 1, 96, f);
+
+ ins->volEnvFlags = (uint8_t)fgetc(f);
+ ins->volEnvLength = (uint8_t)fgetc(f);
+ ins->volEnvSustain = (uint8_t)fgetc(f);
+ ins->volEnvLoopStart = (uint8_t)fgetc(f);
+ ins->volEnvLoopEnd = (uint8_t)fgetc(f);
+ fread(ins->volEnvPoints, 2, 12*2, f);
+
+ ins->panEnvFlags = (uint8_t)fgetc(f);
+ ins->panEnvLength = (uint8_t)fgetc(f);
+ ins->panEnvSustain = (uint8_t)fgetc(f);
+ ins->panEnvLoopStart = (uint8_t)fgetc(f);
+ ins->panEnvLoopEnd = (uint8_t)fgetc(f);
+ fread(ins->panEnvPoints, 2, 12*2, f);
+
+ ins->autoVibType = (uint8_t)fgetc(f);
+ ins->autoVibSweep = (uint8_t)fgetc(f);
+ ins->autoVibDepth = (uint8_t)fgetc(f);
+ ins->autoVibRate = (uint8_t)fgetc(f);
+ fread(&ins->fadeout, 2, 1, f);
+
+ char *insName = readString(f);
+ if (insName == NULL)
+ return false;
+
+ uint32_t insNameLen = (uint32_t)strlen(insName);
+ if (insNameLen > 22)
+ insNameLen = 22;
+
+ memcpy(songTmp.instrName[1+i], insName, insNameLen);
+ free(insName);
+
+ for (int32_t j = 0; j < ins->numSamples; j++)
+ {
+ sample_t *s = &ins->smp[j];
+
+ s->finetune = (int8_t)fgetc(f) ^ 0x80;
+ fseek(f, 1, SEEK_CUR);
+ s->relativeNote = (int8_t)fgetc(f);
+ s->volume = (uint8_t)fgetc(f);
+ s->panning = (uint8_t)fgetc(f);
+ fread(&s->length, 4, 1, f);
+ fread(&s->loopStart, 4, 1, f);
+ uint32_t loopEnd;
+ fread(&loopEnd, 4, 1, f);
+ s->loopLength = loopEnd - s->loopStart;
+
+ uint16_t flags;
+ fread(&flags, 2, 1, f);
+ if (flags & 1) s->flags |= SAMPLE_16BIT;
+ if (flags & 16) s->flags |= LOOP_FWD;
+ if (flags & 32) s->flags |= LOOP_BIDI;
+
+ char *smpName = readString(f);
+ if (smpName == NULL)
+ return false;
+
+ uint32_t smpNameLen = (uint32_t)strlen(smpName);
+ if (smpNameLen > 22)
+ smpNameLen = 22;
+
+ memcpy(s->name, smpName, smpNameLen);
+ free(smpName);
+ }
+ }
+
+ // load tracks
+
+ uint16_t rowsInPattern[256];
+ uint16_t trackList[256*32];
+ fread(rowsInPattern, 2, h.numpat, f);
+ fread(trackList, 2, h.numpat * h.numchn, f);
+
+ note_t *decodedTrack[MAX_TRACKS];
+ for (int32_t i = 0; i < h.numtrk; i++)
+ {
+ uint16_t trackBytesInFile;
+ fread(&trackBytesInFile, 2, 1, f);
+ if (trackBytesInFile == 0)
+ {
+ loaderMsgBox("Error loading BEM: This module is corrupt!");
+trackError:
+ for (int32_t j = 0; j < i; j++)
+ free(decodedTrack[j]);
+
+ return false;
+ }
+
+ decodedTrack[i] = (note_t *)calloc(rowsInPattern[i], sizeof (note_t));
+ if (decodedTrack[i] == NULL)
+ {
+ loaderMsgBox("Not enough memory!");
+ goto trackError;
+ }
+
+ note_t *out = decodedTrack[i];
+
+ // decode track
+
+ uint32_t trackPosInFile = (uint32_t)ftell(f);
+ while ((uint32_t)ftell(f) < trackPosInFile+trackBytesInFile)
+ {
+ uint8_t byte = (uint8_t)fgetc(f);
+ if (byte == 0)
+ break; // end of track
+
+ uint8_t repeat = byte >> 5;
+ uint8_t opcodeBytes = (byte & 0x1F) - 1;
+
+ uint32_t opcodeStart = (uint32_t)ftell(f);
+ uint32_t opcodeEnd = opcodeStart + opcodeBytes;
+
+ for (int32_t j = 0; j <= repeat; j++, out++)
+ {
+ fseek(f, opcodeStart, SEEK_SET);
+ while ((uint32_t)ftell(f) < opcodeEnd)
+ {
+ uint8_t opcode = (uint8_t)fgetc(f);
+
+ if (opcode == 0)
+ break;
+
+ if (opcode == UNI_NOTE)
+ {
+ out->note = 1 + (uint8_t)fgetc(f);
+ }
+ else if (opcode == UNI_INSTRUMENT)
+ {
+ out->instr = 1 + (uint8_t)fgetc(f);
+ }
+ else if (opcode >= UNI_PTEFFECT0 && opcode <= UNI_PTEFFECTF) // PT effects
+ {
+ out->efx = opcode - UNI_PTEFFECT0;
+ out->efxData = (uint8_t)fgetc(f);
+ }
+ else if (opcode >= UNI_XMEFFECTA && opcode <= UNI_XMEFFECTP) // XM effects
+ {
+ out->efx = xmEfxTab[opcode-UNI_XMEFFECTA];
+ out->efxData = (uint8_t)fgetc(f);
+ }
+ else
+ {
+ if (opcode >= UNI_LAST) // illegal opcode
+ {
+ loaderMsgBox("Error loading BEM: illegal pattern opcode!");
+ goto trackError;
+ }
+
+ // unsupported opcode, skip it
+ if (opcode > 0)
+ fseek(f, 1, SEEK_CUR);
+ }
+ }
+ }
+ }
+ }
+
+ // create patterns from tracks
+ for (int32_t i = 0; i < h.numpat; i++)
+ {
+ uint16_t numRows = rowsInPattern[i];
+ if (numRows == 0 || numRows > 256)
+ continue;
+
+ if (!allocateTmpPatt(i, numRows))
+ {
+ loaderMsgBox("Not enough memory!");
+ return false;
+ }
+
+ note_t *dst = patternTmp[i];
+ for (int32_t j = 0; j < h.numchn; j++)
+ {
+ note_t *src = (note_t *)decodedTrack[trackList[(i * h.numchn) + j]];
+ if (src != NULL)
+ {
+ for (int32_t k = 0; k < numRows; k++)
+ dst[(k * MAX_CHANNELS) + j] = src[k];
+ }
+ }
+ }
+
+ // load sample data
+ for (int32_t i = 0; i < h.numins; i++)
+ {
+ instr_t *ins = instrTmp[1 + i];
+ if (ins == NULL)
+ continue;
+
+ for (int32_t j = 0; j < ins->numSamples; j++)
+ {
+ sample_t *s = &ins->smp[j];
+
+ bool sampleIs16Bit = !!(s->flags & SAMPLE_16BIT);
+ if (!allocateSmpData(s, s->length, sampleIs16Bit))
+ {
+ loaderMsgBox("Not enough memory!");
+ return false;
+ }
+
+ fread(s->dataPtr, 1 + sampleIs16Bit, s->length, f);
+ delta2Samp(s->dataPtr, s->length, s->flags);
+ }
+ }
+
+ return true;
+}
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -361,6 +361,7 @@
<ClCompile Include="..\..\src\modloaders\ft2_load_s3m.c" />
<ClCompile Include="..\..\src\modloaders\ft2_load_stk.c" />
<ClCompile Include="..\..\src\modloaders\ft2_load_stm.c" />
+ <ClCompile Include="..\..\src\modloaders\ft2_load_bem.c" />
<ClCompile Include="..\..\src\modloaders\ft2_load_xm.c" />
<ClCompile Include="..\..\src\rtmidi\RtMidi.cpp">
<ExceptionHandling Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Sync</ExceptionHandling>
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
@@ -164,6 +164,9 @@
<ClCompile Include="..\..\src\mixer\ft2_cubic_spline.c">
<Filter>mixer</Filter>
</ClCompile>
+ <ClCompile Include="..\..\src\modloaders\ft2_load_bem.c">
+ <Filter>modloaders</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\rtmidi\RtMidi.h">