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:
parent
8c2dc61a54
commit
8a9b8a2f25
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue