libsidplayfp / libsidplayfp

A library to play Commodore 64 music
GNU General Public License v2.0
58 stars 14 forks source link

Emulate background noise #121

Open drfiemost opened 5 months ago

drfiemost commented 5 months ago

Coming from #102, we could have an (optional) background noise to add realism and a retro feeling.

We need to determine the spectral characteristic and the loudness. Also it should not be computationally expensive, ideally it should be generated at ~1MHz and it should pass through the external filter.

By a quick look at some SOASC samples, where the EXT-IN has been grounded to eliminate the bus noise, what can be noticed is mostly the mains hum.

Maybe we can get a decent result with a square wave at 50/60Hz plus some white or colored noise...

reFX-Mike commented 5 months ago

So, barring any filtering stages, all electronic components' resistance is slightly random. No matter how well you design your circuits, each hardware component has a variable resistance that depends on the material used, the voltage, the temperature, moisture levels in the air, etc.; you name it, it affects it.

It results in pure white noise. Now there might be a low-pass filter in there somewhere, that reduces the higher frequencies, or you might decide you don't like the sound of white noise (too harsh) and use red, pink, or brown noise instead.

The transformer hum is not a square, but a distorted (clipped) sine-wave.

For performance, I would generate it after the external filter (which is static anyway) at the output frequency (usually 44.1 or 48 kHz). I don't think both these features will be too popular anyway. They are undesirable, and distracting, so people will most likely leave them turned off.

reFX-Mike commented 5 months ago

output = std::tanh ( 8.0f * std::sin ( phase ) );

This generates a clipped sine-wave that sounds extremely close to the real thing. The bigger the constant (8.0f is this case) the more harsh it sounds.

https://www.desmos.com/calculator/ukauorqqf0

reFX-Mike commented 5 months ago

Here is a function to generate colored noises:

Somewhere in the class we have float curNoise member (initialized to 0.0)

float coloredNoise ( const float color )
{
    const float whitenoise = frand () * 2.0f - 1.0f;
    curNoise += color * ( whitenoise - curNoise);
    return curNoise * 0.25f;
}

color is between 0.005 (extremely filtered) and 1.0 (white noise)

0.01 sounds pretty good. It's quiet and not annoying. The volume multiplied needs to be adjusted by the color, so the volume gets lower the higher the color is, but it's not linear, and I haven't found a satisfying formula yet. But I guess we will settle on a fixed color anyway, and then we can tweak the volume accordingly.

reFX-Mike commented 5 months ago

I will soon implement a small "post-fx" class to play around with this. It's probably enough to generate a mono-signal and mix it into both output channels (if they exist).

drfiemost commented 5 months ago

Well, if you're going to mix it with the final output this can be done at the frontend level, which may also be a good idea. But I'd like to toy with something added to the signal going into the filter. Just curious to hear if it can loosely resemble a real device or just being annoying.

reFX-Mike commented 4 months ago

Mixing it in before the external filter will suppress the higher frequencies very slightly (3dB), but it has to be done at ~1 MHz and thus requires a lot of CPU for the tanh, etc.

Creating the signal at the resampled output at only 44.1 kHz and adding a 3dB low-pass filter there would be a lot more efficient.

drfiemost commented 4 months ago

Sure, the idea was a simple LCG for white noise plus a square wave at 50/60Hz, but we might get a better result moving this to the frontend.

reFX-Mike commented 4 months ago

I wouldn't go with a pure square. It creates a lot of overtones and needs to be more realistic. A shaped sine with tanh sounds much better (less harsh). If performance is a consideration, we can pre-calculate it for 50/60 Hz at the target sample rate and mix this "sample" in.

reFX-Mike commented 4 months ago

After some experimentation, I agree it's better for the client to do it as a post-FX. We could provide a small, configurable class that adds the noise, but I wouldn't add it to the emulation.

I have a replay gain in my specific use case, so if the noise/hum is added at the emulation level, then the gain multiplier would also apply to the noise. Also, any post-FX I already have (stereo-widening, delay, reverb, loudness, etc.) would all work on the noise/hum as well, and that's not desirable.