shithub: ft²

Download patch

ref: ccbbd41e5d209e32bea9edb123aa5261d42e4ad3
parent: 544e74e4829b1856bd7a60de24317af9f461fdb5
author: Olav Sørensen <olav.sorensen@live.no>
date: Wed Nov 6 16:37:30 EST 2024

.BRR sample loader support

--- a/README.md
+++ b/README.md
@@ -14,8 +14,8 @@
 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
--- 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,7 +34,8 @@
 	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
@@ -38,7 +42,7 @@
 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 @@
 	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 @@
 		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);
--- /dev/null
+++ b/src/smploaders/ft2_load_brr.c
@@ -1,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;
+}
--- 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" />
--- 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">
--