ft2-clone

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

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:
MREADME.md | 4++--
Msrc/ft2_sample_loader.c | 12++++++++++--
Asrc/smploaders/ft2_load_brr.c | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mvs2019_project/ft2-clone/ft2-clone.vcxproj | 1+
Mvs2019_project/ft2-clone/ft2-clone.vcxproj.filters | 3+++
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">