sinshu / meltysynth

A SoundFont MIDI synthesizer for .NET
Other
145 stars 16 forks source link

Modulation envelope is not convex and has the wrong duration #55

Open spessasus opened 1 week ago

spessasus commented 1 week ago

Hi sinshu,

I've found a bug in the soundfont implementation of meltysynth. It is not related to modulators or unsupported/unplanned features, so I'm opening an issue.

SF Specification, section 8.1.2:

26 attackModEnv This is the time, in absolute timecents, from the end of the Modulation Envelope Delay Time until the point at which the Modulation Envelope value reaches its peak. Note that the attack is “convex”; the curve is nominally such that when applied to a decibel or semitone parameter, the result is linear in amplitude or Hz respectively.

As you can see below, meltysynth uses linear attack instead of convex, and it is too short, compared to fluidsynth: image

This was tested using the soundfont specification test

This code was used to render the file:

using MeltySynth;
using NAudio;
using NAudio.Wave;

class Program
{
    static void Main(string[] args)
    {
        Synthesizer synth =
            new Synthesizer("/home/spessasus/Desktop/SoundFont-Spec-Test/sf_spec_test.sf2", 44100);
        MidiFileSequencer seq = new MidiFileSequencer(synth);
        MidiFile mid = new MidiFile("/home/spessasus/Desktop/SoundFont-Spec-Test/sf_spec_test.mid");
        seq.Play(mid, false); ;
        var audioLeft = new float[(int)(44100 * mid.Length.TotalSeconds / seq.Speed)];
        var audioRight = new float[(int)(44100 * mid.Length.TotalSeconds / seq.Speed)];
        Console.WriteLine(audioLeft.Length);
        seq.Render(audioLeft, audioRight);
        WaveFormat waveFormat = new WaveFormat(44100, 16, 2); // 44100 Hz, 16-bit, Stereo
        using (WaveFileWriter writer = new WaveFileWriter("/home/spessasus/Desktop/meltysynth.wav", waveFormat))
        {
            for (int i = 0; i < audioLeft.Length; i++)
            {
                writer.WriteSample(audioLeft[i]);
                writer.WriteSample(audioRight[i]);
            }
        }
    }
}
sinshu commented 1 week ago

Thank you for the report.

It seems that this issue happens because MeltySynth was made based on TinySoundFont. Below is a similar issue reported for TinySoundFont: https://github.com/schellingb/TinySoundFont/issues/94

About the problem with the short attack time, it looks like TinySoundFont changes the attack based on velocity. It can probably be fixed by just removing this process: https://github.com/sinshu/meltysynth/blob/465b65047534d5f85f92c8ab271058a163fd13a0/MeltySynth/src/RegionEx.cs#L54

For the problem of the attack being linear, would it work to use a function like the release curve of the volume envelope, but flipped upside down? 🤔

image

spessasus commented 1 week ago

Here's the fluidsynth's convex and concave lookup tables. I've implemented them in spessasynth and everything works correctly: https://github.com/FluidSynth/fluidsynth/blob/168183c3b63efdf43ec07449a1a921c2c7b63868/src/gentables/gen_conv.c#L41-L60

sinshu commented 1 week ago

Thank you for the information. However, I cannot use FluidSynth's code because it is under the LGPL, and I want to maintain the MIT license. I'll do some more research on this.