commit ccbbd41e5d209e32bea9edb123aa5261d42e4ad3
parent 544e74e4829b1856bd7a60de24317af9f461fdb5
Author: Olav Sørensen <olav.sorensen@live.no>
Date: Wed, 6 Nov 2024 21:37:03 +0100
.BRR sample loader support
Diffstat:
5 files changed, 198 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
@@ -14,8 +14,8 @@ Linux binaries can be found [here](https://repology.org/project/fasttracker2/ver
If these don't work for you, you'll have to compile the code manually.
# Improvements over original DOS version
-- The channel resampler/mixer uses floating-point arithmetics for less errors, and has extra interpolation options (4-point cubic spline, 8-point/16-point windowed-sinc)
-- The sample loader supports FLAC/AIFF samples and more WAV types than original FT2. It will also attempt to tune the sample (finetune and rel. note) to its playback frequency on load.
+- The channel resampler/mixer uses floating-point arithmetics for less errors, and has extra interpolation options (4-point "Gaussian" (SNES), 4-point cubic Hermite spline, 8-point/16-point windowed-sinc)
+- The sample loader supports FLAC/AIFF/BRR (SNES) samples and more WAV types than original FT2. It will also attempt to tune the sample (finetune and rel. note) to its playback frequency on load.
- It contains a new "Trim" feature, which will remove unused stuff to potentially make the module smaller
- Drag n' drop of modules/samples
- The waveform display in the sample editor shows peak based data when zoomed out
diff --git a/src/ft2_sample_loader.c b/src/ft2_sample_loader.c
@@ -20,6 +20,9 @@
bool loadFLAC(FILE *f, uint32_t filesize);
#endif
+bool detectBRR(FILE *f);
+bool loadBRR(FILE *f, uint32_t filesize);
+
bool loadAIFF(FILE *f, uint32_t filesize);
bool loadIFF(FILE *f, uint32_t filesize);
bool loadRAW(FILE *f, uint32_t filesize);
@@ -31,14 +34,15 @@ enum
FORMAT_IFF = 1,
FORMAT_WAV = 2,
FORMAT_AIFF = 3,
- FORMAT_FLAC = 4
+ FORMAT_FLAC = 4,
+ FORMAT_BRR = 5
};
// file extensions accepted by Disk Op. in sample mode
char *supportedSmpExtensions[] =
{
"iff", "raw", "wav", "snd", "smp", "sam", "aif", "pat",
- "aiff","flac", // IMPORTANT: Remember comma after last entry!!!
+ "aiff","flac","brr", // IMPORTANT: Remember comma after last entry!!!
"END_OF_LIST" // do NOT move, remove or edit this line!
};
@@ -78,6 +82,9 @@ static int8_t detectSample(FILE *f)
if (!memcmp("FORM", &D[0], 4) && (!memcmp("AIFF", &D[8], 4) || !memcmp("AIFC", &D[8], 4)))
return FORMAT_AIFF;
+ if (detectBRR(f))
+ return FORMAT_BRR;
+
return FORMAT_UNKNOWN;
}
@@ -125,6 +132,7 @@ static int32_t SDLCALL loadSampleThread(void *ptr)
case FORMAT_IFF: sampleLoaded = loadIFF(f, filesize); break;
case FORMAT_WAV: sampleLoaded = loadWAV(f, filesize); break;
case FORMAT_AIFF: sampleLoaded = loadAIFF(f, filesize); break;
+ case FORMAT_BRR: sampleLoaded = loadBRR(f, filesize); break;
default: sampleLoaded = loadRAW(f, filesize); break;
}
fclose(f);
diff --git a/src/smploaders/ft2_load_brr.c b/src/smploaders/ft2_load_brr.c
@@ -0,0 +1,182 @@
+/* Super Nintendo BRR sample loader (based on work by _astriid_, but heavily modified)
+**
+** Note: Vol/loop sanitation is done in the last stage
+** of sample loading, so you don't need to do that here.
+** Do NOT close the file handle!
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "../ft2_header.h"
+#include "../ft2_sample_ed.h"
+#include "../ft2_sysreqs.h"
+#include "../ft2_sample_loader.h"
+
+#define BRR_RATIO(x) (((x) * 16) / 9)
+
+static int16_t s1, s2;
+
+bool detectBRR(FILE *f)
+{
+ if (f == NULL)
+ return false;
+
+ uint32_t oldPos = (uint32_t)ftell(f);
+ fseek(f, 0, SEEK_END);
+ uint32_t filesize = (uint32_t)ftell(f);
+
+ const uint32_t filesizeMod9 = filesize % 9;
+
+ if (filesize < 11 || filesize > 65536 || (filesizeMod9 != 0 && filesizeMod9 != 2))
+ goto error; // definitely not a BRR file
+
+ rewind(f);
+
+ uint32_t blockBytes = filesize;
+ if (filesizeMod9 == 2) // skip loop block word
+ {
+ fseek(f, 2, SEEK_CUR);
+ blockBytes -= 2;
+ }
+
+ uint32_t numBlocks = blockBytes / 9;
+
+ // if the first block is the last, this is very unlikely to be a real BRR sample
+ uint8_t header = (uint8_t)fgetc(f);
+ if (header & 1)
+ goto error;
+
+ /* Test the shift range of a few blocks.
+ ** While it's possible to have a shift value above 12 (illegal),
+ ** it's rare in dumped BRR samples. I have personally seen 13,
+ ** but never above, so let's test for >13 then.
+ */
+ uint32_t blocksToTest = 8;
+ if (blocksToTest > numBlocks)
+ blocksToTest = numBlocks;
+
+ for (uint32_t i = 0; i < blocksToTest; i++)
+ {
+ const uint8_t shift = header >> 4;
+ if (shift > 13)
+ goto error;
+
+ fseek(f, 8, SEEK_CUR);
+ header = (uint8_t)fgetc(f);
+ }
+
+ fseek(f, oldPos, SEEK_SET);
+ return true;
+
+error:
+ fseek(f, oldPos, SEEK_SET);
+ return false;
+}
+
+static int16_t decodeSample(int8_t nybble, int32_t shift, int32_t filter)
+{
+ int32_t smp = (nybble << shift) >> 1;
+ if (shift >= 13)
+ smp &= ~2047; // invalid shift clamping
+
+ switch (filter)
+ {
+ default: break;
+
+ case 1:
+ smp += (s1 * 15) >> 4;
+ break;
+
+ case 2:
+ smp += (s1 * 61) >> 5;
+ smp -= (s2 * 15) >> 4;
+ break;
+
+ case 3:
+ smp += (s1 * 115) >> 6;
+ smp -= (s2 * 13) >> 4;
+ break;
+ }
+
+ // clamp as 16-bit, even if we decode into a 15-bit sample
+ smp = CLAMP(smp, -32768, 32767);
+
+ // 15-bit clip
+ if (smp & 16384)
+ smp |= ~16383;
+ else
+ smp &= 16383;
+
+ // shuffle last samples (store as 15-bit sample)
+ s2 = s1;
+ s1 = (int16_t)smp;
+
+ return (int16_t)(smp << 1); // multiply by two to get 16-bit scale
+}
+
+bool loadBRR(FILE *f, uint32_t filesize)
+{
+ sample_t *s = &tmpSmp;
+
+ uint32_t blockBytes = filesize, loopStart = 0;
+ if ((filesize % 9) == 2) // loop header present
+ {
+ uint16_t loopStartBlock;
+ fread(&loopStartBlock, 2, 1, f);
+ loopStart = BRR_RATIO(loopStartBlock);
+ blockBytes -= 2;
+ }
+
+ uint32_t sampleLength = BRR_RATIO(blockBytes);
+ if (!allocateSmpData(s, sampleLength, true))
+ {
+ loaderMsgBox("Not enough memory!");
+ return false;
+ }
+
+ uint32_t shift = 0, filter = 0;
+ bool loopFlag = false, endFlag = false;
+
+ s1 = s2 = 0; // clear last BRR samples (for decoding)
+
+ int16_t *ptr16 = (int16_t *)s->dataPtr;
+ for (uint32_t i = 0; i < blockBytes; i++)
+ {
+ const uint32_t blockOffset = i % 9;
+ const uint8_t byte = (uint8_t)fgetc(f);
+
+ if (blockOffset == 0) // this byte is the BRR header
+ {
+ shift = byte >> 4;
+ filter = (byte & 0x0C) >> 2;
+ loopFlag = !!(byte & 0x02);
+ endFlag = !!(byte & 0x01);
+ continue;
+ }
+
+ // decode samples
+ *ptr16++ = decodeSample((int8_t)byte >> 4, shift, filter);
+ *ptr16++ = decodeSample((int8_t)(byte << 4) >> 4, shift, filter);
+
+ if (endFlag && blockOffset == 8)
+ {
+ sampleLength = BRR_RATIO(i+1);
+ break;
+ }
+ }
+
+ s->volume = 64;
+ s->panning = 128;
+ s->flags |= SAMPLE_16BIT;
+ s->length = sampleLength;
+
+ if (loopFlag) // XXX: Maybe this is not how to do it..?
+ {
+ s->flags |= LOOP_FWD;
+ s->loopStart = loopStart;
+ s->loopLength = sampleLength - loopStart;
+ }
+
+ return true;
+}
diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -383,6 +383,7 @@
<ClCompile Include="..\..\src\scopes\ft2_scopedraw.c" />
<ClCompile Include="..\..\src\scopes\ft2_scopes.c" />
<ClCompile Include="..\..\src\smploaders\ft2_load_aiff.c" />
+ <ClCompile Include="..\..\src\smploaders\ft2_load_brr.c" />
<ClCompile Include="..\..\src\smploaders\ft2_load_flac.c" />
<ClCompile Include="..\..\src\smploaders\ft2_load_iff.c" />
<ClCompile Include="..\..\src\smploaders\ft2_load_raw.c" />
diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
@@ -170,6 +170,9 @@
<Filter>mixer</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ft2_diskop.c" />
+ <ClCompile Include="..\..\src\smploaders\ft2_load_brr.c">
+ <Filter>smploaders</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\rtmidi\RtMidi.h">