Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TSF and add SF3 support and enhance $MIDISOUNDFONT behavior #325

Merged
merged 1 commit into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 36 additions & 34 deletions internal/c/parts/audio/extras/midi_ma_vtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,61 @@
// 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"

#include <string.h>

#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. */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -566,11 +567,12 @@ 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,
NULL, /* onInitFileW() */
NULL, /* onInitMemory() */
ma_decoding_backend_uninit__tsf
};
//-----------------------------------------------------------------------------------------------------
// clang-format on
143 changes: 113 additions & 30 deletions internal/c/parts/audio/extras/tinysoundfont/tsf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));

// 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);
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;

// 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)
Expand Down Expand Up @@ -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"))
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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)
Expand All @@ -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;
}

Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -1610,15 +1693,15 @@ 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;
}
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;
Expand Down
14 changes: 12 additions & 2 deletions source/qb64pe.bas
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down