shithub: sf2mid

Download patch

ref: d21fe7b22c8c906e749e4be4bb389efc2e761964
parent: a4cb126c0c9e299d43c24c27f9a6294383a9605e
author: Bernhard Schelling <14200249+schellingb@users.noreply.github.com>
date: Sat Feb 18 21:51:09 EST 2023

Improve SF3 support
- Avoid having to allocate twice when loading regular SF2 file
- Don't rely on C99 (or non-standard) variable-length array support
- Use float OGG decoding instead of converting to shorts then back to floats
- Use push API from stb_vorbis in case the pull API has been disabled

--- a/tsf.h
+++ b/tsf.h
@@ -861,121 +861,107 @@
 	return 1;
 }
 
-// buffer chunkSmpl into smplBuffer
-static int tsf_buffer_smpl(char **smplBuffer, unsigned int *smplLength, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) {
-	int remaining = chunkSmpl->size;
-
-	*smplBuffer = TSF_MALLOC(remaining);
-	*smplLength = remaining;
-
-	char *out = *smplBuffer;
-
-	if (!*smplBuffer) return 0;
-
-	do {
-		int readSize = remaining > 1024 ? 1024 : remaining;
-		stream->read(stream->data, out, readSize);
-		out += readSize;
-		remaining -= readSize;
-	} while (remaining > 0);
-
-	return 1;
-}
-
-// load s16 samples into f32 floatSamples
-static void tsf_convert_samples(short *samples, float *floatSamples, int sampleCount) {
-	int i;
-	for (i = 0; i < sampleCount; i++) {
-		floatSamples[i] = (float)(samples[i] / 32767.0);
-	}
-}
-
 #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H
-static int tsf_decode_vorbis_samples(struct tsf_hydra *hydra, char *smplBuffer, float **fontSamples, int *fontSampleCount) {
-	float *shdrSamples[hydra->shdrNum];
-	unsigned int shdrBufferLengths[hydra->shdrNum];
+static int tsf_decode_samples_ogg(tsf_u8* smplBuffer, tsf_u32 smplLength, float** outSamples, unsigned int* outSampleCount, struct tsf_hydra *hydra)
+{
+	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++) {
-		shdrSamples[i] = TSF_NULL;
-
+	for (i = 0; i < hydra->shdrNum; i++)
+	{
+		stb_vorbis *v;
 		struct tsf_hydra_shdr *shdr = &hydra->shdrs[i];
-		int compressedSize = shdr->end - shdr->start;
+		const tsf_u8 *pSmpl = smplBuffer + shdr->start, *pSmplEnd = smplBuffer + shdr->end;
+		if (pSmplEnd <= pSmpl) continue;
 
-		if (compressedSize <= 0) {
-			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; }
 
-		short *samples = NULL;
-		int channels = 0;
-		int sampleRate = 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;
 
-		int sampleCount = stb_vorbis_decode_memory(
-			smplBuffer + shdr->start,
-			compressedSize,
-			&channels,
-			&sampleRate,
-			&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
 
-		if (sampleCount < 0) {
-			return 0;
+			// 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));
 		}
-
-		float *floatSamples = TSF_MALLOC(sampleCount * sizeof(float));
-
-		if (!floatSamples)  {
-			return 0;
-		}
-
-		tsf_convert_samples(samples, floatSamples, sampleCount);
-
-		// stb_vorbis already uses free and not TSF_ functions
-		free(samples);
-
-		*fontSampleCount += sampleCount;
-
-		shdrSamples[i] = floatSamples;
-		shdrBufferLengths[i] = sampleCount;
+		shdr->end = resNum;
+		stb_vorbis_close(v);
 	}
 
-	TSF_FREE(smplBuffer);
-	smplBuffer = TSF_NULL;
+	// 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
 
-	float *floatSamples = TSF_MALLOC(*fontSampleCount * sizeof(float));
+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;
 
-	if (!fontSamples) {
-		return 0;
-	}
+	#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H
+	if (TSF_FourCCEquals(smplBuffer, "OggS"))
+		return tsf_decode_samples_ogg(smplBuffer, smplLength, outSamples, outSampleCount, hydra);
+	#endif
 
-	unsigned int sampleBufferOffset = 0;
+	// 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;
+}
 
-	for (i = 0; i < hydra->shdrNum; i++) {
-		struct tsf_hydra_shdr *shdr = &hydra->shdrs[i];
-		int sampleCount = shdrBufferLengths[i];
-
-		shdr->start = sampleBufferOffset;
-		shdr->end = sampleBufferOffset + sampleCount;
-		shdr->startLoop += sampleBufferOffset;
-		shdr->endLoop += sampleBufferOffset;
-
-		if (shdrSamples[i]) {
-			for (int j = 0; j < sampleCount; j++) {
-				floatSamples[sampleBufferOffset + j] = shdrSamples[i][j];
-			}
-		}
-
-		free(shdrSamples[i]);
-		shdrSamples[i] = TSF_NULL;
-
-		sampleBufferOffset += sampleCount;
+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
 
-	*fontSamples = floatSamples;
-
-	return 1;
+	// 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);
 }
-#endif
 
 static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate)
 {
@@ -1334,10 +1320,8 @@
 	struct tsf_riffchunk chunkHead;
 	struct tsf_riffchunk chunkList;
 	struct tsf_hydra hydra;
-	char* smplBuffer = TSF_NULL;
+	tsf_u8* smplBuffer = TSF_NULL;
 	unsigned int smplLength = 0;
-	float* fontSamples = TSF_NULL;
-	unsigned int fontSampleCount = 0;
 
 	if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk"))
 	{
@@ -1381,7 +1365,8 @@
 			{
 				if (TSF_FourCCEquals(chunk.id, "smpl") && !smplBuffer && chunk.size >= sizeof(short))
 				{
-					if (!tsf_buffer_smpl(&smplBuffer, &smplLength, &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);
 			}
@@ -1398,32 +1383,13 @@
 	}
 	else
 	{
-		int isSf3 = 0;
-
-#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H
-		char *sampleHeader = smplBuffer + hydra.shdrs[0].start;
-
-		if (TSF_FourCCEquals(sampleHeader, "OggS")) {
-			isSf3 = 1;
-
-			if (!tsf_decode_vorbis_samples(&hydra, smplBuffer, &fontSamples, &fontSampleCount)) {
-				goto out_of_memory;
-			}
-		}
-#endif
-
-		if (!isSf3) {
-			fontSampleCount = smplLength / sizeof(short);
-			fontSamples = TSF_MALLOC(fontSampleCount * sizeof(float));
-			tsf_convert_samples((short*)smplBuffer, fontSamples, fontSampleCount);
-		}
-
+		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)
@@ -1436,7 +1402,7 @@
 	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;
 }