diff --git a/internal/c/parts/audio/extras/midi_ma_vtable.cpp b/internal/c/parts/audio/extras/midi_ma_vtable.cpp index 6523622fc..d542f5763 100644 --- a/internal/c/parts/audio/extras/midi_ma_vtable.cpp +++ b/internal/c/parts/audio/extras/midi_ma_vtable.cpp @@ -13,15 +13,10 @@ // Soundfont (awe32rom.h) from dos-like // https://github.com/mattiasgustavsson/dos-like (MIT) // -// Copyright (c) 2022 Samuel Gomes -// https://github.com/a740g -// //----------------------------------------------------------------------------------------------------- -//----------------------------------------------------------------------------------------------------- -// HEADER FILES -//----------------------------------------------------------------------------------------------------- #include "libqb-common.h" + #include "audio.h" #include "filepath.h" @@ -29,49 +24,50 @@ #include "../miniaudio.h" +#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.c" #define TSF_IMPLEMENTATION #include "tinysoundfont/tsf.h" #define TML_IMPLEMENTATION #include "tinysoundfont/tml.h" +#undef STB_VORBIS_HEADER_ONLY #include "vtables.h" extern "C" { - // These symbols reference a soundfont compiled into the program - // - // We provide a macro to expand to the correct symbol name +// These symbols reference a soundfont compiled into the program +// +// We provide a macro to expand to the correct symbol name #if defined(QB64_WINDOWS) && defined(QB64_32) - // On 32-bit Windows, we use objcopy, and the symbols do not have an - // underscore prefix - extern char binary_soundfont_sf2_start[]; - extern char binary_soundfont_sf2_end[]; +// On 32-bit Windows, we use objcopy, and the symbols do not have an +// underscore prefix +extern char binary_soundfont_sf2_start[]; +extern char binary_soundfont_sf2_end[]; -# define SOUNDFONT_BIN binary_soundfont_sf2_start -# define SOUNDFONT_SIZE (binary_soundfont_sf2_end - binary_soundfont_sf2_start) +# define SOUNDFONT_BIN binary_soundfont_sf2_start +# define SOUNDFONT_SIZE (binary_soundfont_sf2_end - binary_soundfont_sf2_start) #elif defined(QB64_WINDOWS) || defined(QB64_LINUX) - // On Linux and 64-bit Windows, we use objcopy, and the symbols do have an - // underscore prefix. - extern char _binary_soundfont_sf2_start[]; - extern char _binary_soundfont_sf2_end[]; +// On Linux and 64-bit Windows, we use objcopy, and the symbols do have an +// underscore prefix. +extern char _binary_soundfont_sf2_start[]; +extern char _binary_soundfont_sf2_end[]; -# define SOUNDFONT_BIN _binary_soundfont_sf2_start -# define SOUNDFONT_SIZE (_binary_soundfont_sf2_end - _binary_soundfont_sf2_start) +# define SOUNDFONT_BIN _binary_soundfont_sf2_start +# define SOUNDFONT_SIZE (_binary_soundfont_sf2_end - _binary_soundfont_sf2_start) #else - // On Mac OS we use xxd, which gives an array and size - extern unsigned char soundfont_sf2[]; - extern unsigned int soundfont_sf2_len; +// On Mac OS we use xxd, which gives an array and size +extern unsigned char soundfont_sf2[]; +extern unsigned int soundfont_sf2_len; -# define SOUNDFONT_BIN soundfont_sf2 -# define SOUNDFONT_SIZE soundfont_sf2_len +# define SOUNDFONT_BIN soundfont_sf2 +# define SOUNDFONT_SIZE soundfont_sf2_len #endif } -//----------------------------------------------------------------------------------------------------- - struct ma_tsf { // This part is for miniaudio ma_data_source_base ds; /* The decoder can be used independently as a data source. */ @@ -274,8 +270,14 @@ static ma_result ma_tsf_ds_get_length(ma_data_source *pDataSource, ma_uint64 *pL return ma_tsf_get_length_in_pcm_frames((ma_tsf *)pDataSource, pLength); } -static ma_data_source_vtable ma_data_source_vtable_tsf = {ma_tsf_ds_read, ma_tsf_ds_seek, ma_tsf_ds_get_data_format, ma_tsf_ds_get_cursor, - ma_tsf_ds_get_length}; +// clang-format off +static ma_data_source_vtable ma_data_source_vtable_tsf = { + ma_tsf_ds_read, ma_tsf_ds_seek, + ma_tsf_ds_get_data_format, + ma_tsf_ds_get_cursor, + ma_tsf_ds_get_length +}; +// clang-format on static int ma_tsf_of_callback__read(void *pUserData, unsigned char *pBufferOut, int bytesToRead) { ma_tsf *pTsf = (ma_tsf *)pUserData; @@ -357,13 +359,12 @@ static ma_result ma_tsf_init_internal(const ma_decoding_backend_config *pConfig, return MA_SUCCESS; } -ma_result ma_tsf_load_memory(ma_tsf *pTsf) -{ +ma_result ma_tsf_load_memory(ma_tsf *pTsf) { // Attempt to load a SoundFont from memory pTsf->tinySoundFont = tsf_load_memory(SOUNDFONT_BIN, SOUNDFONT_SIZE); // Return failue if loading from memory also failed. This should not happen though - return pTsf->tinySoundFont? MA_SUCCESS: MA_OUT_OF_MEMORY; + return pTsf->tinySoundFont ? MA_SUCCESS : MA_OUT_OF_MEMORY; } static ma_result ma_tsf_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, @@ -566,6 +567,7 @@ static void ma_decoding_backend_uninit__tsf(void *pUserData, ma_data_source *pBa ma_free(pTsf, pAllocationCallbacks); } +// clang-format off ma_decoding_backend_vtable ma_vtable_midi = { ma_decoding_backend_init__tsf, ma_decoding_backend_init_file__tsf, @@ -573,4 +575,4 @@ ma_decoding_backend_vtable ma_vtable_midi = { NULL, /* onInitMemory() */ ma_decoding_backend_uninit__tsf }; -//----------------------------------------------------------------------------------------------------- +// clang-format on diff --git a/internal/c/parts/audio/extras/tinysoundfont/tsf.h b/internal/c/parts/audio/extras/tinysoundfont/tsf.h index c94ba0de0..3ca92d4d8 100644 --- a/internal/c/parts/audio/extras/tinysoundfont/tsf.h +++ b/internal/c/parts/audio/extras/tinysoundfont/tsf.h @@ -121,7 +121,7 @@ enum TSFOutputMode // Two channels with all samples for the left channel first then right TSF_STEREO_UNWEAVED, // A single channel (stereo instruments are mixed into center) - TSF_MONO, + TSF_MONO }; // Thread safety: @@ -697,7 +697,7 @@ static void tsf_region_envtosecs(struct tsf_envelope* p, TSF_BOOL sustainIsGain) // to keep the values in timecents so we can calculate it during startNote if (!p->keynumToHold) p->hold = (p->hold < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->hold)); if (!p->keynumToDecay) p->decay = (p->decay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->decay)); - + if (p->sustain < 0.0f) p->sustain = 0.0f; else if (sustainIsGain) p->sustain = tsf_decibelsToGain(-p->sustain / 10.0f); else p->sustain = 1.0f - (p->sustain / 1000.0f); @@ -861,27 +861,108 @@ static int tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int font return 1; } -static int tsf_load_samples(float** fontSamples, unsigned int* fontSampleCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +static int tsf_decode_samples_ogg(tsf_u8* smplBuffer, tsf_u32 smplLength, float** outSamples, unsigned int* outSampleCount, struct tsf_hydra *hydra) { - // Read sample data into float format buffer. - float* out; unsigned int samplesLeft, samplesToRead, samplesToConvert; - samplesLeft = *fontSampleCount = chunkSmpl->size / sizeof(short); - out = *fontSamples = (float*)TSF_MALLOC(samplesLeft * sizeof(float)); - if (!out) return 0; - for (; samplesLeft; samplesLeft -= samplesToRead) + float *res = TSF_NULL; + unsigned int resNum = 0, resMax = 0, resInitial = (smplLength > 0x100000 ? (smplLength & ~0xFFFFF) : 65536); + int i; + for (i = 0; i < hydra->shdrNum; i++) { - short sampleBuffer[1024], *in = sampleBuffer;; - samplesToRead = (samplesLeft > 1024 ? 1024 : samplesLeft); - stream->read(stream->data, sampleBuffer, samplesToRead * sizeof(short)); + stb_vorbis *v; + struct tsf_hydra_shdr *shdr = &hydra->shdrs[i]; + const tsf_u8 *pSmpl = smplBuffer + shdr->start, *pSmplEnd = smplBuffer + shdr->end; + if (pSmplEnd <= pSmpl) continue; - // Convert from signed 16-bit to float. - for (samplesToConvert = samplesToRead; samplesToConvert > 0; --samplesToConvert) - // If we ever need to compile for big-endian platforms, we'll need to byte-swap here. - *out++ = (float)(*in++ / 32767.0); + // 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)); + } + shdr->end = resNum; + stb_vorbis_close(v); } + + // Trim the sample buffer down then return success (unless out of memory) + res = (float*)TSF_REALLOC(res, resNum * sizeof(float)); + *outSamples = res; + *outSampleCount = resNum; + return (res ? 1 : 0); +} +#endif + +static int tsf_decode_samples(tsf_u8* smplBuffer, tsf_u32 smplLength, float** outSamples, unsigned int* outSampleCount, struct tsf_hydra *hydra) +{ + float *out; const short *in; + + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + if (TSF_FourCCEquals(smplBuffer, "OggS")) + return tsf_decode_samples_ogg(smplBuffer, smplLength, outSamples, outSampleCount, hydra); + #endif + + // Inline convert the samples from short to float (buffer was allocated big enough in tsf_load_samples) + *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; } +static int tsf_load_samples(tsf_u8** smplBuffer, tsf_u32 smplLength, struct tsf_stream* stream) +{ + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + // With OGG Vorbis support scan for a specific 4 byte sample header first + if (smplLength >= sizeof(tsf_fourcc)) + { + // If the format is not OGG the buffer is made large enough to hold the decoded float samples + tsf_fourcc format; + stream->read(stream->data, &format, sizeof(tsf_fourcc)); + if (TSF_FourCCEquals(format, "OggS")) + *smplBuffer = (tsf_u8*)TSF_MALLOC(smplLength); + else + *smplBuffer = (tsf_u8*)TSF_MALLOC(smplLength / sizeof(short) * sizeof(float)); + if (!*smplBuffer) return 0; + memcpy(*smplBuffer, &format, sizeof(tsf_fourcc)); + return stream->read(stream->data, (char*)*smplBuffer + sizeof(tsf_fourcc), smplLength - sizeof(tsf_fourcc)); + } + #endif + + // Allocate enough to hold the decoded float samples (see tsf_decode_samples) + *smplBuffer = (tsf_u8*)TSF_MALLOC(smplLength / sizeof(short) * sizeof(float)); + 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) { switch (active_segment) @@ -1239,8 +1320,8 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) struct tsf_riffchunk chunkHead; struct tsf_riffchunk chunkList; struct tsf_hydra hydra; - float* fontSamples = TSF_NULL; - unsigned int fontSampleCount = 0; + tsf_u8* smplBuffer = TSF_NULL; + unsigned int smplLength = 0; if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk")) { @@ -1282,9 +1363,10 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) { while (tsf_riffchunk_read(&chunkList, &chunk, stream)) { - if (TSF_FourCCEquals(chunk.id, "smpl") && !fontSamples && chunk.size >= sizeof(short)) + if (TSF_FourCCEquals(chunk.id, "smpl") && !smplBuffer && chunk.size >= sizeof(short)) { - if (!tsf_load_samples(&fontSamples, &fontSampleCount, &chunk, stream)) goto out_of_memory; + smplLength = chunk.size; + if (!tsf_load_samples(&smplBuffer, smplLength, stream)) goto out_of_memory; } else stream->skip(stream->data, chunk.size); } @@ -1295,18 +1377,19 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream) { //if (e) *e = TSF_INVALID_INCOMPLETE; } - else if (fontSamples == TSF_NULL) + else if (smplBuffer == TSF_NULL) { //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 res = (tsf*)TSF_MALLOC(sizeof(tsf)); - if (!res) goto out_of_memory; - TSF_MEMSET(res, 0, sizeof(tsf)); - if (!tsf_load_presets(res, &hydra, fontSampleCount)) goto out_of_memory; + 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; - fontSamples = TSF_NULL; //don't free below res->outSampleRate = 44100.0f; } if (0) @@ -1319,7 +1402,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(fontSamples); + TSF_FREE(smplBuffer); return res; } @@ -1566,7 +1649,7 @@ TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing tsf_render_float(f, floatSamples, channelSamples, TSF_FALSE); samples -= channelSamples; - if (flag_mixing) + if (flag_mixing) while (buffer != bufferEnd) { float v = *floatSamples++; @@ -1610,7 +1693,7 @@ static struct tsf_channel* tsf_channel_init(tsf* f, int channel) if (!f->channels) { f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); - if (!f->channels) return NULL; + if (!f->channels) return TSF_NULL; f->channels->setupVoice = &tsf_channel_setup_voice; f->channels->channelNum = 0; f->channels->activeChannel = 0; @@ -1618,7 +1701,7 @@ static struct tsf_channel* tsf_channel_init(tsf* f, int channel) else { struct tsf_channels *newChannels = (struct tsf_channels*)TSF_REALLOC(f->channels, sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); - if (!newChannels) return NULL; + if (!newChannels) return TSF_NULL; f->channels = newChannels; } i = f->channels->channelNum; diff --git a/source/qb64pe.bas b/source/qb64pe.bas index 544b346a6..51ee9d547 100644 --- a/source/qb64pe.bas +++ b/source/qb64pe.bas @@ -3493,11 +3493,21 @@ DO ' Verify there are no extra characters after end quote IF INSTR(MidiSoundFont$, CHR$(34)) <> LEN(MidiSoundFont$) THEN a$ = "Unexpected characters after the quoted file name": GOTO errmes + ' Strip the trailing quote MidiSoundFont$ = MID$(MidiSoundFont$, 1, LEN(MidiSoundFont$) - 1) IF NOT _FILEEXISTS(MidiSoundFont$) THEN - a$ = "Soundfont file " + AddQuotes$(MidiSoundFont$) + " could not be found!" - GOTO errmes + ' Just try to concatenate the path with the source or include path and check if we are able to find the file + IF inclevel > 0 AND _FILEEXISTS(getfilepath(incname(inclevel)) + MidiSoundFont$) THEN + MidiSoundFont$ = getfilepath(incname(inclevel)) + MidiSoundFont$ + ELSEIF _FILEEXISTS(FixDirectoryName(idepath$) + MidiSoundFont$) THEN + MidiSoundFont$ = FixDirectoryName(idepath$) + MidiSoundFont$ + END IF + + IF NOT _FILEEXISTS(MidiSoundFont$) THEN + a$ = "Soundfont file " + AddQuotes$(MidiSoundFont$) + " could not be found!" + GOTO errmes + END IF END IF ELSE ' Constant values, only one for now