1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-07-06 01:10:23 +00:00

Update TSF and add SF3 support. Fix $MIDISOUNDFONT behavior

This commit is contained in:
Samuel Gomes 2023-03-26 11:38:03 +05:30
parent 8c2dc61a54
commit 8a9b8a2f25
3 changed files with 160 additions and 65 deletions

View file

@ -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

View file

@ -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;

View file

@ -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