TinySoundFont icon indicating copy to clipboard operation
TinySoundFont copied to clipboard

Reverb and Chorus

Open mmontag opened this issue 6 years ago • 15 comments

How hard would it be to get reverb and chorus going? :)

These effects can be implemented in a tiny amount of code. I was poking around FluidSynth and they are using a 4 allpass + 8 comb filter network for reverb. I believe it is based on Freeverb.

My thoughts are that there's no reference implementation for these FX, so might as well make them sound as good as possible. There might be better options than Freeverb. Thoughts?

mmontag avatar Sep 08 '18 15:09 mmontag

I would love to implement them with a tiny amount of code :-)

The current LFO and lowpass filters aren't high quality so I don't think high quality is a requirement. Tiny but decent quality would be great IMHO :-)

Reverb

Freeverb is public domain (source download) so that's good. I think with refactoring we could add it in maybe 300 lines of code. It allocates 100kb memory for 44.1KHz stereo so it needs to be at least somewhat optional. I guess just not allocating memory if the soundfont doesn't use reverb is enough. And maybe a compile time switch for embedded platforms that can't support it. It's still quite a task to integrate nicely I think.

Chorus

A public domain chorus filter would be nice to have. I found http://denniscronin.net/dsp/vst.html which has a chorus filter with no license attached. Maybe Dennis can be contacted. This is also 500 lines that could probably be refactored to 300. It uses a 64kb history so same rules apply as with reverb. It would be nice to share the history between filters but at least freeverb has some accumulated buffers not just a sample history buffer.

Both filters come with quite a lot of parameters. Soundfont just has a percentage "to which the audio output of the note is sent to the effects processor". Not sure if these parameters need to be configurable or if reasonable defaults would be enough.

schellingb avatar Sep 10 '18 09:09 schellingb

I've made a POC of TSF with reverb in a VST using Martin Eastwood's mVerb code. It works on windows inside the SDL audio callback so now I'm trying to push it over to my development board. It doesn't have all the bells and whistles but it sounds very nice. I'll see what the CPU overhead is.....

adrianmcroft avatar Oct 16 '18 12:10 adrianmcroft

Bum, I have global reverb on my Beaglebone but it's stealing 25% CPU. Pity, it sounds very clean....

adrianmcroft avatar Oct 16 '18 14:10 adrianmcroft

@adrianmcroft Can you maybe share a fork with your changes? It would be interesting to see where you made the required changes for adding those missing effects.

Danielku15 avatar Dec 29 '18 12:12 Danielku15

@Danielku15 Hi Daniel, I've been building a VST wrapper for my Bela / TSF implementation for a while - developing and testing on Windows with VS is considerably easier than working with Bela's built in IDE. I'll have to mull over what I did but I can say that mine was a global effort - I applied mVerb to TSFs buffer during render, so it 'effected' all voices rather than individual ones - so it's not really a kosher design (but sufficient for my requirements). I'll dig it all out and resolve it for you anyway.

adrianmcroft avatar Dec 29 '18 22:12 adrianmcroft

@Danielku15 Here is my render cycle from my old Bela code....

It's just

  1. render a TSF block to an intermediate buffer
  2. process that buffer through mVerb
  3. output the buffer to the Bela audio array

I've forced the output mode to Unweaved to make it simpler to match mVerbs pointer to pointer format.

adrianmcroft avatar Dec 30 '18 14:12 adrianmcroft

const int sampleCount = context->audioFrames; // 64 ?

//Number of sample words (floats) for all channels 
const int channelSampleCount = sampleCount * channelCount; // 128

//Number of sample words per block 
const int sampleBlock = TSF_RENDER_EFFECTSAMPLEBLOCK; // 64

//Number of sample words per block per channel
const int channelSampleBlock = (sampleBlock * channelCount); // 128

float* nstream = intermediateArray;

int SampleBlock, SampleCount = sampleCount;
for (SampleBlock = sampleBlock; SampleCount; SampleCount -= SampleBlock, nstream += channelSampleBlock)
{
	if (SampleBlock > SampleCount) SampleBlock = SampleCount;

	// Render the block of audio samples in float format
	tsf_render_float(g_tinySoundFont, nstream, SampleBlock, 0); // Renders 64 floats left, then 64 floats right

	inputArray[0] = nstream;
	inputArray[1] = nstream + context->audioFrames; // Input array is not ** pointer (or multidemensional array pointer to float[2][64])

	em_verb.process(inputArray, outputArray, context->audioFrames);

	for (unsigned int n = 0; n < sampleBlock; n++)
	{
		audioWrite(context, n, 0, outputArray[0][n]);
		audioWrite(context, n, 1, outputArray[1][n]);
	}
}

adrianmcroft avatar Dec 30 '18 14:12 adrianmcroft

Any updates on the reverb / chorus? I'd like to have these features makes the song sound nicer :)

ghost avatar Dec 01 '21 17:12 ghost

Generally I inject these as external effects to the samples produced by TSF.

WindowsNT avatar Dec 01 '21 17:12 WindowsNT

Hi. Do you have an example of how to do this? @WindowsNT This game engine that I am modifying does support Reverb/Chorus implement but is for an entire audio bus. Has code for it but I don't think I can selectively apply it for specfic PCM samples/instruments. I would probably have to modify tml/tsf directly to use the verb/chorus code before it gets sent out to the buffer.

ghost avatar Dec 01 '21 17:12 ghost

You can use tsf_voice_render and then process the samples.

WindowsNT avatar Dec 01 '21 17:12 WindowsNT

Okay that narrows it down. I will look into it. I think I don't have that function exposed yet. Thank you.

Perahps a hook can be implemented to allow us to use our own chorus/verb code library-wide before it gets sent to the buffer so we wouldn't need to implement it per voice? 😃 I'll look into this voice_render function though.

ghost avatar Dec 01 '21 17:12 ghost

Yes, a pre-processing callback would be nice.

WindowsNT avatar Dec 01 '21 17:12 WindowsNT

Okay sorry to bother you. I think maybe I understand @WindowsNT

I need to detect a TML_CONTROL_CHANGE = 0xB0 and have 2 special cases for TML_FX_CHORUS and TML_FX_REVERB = 91 . I think I can use tsf_voice_render() and filter the data though my reverb /chorus code. else process rest of controls like usual.

I am already detecting the control change but I just pass it to the soundfont but it doesn't process the verb/chorus since is not implemented so I never get that in the buffer output . image This is the code ref if anyone is interested how I am doing it https://github.com/goblinengine/goblin/blob/0f02d25d33494d22a59e2a823280b52240526fb8/modules/goblin/midi_player.cpp#L435

ghost avatar Dec 01 '21 17:12 ghost

IMHO if you need more complex features, maybe it's better to go with fluidsynth...

romanbsd avatar Dec 02 '21 12:12 romanbsd