How does Quantization Noise sound?ΒΆ

In a last article, we explained the mathematical effect of quantization and what the resulting quantization noise is. In this article, we will hear, how the quantization noise actually sounds. As a teaser, listen to the following:

# For running this code, the code snippets below need to be run beforehand
display(HTML("Original signal:" + Audio(data=data_music, rate=rate)._repr_html_()))
showQuantization(data_music, U=1,bits=4, showSignals=False);
Original signal:
Quantized to q=4 bitsQuantization Noise

Clearly, we hear a significant noise floor below the original music signal. Let's look deeper into what actually happens. First, we define a function to load some audio file from the internet:

def loadAudio(url, start, length):
    R = requests.get(url)
    with open("sound.mp3", "wb") as f:
        f.write(R.content)
    !ffmpeg -y -i sound.mp3 sound.wav > /dev/null 2>&1
    rate, data = wavfile.read("sound.wav")
    if len(data.shape) > 1:  # stereo to mono conversion
        data = data.sum(axis=1)
    data = (1.0 * data / abs(data).max()).astype(np.float32)
    
    dataPart = data[rate*start+np.arange(min(rate*length, len(data)))]
    targetRate = 10000   # Resample signal to 10kHz sampling rate
    targetSamples = int(len(dataPart) * targetRate / rate) 
    resampled = signal.resample(dataPart, targetSamples) 
    return targetRate, resampled / abs(resampled).max()

# Utility function two display two audios side by side in the notebook
def audioSideBySide(name1, audio1, name2, audio2):
    text = '
%s%s
%s%s
'
% (name1, name2, audio1._repr_html_(), audio2._repr_html_()) display(HTML(text))

Then, we load some music from the internet and extract a portion of 10 seconds length out of it:

url_music = "http://www.scientificinvesting.eu/a/Mozart%20-%20Symphony%20n.10%20K.74%20in%20G%20-%201%20Allegro.mp3"
rate_music, data_music = loadAudio(url_music, 40, 10)
rate = rate_music

Next, we define the functions for calculation of the quantization thresholds and for performing the actual quantization. Sure, we have shamelessly copied them from our previous article:

# Calculate the quantization levels with a uniform quantizer
def calcLevels(U, b, quantization_type):
    N_levels = 2**b
    delta = 2*U / N_levels
    
    if quantization_type == 'mid-rise':
        levels = -U + delta/2 + np.arange(N_levels) * delta 
    elif quantization_type == 'mid-tread':
        levels = -U + np.arange(N_levels) * delta
    else:
        raise RuntimeError("Unknown quantization type!")
    
    return levels

# Map the input array x to the nearest values in S
def quantize(x, S):
    X = x.reshape((-1,1))
    S = S.reshape((1,-1))
    dists = abs(X-S)
    
    nearestIndex = dists.argmin(axis=1)
    quantized = S.flat[nearestIndex]
    
    return quantized.reshape(x.shape)

Let us now define a convenience function that performs the quantization of a signal, shows the resulting signals and creates the audio objects:

def showQuantization(audio, U, bits, quantization_type='mid-rise', showNoise=True, showSignals=True):
    S = calcLevels(U=U, b=bits, quantization_type=quantization_type)
    quantized = quantize(audio, S)    # Perform quantization
    q_noise = audio - quantized       # Calculate quantization noise
    
    P_signal = sum(abs(audio**2))        # Calculate SNR in dB
    P_noise = sum(abs(q_noise**2))
    SNR = 10*np.log10(P_signal/P_noise)
    
    audioSideBySide("Quantized to q=%d bits" % bits, Audio(data=quantized, rate=rate),
                    "Quantization Noise", Audio(data=q_noise, rate=rate))
    t = np.arange(len(audio)) / rate    
    if showSignals:
        plt.plot(t, audio, label='Original')
        plt.plot(t, quantized, label='Quantized to q=%d bits' % bits)
        if showNoise: 
            plt.plot(t, q_noise, label='Quantization Noise')
    
    return t, quantized, q_noise

Now, we are ready to listen to the quantized music and also look at the resulting quantized signals. First, listen to the original signal once again:

Audio(data=data_music, rate=rate)