[Feature Request] Bring back pink noise into testing signals
After I migrated my preset from PulseEffects 4 to EasyEffects 7 I noticed that EasyEffects is missing options like pink noise, violet noise, gaussian noise, or red noise in testing signals I used to calibrate sound. Can we bring those back?
vs
Can we bring those back?
I would love to but it is a little tough. In the past we offered these modes because we used GStreamer. And GStreamer has all those algorithms implemented. But once we moved to PipeWire's native DSP filters staying on GStreamer did not make sense anymore. But the downside is that we lost the things GStreamer offers for free.
The case of the sine wave generator and the white noise was simpler to implement by myself on our side. The others will require more effort. And also knowledge I do not have right now. If there is somewhere a simple algorithm for them I am willing to bring them back.
If there is somewhere a simple algorithm for them I am willing to bring them back.
Try to ask to ChatGPT/DeepSeek. Maybe they can provide one.
Try to ask to ChatGPT/DeepSeek. Maybe they can provide one.
I should have thought about this before 😄 . They actually suggested one based on what they called "Voss algorithm". I do not have time right now to test it but it is probably better than nothing.
They actually suggested one based on what they called "Voss algorithm".
For pink noise generation. I did not ask about the others yet.
I will add this to the Qt port to do list so I do not forget about going back to chatgpt.
We can also copy the part of code that handles this from GStreamer? Because I think they have it licensed as Open Source.
We can also copy the part of code that handles this from GStreamer? Because I think they have it licensed as Open Source.
It should be fine as far as license is concerned. I expect the technical side to be the challenge. Things are handled so differently in GStreamer source code that copy and paste may not be easy. It may be necessary to understand what is being done and modify the code accordingly while doing the port.
I can contribute the basic approach. First, some Python. Pink noise is constructed by creating vectors with decreasing random magnitude and random phase. The vector length should decrease according to 1 / sqrt(f) to create pink noise:
#!/usr/bin/python3
import matplotlib.pyplot as plt
import math, random, scipy
x = [0] * 4096
for _ in range(1, len(x)//2): # skip index 0, the DC offset
mag = random.random() / (_ ** 0.5)
pha = random.random() * 2 * math.pi
# fill positive and negative frequencies to create real-valued signal
x[_] = math.cos(pha) * mag + math.sin(pha) * mag * 1j
x[len(x) - _] = x[_].conjugate()
# this is as close to real signal as numerical precision allows
pink = scipy.fft.ifft(x)
spectrum = abs(scipy.fft.fft(pink))
spectrum = spectrum[0 : len(spectrum) // 2]
plt.figure(figsize=(12, 8), tight_layout=True)
plt.loglog(spectrum)
plt.ylim([1e-3, None]);
plt.show()
The pink is the noise as real-valued samples; spectrum is the rendering of it. The magnitudes are computed, negative frequencies are removed and what remains get placed into log-log plot, showing the result.
I think that the buffer length has to have at least some size for the pink noise spectrum to be accurate. Maybe something like 1024 samples at once should be produced, or something like that. I created 4096 samples of pink noise, because I wanted to show that descending magnitude clearly.
Since EasyEffects already depends on fftw3, this approach should be readily adaptable to it.
Maybe even more optimized version:
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import random, math
N = 4096
x = np.zeros(N, dtype=complex)
# DC component zero
x[0] = 0.0
# Fill positive frequencies
for k in range(1, N//2):
mag = random.random() / (k ** 0.5)
pha = random.random() * 2 * math.pi
x[k] = mag * complex(math.cos(pha), math.sin(pha))
x[N - k] = np.conj(x[k])
# Nyquist bin real-valued
x[N//2] = random.random()
# Inverse FFT to get real signal
pink = np.fft.ifft(x).real
# Compute magnitude spectrum
spectrum = abs(np.fft.fft(pink))[:N//2]
# Plot
plt.figure(figsize=(12, 8), tight_layout=True)
plt.loglog(spectrum)
plt.ylim([1e-3, None])
plt.xlabel('Frequency Bin')
plt.ylabel('Magnitude')
plt.title('Pink Noise Spectrum (1/f Behavior)')
plt.show()
Well, we can elaborate this until the cows come home, but it has to become C++ before it actually does the thing. If you're willing to write the C++ implementation, that would be very good. However, I happened to notice an alternative approach from trusty old Julius O. Smith. He published 3rd order filter parameters that converts white noise to pink noise with high accuracy:
B = [0.049922035 -0.095993537 0.050612699 -0.004408786]; A = [1 -2.494956002 2.017265875 -0.522189400];
Analysis here: https://ccrma.stanford.edu/%7Ejos/sasp/Example_Pink_Noise_Analysis.html and I think it shows that this will do the job without having to deal with FFTW3 at all.
https://github.com/alankila/easyeffects/tree/feature/pink-noise
Implemented here according to the filter.
By the way, before using this, it's best to validate that it works at any sampling rate. I have my doubts now that I slept on it. I'll have to basically write a test program to make sure that these coefficients don't have to change depending on the sampling rate of the noise. If they do, then this method likely goes to the rubbish bin because these are probably result of some numerical fit, and there might be no actual method for calculating them.
Even the FFT method probably has to compute the actual frequency in each bin so that the produced noise is actually pink noise in the desired sampling rate. f = index only in the case where number of bins equals the desired sampling rate of the noise.
It seems that the level of noise within the passband does vary, but slope does not. I guess I doubted the master for no reason.
We still need to compensate for this. The level appears to increase at higher sampling rates, probably because the slope starts at a later frequency and always does down at -3 dB/oct from there. 6 dB indicates factor of two, e.g. double rate of noise, halve the gain factor.
I determined that the approximate factor is (1 << 18) / sampling rate which clips slightly at 48k and below, but this is probably acceptable for now.
I determined that the approximate factor is (1 << 18) / sampling rate which clips slightly at 48k and below, but this is probably acceptable for now.
Ok. Although I think the small difference in level is not a big deal for our case. The user is free to change his volume control anyway.
Ok. Although I think the small difference in level is not a big deal for our case. The user is free to change his volume control anyway.
I'm not so sure. It actually may be big deal, because test signal is here to calibrate the output. E.g. we use it when it's not calibrated yet. In that case, difference between pink noise and real output could affect quality of calibration process.
I'm not so sure. It actually may be big deal, because test signal is here to calibrate the output. E.g. we use it when it's not calibrated yet. In that case, difference between pink noise and real output could affect quality of calibration process.
It depends on what you have in mind when you think about calibration. If the difference between sampling rates implies in for example having the maximum of the curve at different frequency then you are right. This will obviously affect the room calibration as far as frequency response is concerned. But looking at the chart above the main difference above 100 Hz seems to be just a shift in volume level. With some minor difference in the maximum below 100 Hz that may not be relevant.
But if you are thinking about volume level calibration the situation changes. For example the user may decide to set the system volume at 50% instead of 100%. PipeWire or the driver may add some kind of amplification. If you really want to do the calibration at a specific signal level you will have to use some kind of calibrated sensor and adjust the system volume accordingly to achieve the desired level. There is no way to guarantee the final level from EasyEffects side. But If the difference from one curve to another is just a shift in volume levels and the person doing the calibration has a realiable way to set the final level it is just a matter of adjusting the system volume accordingly. From this point of view it is not a big deal a shift in the volume level.
Our current master branch has a simple pink noise generator based on Paul Kellet's filter. It seems good enough for our purposes. If necessary in the future we could try something more elaborated.