schellingb / TinySoundFont

SoundFont2 synthesizer library in a single C/C++ file
MIT License
618 stars 72 forks source link

Reverb and Chorus #24

Open mmontag opened 6 years ago

mmontag commented 6 years ago

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?

schellingb commented 5 years ago

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.

adrianmcroft commented 5 years ago

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 commented 5 years ago

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

Danielku15 commented 5 years ago

@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.

adrianmcroft commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago
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]);
    }
}
ghost commented 2 years ago

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

WindowsNT commented 2 years ago

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

ghost commented 2 years ago

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.

WindowsNT commented 2 years ago

You can use tsf_voice_render and then process the samples.

ghost commented 2 years ago

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.

WindowsNT commented 2 years ago

Yes, a pre-processing callback would be nice.

ghost commented 2 years ago

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

romanbsd commented 2 years ago

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