From 1961b134aff2e1226c070d2b006b889b7e80dc5d Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Thu, 14 Sep 2023 01:36:04 +0900 Subject: [PATCH] Support simple but heavily compressed custom .sfo files Only available when compiling TSF together with stb_vorbis This also add a sfotool to write such files --- sfotool/.gitignore | 15 +++ sfotool/Makefile | 2 + sfotool/README.md | 29 ++++++ sfotool/main.c | 200 ++++++++++++++++++++++++++++++++++++++++ sfotool/sfotool.sln | 28 ++++++ sfotool/sfotool.vcxproj | 100 ++++++++++++++++++++ tsf.h | 155 +++++++++++++++++-------------- 7 files changed, 462 insertions(+), 67 deletions(-) create mode 100644 sfotool/.gitignore create mode 100644 sfotool/Makefile create mode 100644 sfotool/README.md create mode 100644 sfotool/main.c create mode 100644 sfotool/sfotool.sln create mode 100644 sfotool/sfotool.vcxproj diff --git a/sfotool/.gitignore b/sfotool/.gitignore new file mode 100644 index 0000000..f2af1ae --- /dev/null +++ b/sfotool/.gitignore @@ -0,0 +1,15 @@ +*.ncb +*.opt +*.plg +*.aps +*.ipch +*.suo +*.user +*.sdf +*.opensdf +*.dsw +*-i686 +*-x86_64 +Debug +Release +.vs diff --git a/sfotool/Makefile b/sfotool/Makefile new file mode 100644 index 0000000..aef5ce0 --- /dev/null +++ b/sfotool/Makefile @@ -0,0 +1,2 @@ +all: + gcc main.c -o sfotool diff --git a/sfotool/README.md b/sfotool/README.md new file mode 100644 index 0000000..89c757b --- /dev/null +++ b/sfotool/README.md @@ -0,0 +1,29 @@ +# SFOTool for TinySoundFont +A tool to create heavily compressed .SFO files from .SF2 SoundFont files. + +## Purpose +SFO files are just regular SoundFont v2 files with the entire block of raw PCM samples replaced +with a single Ogg Vorbis compressed stream. Unlike .sf3 files, which can have every separate font +sample compressed individually, this will compress the entire sound data as if it were a single +sample. This results in much higher compression than processing samples individually but also +higher loss of quality. + +## Usage Help +```sh +sfotool : Show type of sample stream contained (PCM or OGG) +sfotool : Dump PCM sample stream to .WAV file +sfotool : Dump OGG sample stream to .OGG file +sfotool : Write new .SF2 soundfont file using PCM sample stream from .WAV file +sfotool : Write new .SFO soundfont file using OGG sample stream from .OGG file +``` + +## Making a .SFO file from a .SF2 file +1. Dump the PCM data of a .SF2 file to a .WAV file + `sfotool ` +2. Compress the .WAV file to .OGG (i.e. with [Audacity](https://www.audacityteam.org/download/legacy-windows/)) + Make sure to choose the desired compression quality level +3. Build the .SFO file from the .SF2 file and the new .OGG + `sfotool ` + +# License +SFOTool is available under the [Unlicense](http://unlicense.org/) (public domain). diff --git a/sfotool/main.c b/sfotool/main.c new file mode 100644 index 0000000..65a6652 --- /dev/null +++ b/sfotool/main.c @@ -0,0 +1,200 @@ +//--------------------------------------------// +// SFOTool // +// License: Public Domain (www.unlicense.org) // +//--------------------------------------------// + +#include +#include + +typedef char sfo_fourcc[4]; +#define SFO_FourCCEquals(a, b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]) +struct sfo_riffchunk { sfo_fourcc id; unsigned int size; }; +struct sfo_wavheader +{ + char RIFF[4]; unsigned int ChunkSize; char WAVE[4], fmt[4]; unsigned int Subchunk1Size; + unsigned short AudioFormat,NumOfChan; unsigned int SamplesPerSec, bytesPerSec; + unsigned short blockAlign, bitsPerSample; char Subchunk2ID[4]; unsigned int Subchunk2Size; +}; + +static void sfo_copy(FILE* src, FILE* trg, unsigned int size) +{ + unsigned int block; + unsigned char buf[512]; + for (; size; size -= block) + { + block = (size > sizeof(buf) ? sizeof(buf) : size); + fread(buf, 1, block, src); + fwrite(buf, 1, block, trg); + } +} + +static int sfo_riffchunk_read(struct sfo_riffchunk* parent, struct sfo_riffchunk* chunk, FILE* f) +{ + int is_riff, is_list; + if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) > parent->size) return 0; + if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; + if (!fread(&chunk->size, sizeof(unsigned int), 1, f)) return 0; + if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size > parent->size) return 0; + if (parent) parent->size -= sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size; + is_riff = SFO_FourCCEquals(chunk->id, "RIFF"), is_list = SFO_FourCCEquals(chunk->id, "LIST"); + if (is_riff && parent) return 0; /* not allowed */ + if (!is_riff && !is_list) return 1; /* custom type without sub type */ + if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; + chunk->size -= sizeof(sfo_fourcc); + return 1; +} + +int main(int argc, const char** argv) +{ + const char* arg_sf_in = (argc > 1 ? argv[1] : NULL); + const char* arg_smpl = (argc > 2 ? argv[2] : NULL); + const char* arg_sf_out = (argc > 3 ? argv[3] : NULL); + char ext_sf_in = (arg_sf_in ? arg_sf_in [strlen(arg_sf_in)-1] | 0x20 : '\0'); + char ext_smpl = (arg_smpl ? arg_smpl [strlen(arg_smpl)-1] | 0x20 : '\0'); + char ext_sf_out = (arg_sf_out ? arg_sf_out[strlen(arg_sf_out)-1] | 0x20 : '\0'); + struct sfo_riffchunk chunkHead, chunkList, chunk; + FILE* f_sf_in = NULL, *f_smpl = NULL, *f_sf_out = NULL; + + if (argc < 2 || argc > 4) + { + print_usage: + fprintf(stderr, "Usage Help:\n"); + fprintf(stderr, "%s : Show type of sample stream contained (PCM or OGG)\n", argv[0]); + fprintf(stderr, "%s : Dump PCM sample stream to .WAV file\n", argv[0]); + fprintf(stderr, "%s : Dump OGG sample stream to .OGG file\n", argv[0]); + fprintf(stderr, "%s : Write new .SF2 soundfont file using PCM sample stream from .WAV file\n", argv[0]); + fprintf(stderr, "%s : Write new .SFO soundfont file using OGG sample stream from .OGG file\n", argv[0]); + if (f_sf_in) fclose(f_sf_in); + if (f_smpl) fclose(f_smpl); + if (f_sf_out) fclose(f_sf_out); + return 1; + } + + f_sf_in = fopen(arg_sf_in, "rb"); + if (!f_sf_in) { fprintf(stderr, "Error: Passed input file '%s' does not exist\n\n", arg_sf_in); goto print_usage; } + + if (!sfo_riffchunk_read(NULL, &chunkHead, f_sf_in) || !SFO_FourCCEquals(chunkHead.id, "sfbk")) + { + fprintf(stderr, "Error: Passed input file '%s' is not a valid soundfont file\n\n", arg_sf_in); + goto print_usage; + } + while (sfo_riffchunk_read(&chunkHead, &chunkList, f_sf_in)) + { + unsigned int pos_listsize = (unsigned int)ftell(f_sf_in) - 8; + if (!SFO_FourCCEquals(chunkList.id, "sdta")) + { + fseek(f_sf_in, chunkList.size, SEEK_CUR); + continue; + } + for (; sfo_riffchunk_read(&chunkList, &chunk, f_sf_in); fseek(f_sf_in, chunkList.size, SEEK_CUR)) + { + int is_pcm = SFO_FourCCEquals(chunk.id, "smpl"); + if (!is_pcm && !SFO_FourCCEquals(chunk.id, "smpo")) + continue; + + printf("Soundfont file '%s' contains a %s sample stream\n", arg_sf_in, (is_pcm ? "PCM" : "OGG")); + if (ext_sf_in != '2' && ext_sf_in != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); + if (ext_sf_in == '2' && !is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", '2', "OGG (should be .SFO)"); + if (ext_sf_in == 'o' && is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", 'O', "PCM (should be .SF2)"); + if (arg_sf_out) + { + unsigned int pos_smpchunk, end_smpchunk, len_smpl, end_sf, len_list_in, len_list_out; + + printf("Writing file '%s' with samples from '%s'\n", arg_sf_out, arg_smpl); + if (ext_sf_out != '2' && ext_sf_out != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); + if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); + if (ext_sf_out == '2' && ext_smpl != 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", '2', "OGG"); + if (ext_sf_out == 'o' && ext_smpl == 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", 'O', "WAV"); + + f_smpl = fopen(arg_smpl, "rb"); + if (!f_smpl) { fprintf(stderr, "Error: Unable to open input file '%s'\n\n", arg_smpl); goto print_usage; } + + if (ext_smpl == 'v') + { + struct sfo_wavheader wav_hdr; + fread(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); + if (!SFO_FourCCEquals(wav_hdr.Subchunk2ID, "data") || !SFO_FourCCEquals(wav_hdr.RIFF, "RIFF") + || !SFO_FourCCEquals(wav_hdr.WAVE, "WAVE") || !SFO_FourCCEquals(wav_hdr.fmt, "fmt ") + || wav_hdr.Subchunk1Size != 16 || wav_hdr.AudioFormat != 1 || wav_hdr.NumOfChan != 1 + || wav_hdr.bytesPerSec != wav_hdr.SamplesPerSec * sizeof(short) || wav_hdr.bitsPerSample != sizeof(short) * 8) + { fprintf(stderr, "Input .WAV file is not a valid raw PCM encoded wave file\n\n"); goto print_usage; } + + len_smpl = wav_hdr.Subchunk2Size; + } + else + { + fseek(f_smpl, 0, SEEK_END); + len_smpl = (unsigned int)ftell(f_smpl); + fseek(f_smpl, 0, SEEK_SET); + } + + f_sf_out = fopen(arg_sf_out, "wb"); + if (!f_sf_out) { fprintf(stderr, "Error: Unable to open output file '%s'\n\n", arg_sf_out); goto print_usage; } + + pos_smpchunk = (unsigned int)(ftell(f_sf_in) - sizeof(struct sfo_riffchunk)); + end_smpchunk = pos_smpchunk + (unsigned int)sizeof(struct sfo_riffchunk) + chunk.size; + fseek(f_sf_in, 0, SEEK_END); + end_sf = (unsigned int)ftell(f_sf_in); + + /* Write data before list chunk size */ + fseek(f_sf_in, 0, SEEK_SET); + sfo_copy(f_sf_in, f_sf_out, pos_listsize); + + /* Write new list chunk size */ + fread(&len_list_in, 4, 1, f_sf_in); + len_list_out = len_list_in - chunk.size + len_smpl; + fwrite(&len_list_out, 4, 1, f_sf_out); + + /* Write data until sample chunk */ + sfo_copy(f_sf_in, f_sf_out, pos_smpchunk - pos_listsize - 4); + + /* Write sample chunk */ + fwrite((ext_smpl == 'v' ? "smpl" : "smpo"), 4, 1, f_sf_out); + fwrite(&len_smpl, 4, 1, f_sf_out); + sfo_copy(f_smpl, f_sf_out, len_smpl); + fclose(f_smpl); + + /* Write data after sample chunk */ + fseek(f_sf_in, end_smpchunk, SEEK_SET); + sfo_copy(f_sf_in, f_sf_out, end_sf - end_smpchunk); + fclose(f_sf_out); + } + else if (arg_smpl) + { + f_smpl = fopen(arg_smpl, "wb"); + printf("Writing file '%s' with %s sample stream\n", arg_smpl, (is_pcm ? "PCM" : "OGG")); + if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); + if (ext_smpl == 'v' && !is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "WAV", "OGG (should be .OGG)"); + if (ext_smpl == 'g' && is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "OGG", "PCM (should be .WAV)"); + if (!f_smpl) { fprintf(stderr, "Unable to open output file '%s'\n\n", arg_smpl); goto print_usage; } + + if (is_pcm) + { + struct sfo_wavheader wav_hdr; + memcpy(wav_hdr.Subchunk2ID, "data", 4); + memcpy(wav_hdr.RIFF, "RIFF", 4); + memcpy(wav_hdr.WAVE, "WAVE", 4); + memcpy(wav_hdr.fmt, "fmt ", 4); + wav_hdr.Subchunk1Size = 16; + wav_hdr.AudioFormat = 1; + wav_hdr.NumOfChan = 1; + wav_hdr.Subchunk2Size = (unsigned int)chunk.size; + wav_hdr.ChunkSize = sizeof(wav_hdr) - 4 - 4 + wav_hdr.Subchunk2Size; + wav_hdr.SamplesPerSec = 22050; + wav_hdr.bytesPerSec = 22050 * sizeof(short); + wav_hdr.blockAlign = 1 * sizeof(short); + wav_hdr.bitsPerSample = sizeof(short) * 8; + fwrite(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); + } + sfo_copy(f_sf_in, f_smpl, chunk.size); + fclose(f_smpl); + printf("DONE\n"); + } + fclose(f_sf_in); + return 0; + } + } + + fprintf(stderr, "Passed input file is not a valid soundfont file\n\n"); + goto print_usage; +} diff --git a/sfotool/sfotool.sln b/sfotool/sfotool.sln new file mode 100644 index 0000000..dbcf421 --- /dev/null +++ b/sfotool/sfotool.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sfotool", "sfotool.vcxproj", "{FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.ActiveCfg = Debug|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.Build.0 = Debug|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.ActiveCfg = Debug|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.Build.0 = Debug|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.ActiveCfg = Release|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.Build.0 = Release|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.ActiveCfg = Release|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/sfotool/sfotool.vcxproj b/sfotool/sfotool.vcxproj new file mode 100644 index 0000000..d2f39bc --- /dev/null +++ b/sfotool/sfotool.vcxproj @@ -0,0 +1,100 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF} + Win32Proj + sfotool + sfotool + + + + Application + v110_xp + v120 + v140 + v141 + v142 + v143 + $(DefaultPlatformToolset) + false + MultiByte + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + + + true + + + false + true + + + + + + + true + + + false + + + + Level3 + true + false + false + false + + + Console + true + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDebug + + + true + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + /Gw %(AdditionalOptions) + + + true + true + false + + + + + + + \ No newline at end of file diff --git a/tsf.h b/tsf.h index ccb1bd8..2f3a793 100644 --- a/tsf.h +++ b/tsf.h @@ -21,7 +21,7 @@ LICENSE (MIT) - Copyright (C) 2017, 2018 Bernhard Schelling + Copyright (C) 2017-2023 Bernhard Schelling Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) Permission is hereby granted, free of charge, to any person obtaining a copy of this @@ -862,11 +862,53 @@ static int tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int font return 1; } -static int tsf_decode_samples(tsf_u8* smplBuffer, tsf_u32 smplLength, float** outSamples, unsigned int* outSampleCount, struct tsf_hydra *hydra) +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +static TSF_BOOL tsf_decode_ogg(const tsf_u8 *pSmpl, const tsf_u8 *pSmplEnd, float** pRes, tsf_u32* pResNum, tsf_u32* pResMax, tsf_u32 resInitial) { - #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + float* res = *pRes; tsf_u32 resNum = *pResNum; tsf_u32 resMax = *pResMax; stb_vorbis *v; + + // Use whatever stb_vorbis API that is available (either pull or push) + #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) + v = stb_vorbis_open_memory(pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, TSF_NULL); + #else + { int use, err; v = stb_vorbis_open_pushdata(pSmpl, (int)(pSmplEnd - pSmpl), &use, &err, TSF_NULL); pSmpl += use; } + #endif + if (v == TSF_NULL) return TSF_FALSE; + + for (;;) + { + float** outputs; int n_samples; + + // Decode one frame of vorbis samples with whatever stb_vorbis API that is available + #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) + n_samples = stb_vorbis_get_frame_float(v, TSF_NULL, &outputs); + if (!n_samples) break; + #else + if (pSmpl >= pSmplEnd) break; + { int use = stb_vorbis_decode_frame_pushdata(v, pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, &outputs, &n_samples); pSmpl += use; } + if (!n_samples) continue; + #endif + + // Expand our output buffer if necessary then copy over the decoded frame samples + resNum += n_samples; + if (resNum > resMax) + { + do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); + res = (float*)TSF_REALLOC(res, resMax * sizeof(float)); + if (!res) { stb_vorbis_close(v); return 0; } + } + TSF_MEMCPY(res + resNum - n_samples, outputs[0], n_samples * sizeof(float)); + } + stb_vorbis_close(v); + *pRes = res; *pResNum = resNum; *pResMax = resMax; + return TSF_TRUE; +} + +static int tsf_decode_sf3_samples(const void* rawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_hydra *hydra) +{ + const tsf_u8* smplBuffer = (const tsf_u8*)rawBuffer; + tsf_u32 smplLength = *pSmplCount, resNum = 0, resMax = 0, resInitial = (smplLength > 0x100000 ? (smplLength & ~0xFFFFF) : 65536); float *res = TSF_NULL; - tsf_u32 resNum = 0, resMax = 0, resInitial = (smplLength > 0x100000 ? (smplLength & ~0xFFFFF) : 65536); int i; for (i = 0; i < hydra->shdrNum; i++) { @@ -874,7 +916,6 @@ static int tsf_decode_samples(tsf_u8* smplBuffer, tsf_u32 smplLength, float** ou if (shdr->end <= shdr->start) continue; if (shdr->sampleType & 0x30) // compression flags (sometimes Vorbis flag) { - stb_vorbis *v; const tsf_u8 *pSmpl = smplBuffer + shdr->start, *pSmplEnd = smplBuffer + shdr->end; if (!TSF_FourCCEquals(pSmpl, "OggS")) { @@ -882,44 +923,12 @@ static int tsf_decode_samples(tsf_u8* smplBuffer, tsf_u32 smplLength, float** ou continue; } - // Use whatever stb_vorbis API that is available (either pull or push) - #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) - v = stb_vorbis_open_memory(pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, TSF_NULL); - #else - { int use, err; v = stb_vorbis_open_pushdata(pSmpl, (int)(pSmplEnd - pSmpl), &use, &err, TSF_NULL); pSmpl += use; } - #endif - if (v == TSF_NULL) { TSF_FREE(res); return 0; } - // Fix up sample indices in shdr (end index is set after decoding) shdr->start = resNum; shdr->startLoop += resNum; shdr->endLoop += resNum; - for (;;) - { - float** outputs; int n_samples; - - // Decode one frame of vorbis samples with whatever stb_vorbis API that is available - #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) - n_samples = stb_vorbis_get_frame_float(v, TSF_NULL, &outputs); - if (!n_samples) break; - #else - if (pSmpl >= pSmplEnd) break; - { int use = stb_vorbis_decode_frame_pushdata(v, pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, &outputs, &n_samples); pSmpl += use; } - if (!n_samples) continue; - #endif - - // Expand our output buffer if necessary then copy over the decoded frame samples - resNum += n_samples; - if (resNum > resMax) - { - do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); - res = (float*)TSF_REALLOC(res, resMax * sizeof(float)); - if (!res) { stb_vorbis_close(v); return 0; } - } - TSF_MEMCPY(res + resNum - n_samples, outputs[0], n_samples * sizeof(float)); - } + if (!tsf_decode_ogg(pSmpl, pSmplEnd, &res, &resNum, &resMax, resInitial)) { TSF_FREE(res); return 0; } shdr->end = resNum; - stb_vorbis_close(v); } else // raw PCM sample { @@ -951,30 +960,37 @@ static int tsf_decode_samples(tsf_u8* smplBuffer, tsf_u32 smplLength, float** ou // Trim the sample buffer down then return success (unless out of memory) res = (float*)TSF_REALLOC(res, resNum * sizeof(float)); - *outSamples = res; - *outSampleCount = resNum; + *pFloatBuffer = res; + *pSmplCount = resNum; return (res ? 1 : 0); - #else - // Inline convert the samples from short to float (buffer was allocated big enough in tsf_load_samples) - float *out; const short *in; - *outSamples = (float*)smplBuffer; - *outSampleCount = smplLength / sizeof(short); - for (in = (short*)smplBuffer + *outSampleCount, out = *outSamples + *outSampleCount; in != (short*)smplBuffer;) - *(--out) = (float)(*(--in) / 32767.0); - return 1; - #endif } +#endif -static int tsf_load_samples(tsf_u8** smplBuffer, tsf_u32 smplLength, struct tsf_stream* stream) +static int tsf_load_samples(void** pRawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) { #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H - // With OGG Vorbis support we cannot pre-allocate the memory for tsf_decode_samples - *smplBuffer = (tsf_u8*)TSF_MALLOC(smplLength); + // With OGG Vorbis support we cannot pre-allocate the memory for tsf_decode_sf3_samples + *pSmplCount = chunkSmpl->size; + *pRawBuffer = (void*)TSF_MALLOC(*pSmplCount); + if (!*pRawBuffer || !stream->read(stream->data, *pRawBuffer, chunkSmpl->size)) return 0; + if (chunkSmpl->id[3] != 'o') return 1; + + // Decode custom .sfo 'smpo' format where all samples are in a single ogg stream + tsf_u32 resNum = 0, resMax = 0, resInitial = 65536; + if (!tsf_decode_ogg((tsf_u8*)*pRawBuffer, (tsf_u8*)*pRawBuffer + chunkSmpl->size, pFloatBuffer, &resNum, &resMax, resInitial)) return 0; + *pFloatBuffer = (float*)TSF_REALLOC(*pFloatBuffer, resNum * sizeof(float)); + *pSmplCount = resNum; + return (*pFloatBuffer ? 1 : 0); #else - // Allocate enough to hold the decoded float samples (see tsf_decode_samples) - *smplBuffer = (tsf_u8*)TSF_MALLOC(smplLength / sizeof(short) * sizeof(float)); + // Inline convert the samples from short to float + float *res, *out; const short *in; + *pSmplCount = chunkSmpl->size / sizeof(short); + *pFloatBuffer = (float*)TSF_MALLOC(*pSmplCount * sizeof(float)); + if (!*pFloatBuffer || !stream->read(stream->data, *pFloatBuffer, chunkSmpl->size)) return 0; + for (res = *pFloatBuffer, out = res + *pSmplCount, in = (short*)res + *pSmplCount; out != res;) + *(--out) = (float)(*(--in) / 32767.0); + return 1; #endif - return (*smplBuffer ? stream->read(stream->data, *smplBuffer, smplLength) : 0); } static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate) @@ -1334,8 +1350,9 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) struct tsf_riffchunk chunkHead; struct tsf_riffchunk chunkList; struct tsf_hydra hydra; - tsf_u8* smplBuffer = TSF_NULL; - unsigned int smplLength = 0; + void* rawBuffer = TSF_NULL; + float* floatBuffer = TSF_NULL; + tsf_u32 smplCount = 0; if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk")) { @@ -1377,10 +1394,13 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) { while (tsf_riffchunk_read(&chunkList, &chunk, stream)) { - if (TSF_FourCCEquals(chunk.id, "smpl") && !smplBuffer && chunk.size >= sizeof(short)) + if ((TSF_FourCCEquals(chunk.id, "smpl") + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + || TSF_FourCCEquals(chunk.id, "smpo") + #endif + ) && !rawBuffer && !floatBuffer && chunk.size >= sizeof(short)) { - smplLength = chunk.size; - if (!tsf_load_samples(&smplBuffer, smplLength, stream)) goto out_of_memory; + if (!tsf_load_samples(&rawBuffer, &floatBuffer, &smplCount, &chunk, stream)) goto out_of_memory; } else stream->skip(stream->data, chunk.size); } @@ -1391,20 +1411,21 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) { //if (e) *e = TSF_INVALID_INCOMPLETE; } - else if (smplBuffer == TSF_NULL) + else if (!rawBuffer && !floatBuffer) { //if (e) *e = TSF_INVALID_NOSAMPLEDATA; } else { - float* fontSamples; unsigned int fontSampleCount; - if (!tsf_decode_samples(smplBuffer, smplLength, &fontSamples, &fontSampleCount, &hydra)) goto out_of_memory; - if (fontSamples == (float*)smplBuffer) smplBuffer = TSF_NULL; // Was converted inline, don't free below + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + if (!floatBuffer && !tsf_decode_sf3_samples(rawBuffer, &floatBuffer, &smplCount, &hydra)) goto out_of_memory; + #endif res = (tsf*)TSF_MALLOC(sizeof(tsf)); if (res) TSF_MEMSET(res, 0, sizeof(tsf)); - if (!res || !tsf_load_presets(res, &hydra, fontSampleCount)) { TSF_FREE(fontSamples); goto out_of_memory; } - res->fontSamples = fontSamples; + if (!res || !tsf_load_presets(res, &hydra, smplCount)) goto out_of_memory; res->outSampleRate = 44100.0f; + res->fontSamples = floatBuffer; + floatBuffer = TSF_NULL; // don't free below } if (0) { @@ -1416,7 +1437,7 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods); TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags); TSF_FREE(hydra.imods); TSF_FREE(hydra.igens); TSF_FREE(hydra.shdrs); - TSF_FREE(smplBuffer); + TSF_FREE(rawBuffer); TSF_FREE(floatBuffer); return res; }