shithub: sf2mid

Download patch

ref: 31e92f8d774d118b58da740b15eae6304b2d94c0
parent: e86d986ed6ccb93b4ee41d2de656c49c721b18db
author: Ellie <el@horse64.org>
date: Sun Nov 14 21:26:03 EST 2021

Kill off voices in release segment when at max voices

--- a/tsf.h
+++ b/tsf.h
@@ -882,6 +882,11 @@
 	return 1;
 }
 
+static int tsf_voice_envelope_release_samples(
+		struct tsf_voice_envelope* e, float outSampleRate) {
+	return (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate);
+}
+
 static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate)
 {
 	switch (active_segment)
@@ -964,7 +969,7 @@
 			return;
 		case TSF_SEGMENT_SUSTAIN:
 			e->segment = TSF_SEGMENT_RELEASE;
-			e->samplesUntilNextSegment = (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate);
+			e->samplesUntilNextSegment = tsf_voice_envelope_release_samples(e, outSampleRate);
 			if (e->isAmpEnv)
 			{
 				// I don't truly understand this; just following what LinuxSampler does.
@@ -1448,15 +1453,40 @@
 			struct tsf_voice* newVoices;
 			if (f->maxVoiceNum)
 			{
-				// voices have been pre-allocated and limited to a maximum, unable to start playing this voice
-				continue;
+				// Voices have been pre-allocated and limited to a maximum.
+				// Try to kill a voice off in its release envelope:
+				int bestKillReleaseSamplePos = 0;
+				struct tsf_voice *bestKill = NULL;
+				v = f->voices;
+				for (; v != vEnd; v++) {
+					if (v->ampenv.segment == TSF_SEGMENT_RELEASE) {
+						int releaseSamplesDone = tsf_voice_envelope_release_samples(
+							&v->ampenv, f->outSampleRate
+						) - v->ampenv.samplesUntilNextSegment;
+						if (bestKill != NULL && releaseSamplesDone <= bestKillReleaseSamplePos)
+						{
+							continue;
+						}
+						// We're looking for the voice furthest into its release:
+						bestKill = v;
+						bestKillReleaseSamplePos = releaseSamplesDone;
+					}
+				}
+				if (bestKill) {
+					tsf_voice_kill(bestKill);
+					voice = bestKill;
+				}
+				if (!voice)
+					continue;
+			} else {
+				// Allocate more voices so we don't need to kill one off.
+				f->voiceNum += 4;
+				newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice));
+				if (!newVoices) return 0;
+				f->voices = newVoices;
+				voice = &f->voices[f->voiceNum - 4];
+				voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1;
 			}
-			f->voiceNum += 4;
-			newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice));
-			if (!newVoices) return 0;
-			f->voices = newVoices;
-			voice = &f->voices[f->voiceNum - 4];
-			voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1;
 		}
 
 		voice->region = region;