#!/usr/bin/env python # coding: utf-8 #

# #

# # ### Prof. Dr. -Ing. Gerald Schuller
Jupyter Notebook: Renato Profeta # # # In[1]: # For Google Colab Only try: import google.colab get_ipython().system('pip uninstall plotly -y') get_ipython().system('pip install plotly==3.10.0') except Exception as e: print("Not inside Google Colab: %s. Using standard configurations." % (e)) # In[2]: # For Google Colab Only inColab=False try: import google.colab import plotly.io as pio pio.renderers.default = 'colab' def enable_plotly_in_cell(): import IPython from plotly.offline import init_notebook_mode display(IPython.core.display.HTML('''''')) init_notebook_mode(connected=False) inColab=True except Exception as e: print("Not inside Google Colab: %s. Using standard configurations." % (e)) # # Quantization # In[3]: get_ipython().run_cell_magic('html', '', '\n') # **Quantization** is the process of mapping a continuous range of values into a finite range of discrete values. # In[4]: get_ipython().run_cell_magic('html', '', '\n') # #### Python Example # Assume our A/D converter has an input range of -1V to 1V, 4 bit accuracy (meaning we have a total of $2^4$ codewords or indices), and the A/D converter has **0.2 V** at its **input**. # In[5]: # Stepsize range_max=1 # Maximum input range range_min=-1 # Minumum input range N=4 # Number of bits stepsize=(range_max-range_min)/(2**N) stepsize # Next we get **quantization index** which is then encoded as a **codeword:** # In[6]: input_voltage=0.2 index = round(input_voltage/stepsize) index # **Observe:** If the quantization **stepsize is constant**, independent of the signal, we call it a “**uniform quantizer**”.
# The index then is **coded** using the 4 bits and sent to a decoder, for instance using the 4 bit binary **codeword** “0010”. The first bit usually is the sign bit. The **decoder reconstructs** the voltage by first decoding the codeword to an index, and for instance by **multiplying the index** with the **stepsize:** # In[7]: reconstr=stepsize*index reconstr # This is also called the **(de-)quantized signal**, and its difference to the original value or signal is called the **quantization error**. In our example the quantization error is **Quantized Value – Original Value** = 0.25V-0.2V=**0.05 V** #
# **Observe:** There is always a range of voltages which is mapped to the same codeword. We call this range $\Delta$, or **stepsize**. These steps represent the quantization in the A/D conversion process, and they lead to quantization errors.
# The output after quantization is a linear **“Pulse Code Modulation” (PCM)** signal. It is linear in the sense that the code values are proportional to the input signal values.
# # ## Quantization Error # In[8]: get_ipython().run_cell_magic('html', '', '\n') # Let's now call our quantization error “**e**”. Then the **quantization error power** is the **expectation** value of the squared quantization error **e**:
# # $$ \large # E(e^2)=\int_{-\Delta /2}^{\Delta /2}e^2.p(e)de$$ # # where **$p(e)$** is the probability of error value **e**. Here we compute the power of each possible error value **e** by squaring it, and multiply it with its probability to obtain the **average power**. #

# This number will give us some impression of the signal quality after quantization, if we set it in **relation to the signal energy**. Then we get a **Signal to Noise Ratio** (SNR) for our quantizer and A/D converter.
# Assume the quantization error **e** is uniformly distributed (all possible values of the quantization error e appear with equal probability), which is usually the case if the signal is much larger than the quantization step size $\Delta$ (large signal condition). Since the integral over the probabilities of all possible values of **e** must be 1, and the possible values of e are between $-\Delta /2$ and $\Delta /2$, # # $$ \large # 1=\int_{-\Delta /2}^{\Delta /2}p(e)de=p(e) \cdot \int_{-\Delta /2}^{\Delta /2}de=p(e) \cdot \Delta$$ # # # we have # # $$ \large p(e)=1/\Delta$$ # which yields # # $$ \large E(e^2)=\frac{1} {\Delta} \cdot \int_ {-\Delta/2} ^ {\Delta/2} e^2 de # = \frac{1} {\Delta} \left(\frac{(\Delta/2)^3} {3} - \frac{(-\Delta/2)^3} {3} \right) = \frac{\Delta^2}{12}$$ # # Hence the **quantization error power for a uniform quantizer** with stepsize $\Delta$ and with a large signal is: # # $$ \large E(e^2)=\frac{\Delta^2}{12} $$ # ## Mid-Rise and Mid-Tread Quantization # In[9]: get_ipython().run_cell_magic('html', '', '\n') # Depending on if the quantizer has the input voltage 0 at the center of a quantization interval or on the boundary of that interval, we call the quantizer a mid-tread or a mid rise quantiser, as illustrated in the following picture: # #
# # (From:http://eeweb.poly.edu/~yao/EE3414/quantization.pdf) # # # Here, $Q_i(f)$ is the index after quantization (which is then encoded and sent to the receiver), and Q(f) is the de-quantization, which produces the quantized reconstructed value at the receiver.

# # This makes mainly a difference at **very small input values**. For the mid-rise quantiser, very small values are always quantized to +/- half the quantization interval ($\pm \Delta/2$ ), whereas for the mid-tread quantizer, very small input values are always rounded to zero. You can also think about the **mid-rise** quantizer as **not having a zero** as a reconstruction value, but only very small positive and negative values.
# # So the mid-rise can be seen as more accurate, because it also reacts to very small input values, and the mid tread can be seen as saving bit-rate because it always quantizes very small values to zero.
# # Observe that the expectation of the quantization error power for large signals stays the same for both types. # **Observe:** the Mid-Tread quantizer “swallows” small signal levels, since they are all rounded to zero. # The Mid-Rise quantizer still captures small levels, but distorted. # #### Python Example # In[10]: get_ipython().run_cell_magic('html', '', '\n') # In[11]: # Imports import numpy as np import matplotlib.pyplot as plt import plotly.offline import plotly.tools as tls import plotly.plotly as py # Configurations plotly.offline.init_notebook_mode(connected=True) import warnings; warnings.simplefilter('ignore') # In[12]: # Signal Processing Parameters Fs = 32000 # Sampling frequency T=1/Fs # Sampling Time # In[13]: # Input Signal A=1 freq=500 n_period=1 period=np.round((1/freq)*n_period*Fs).astype(int) t = np.arange(Fs+1)*T # Time vector sinewave = A*np.sin(2*np.pi*freq*t) # In[14]: # Quantization and De-quantization N=4 stepsize=(1.0-(-1.0))/(2**N) #Encode sinewave_quant_rise_ind=np.floor(sinewave/stepsize) sinewave_quant_tread_ind=np.round(sinewave/stepsize) #Decode sinewave_quant_rise_rec=sinewave_quant_rise_ind*stepsize+stepsize/2 sinewave_quant_tread_rec=sinewave_quant_tread_ind*stepsize # In[15]: # Shape for plotting t_quant=np.delete(np.repeat(t[:period+1],2),-1) sinewave_quant_rise_rec_plot=np.delete(np.repeat(sinewave_quant_rise_rec[:period+1],2),0) sinewave_quant_tread_rec_plot=np.delete(np.repeat(sinewave_quant_tread_rec[:period+1],2),0) # In[16]: # Quantization Error quant_error_tread=sinewave_quant_tread_rec-sinewave quant_error_rise=sinewave_quant_rise_rec-sinewave # In[17]: #plot if inColab: enable_plotly_in_cell() fig = plt.figure(figsize=(12,8)) plt.subplot(2,1,1) plt.plot(t[:period+1],sinewave[:period+1], label='Original Signal') plt.plot(t_quant,sinewave_quant_rise_rec_plot, label='Quantized Signal (Mid-Rise)') plt.plot(t_quant,sinewave_quant_tread_rec_plot, label='Quantized Signal (Mid-Tread)') plt.title('Orignal and Quantized Signals', fontsize=18) plt.xlabel('Time [s]') plt.ylabel('Amplitude') plt.yticks(np.arange(-1-stepsize, 1+stepsize, stepsize)) #plt.legend() plt.grid() #plt.tight_layout() plt.subplot(2,1,2) plt.plot(t[:period+1],quant_error_tread[:period+1], label='Quantization Error') plt.grid() plt.title('Quantization Error (Mid-Tread)', fontsize=18) plt.xlabel('Time [s]') plt.ylabel('Amplitude') #plt.tight_layout() plt.subplots_adjust(hspace=0.5) plotly_fig = tls.mpl_to_plotly(fig) plotly_fig.layout.update(showlegend=True) plotly.offline.iplot(plotly_fig) # In[18]: # Listen to Audio import IPython.display as ipd print('Orignal Signal') ipd.display(ipd.Audio(sinewave, rate=Fs)) print('Quantized Signal (Mid-Tread)') ipd.display(ipd.Audio(sinewave_quant_tread_rec, rate=Fs)) print('Quantized Signal (Mid-Rise)') ipd.display(ipd.Audio(sinewave_quant_rise_rec, rate=Fs)) print('Quantization Error (Mid-Tread)') ipd.display(ipd.Audio(quant_error_tread, rate=Fs)) print('Quantization Error (Mid-Rise)') ipd.display(ipd.Audio(quant_error_rise, rate=Fs)) # In[19]: # Imports from scipy.fftpack import fft import plotly.offline import plotly.tools as tls import plotly.plotly as py # Configurations if inColab==False: plotly.offline.init_notebook_mode(connected=True) else: enable_plotly_in_cell() import warnings; warnings.simplefilter('ignore') # Signal Processing Parameters NFFT=2**10 #Frequency Analysis freqs = np.fft.fftfreq(NFFT, d=T) # Frequency bins original_fft=fft(sinewave, n=NFFT) original_fft/=np.abs(original_fft).max() quantized_tread_fft=fft(sinewave_quant_tread_rec, n=NFFT) quantized_tread_fft/=np.abs(quantized_tread_fft).max() quantized_rise_fft=fft(sinewave_quant_rise_rec, n=NFFT) quantized_rise_fft/=np.abs(quantized_rise_fft).max() # Plot fig=plt.figure(figsize=(12,8)) plt.plot(freqs[0:NFFT//2],20*np.log10(np.abs(quantized_rise_fft[0:NFFT//2]).clip(min=1e-5)), label='Quantized Mid-Rise') plt.plot(freqs[0:NFFT//2],20*np.log10(np.abs(quantized_tread_fft[0:NFFT//2]).clip(min=1e-5)), label='Quantized Mid-Tread') plt.plot(freqs[0:NFFT//2],20*np.log10(np.abs(original_fft[0:NFFT//2]).clip(min=1e-5)), label='Original') plt.grid() plt.title('Original vs. Quantized Signal Spectrum') plt.ylabel('Magnitude Normalized') plt.xlabel('Frequency [Hz]') #plt.legend() plotly_fig = tls.mpl_to_plotly(fig) plotly_fig.layout.update(showlegend=True) plotly.offline.iplot(plotly_fig) # ## Real-time Audio Python Example # # **Real-Time Audio Examples will not work in remote environments such as Binder and Google Colab** # In[ ]: get_ipython().run_cell_magic('html', '', '\n') # In[ ]: """ PyAudio Example: Make a quantization between input and output (i.e., record a few samples, quatize them with a mid-tread or mid-rise quantizer, and play them back immediately). Using block-wise processing instead of a for loop Gerald Schuller, Octtober 2014 Modified to Jupyter Notebook by Renato Profeta, October 2019 """ # Imports import pyaudio import struct import numpy as np from ipywidgets import ToggleButton, Dropdown, Button, BoundedIntText, Label from ipywidgets import HBox, interact import threading # Parameters CHUNK = 5000 #Blocksize FORMAT = pyaudio.paInt16 #conversion format for PyAudio stream CHANNELS = 1 RATE = 32000 #Sampling Rate in Hz # Quantization Bit-Depth N=8 quant_type='Mid-Tread' # Quantization Application def quantization_example(toggle_run): global N, quant_type while(True): if toggle_run.value==True: break #Reading from audio input stream into data with block length "CHUNK": data_stream = stream.read(CHUNK) #Convert from stream of bytes to a list of short integers #(2 bytes here) in "samples": shorts = (struct.unpack( 'h' * CHUNK, data_stream )); samples=np.array(list(shorts),dtype=float); #start block-wise signal processing: q=int((2**15-(-2**15))/(2**N)) if quant_type=='Mid-Tread': #Mid Tread quantization: indices=np.round(samples/q) #de-quantization: samples=indices*q else: #Mid -Rise quantizer: indices=np.floor(samples/q) #de-quantization: samples=(indices*q+q/2).astype(int) #end signal processing #converting from short integers to a stream of bytes in "data": #play out samples: samples=np.clip(samples, -2**15,2**15) samples=samples.astype(int) data=struct.pack('h' * len(samples), *samples); #Writing data back to audio output stream: stream.write(data, CHUNK) # GUI toggle_run = ToggleButton(description='Stop') button_start = Button(description='Start') dropdown_type = Dropdown( options=['Mid-Tread', 'Mid-Rise'], value='Mid-Tread', description='Quantization Type:', disabled=False, ) bitdepth_int = BoundedIntText( value=8, min=2, max=16, step=1, description='Bit-Depth:', disabled=False ) q=int((2**15-(-2**15))/(2**N)) stepsize_label = Label(value="Stepsize: {:d}".format(q)) def start_button(button_start): thread.start() button_start.disabled=True button_start.on_click(start_button) def on_click_toggle_run(change): if change['new']==False: stream.stop_stream() stream.close() p.terminate() plt.close() toggle_run.observe(on_click_toggle_run, 'value') def inttext_bitdepth_changed(bitdepth_int): global N, q if bitdepth_int['new']: N=bitdepth_int['new'] stepsize_label.value="Stepsize: {:d}".format(int((2**15-(-2**15))/(2**N))) bitdepth_int.observe(inttext_bitdepth_changed, names='value') def dropdown_type_changed(dropdown_type): global quant_type if dropdown_type['new']: quant_type=dropdown_type['new'] dropdown_type.observe(dropdown_type_changed, names='value') box_buttons = HBox([button_start,toggle_run]) box_controls = HBox([bitdepth_int, dropdown_type,stepsize_label]) # Create a Thread for run_spectrogram function thread = threading.Thread(target=quantization_example, args=(toggle_run,)) # Start Audio Stream # Create p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, output=True, frames_per_buffer=CHUNK) input_data = stream.read(CHUNK) samples = np.frombuffer(input_data,np.int16) display(box_buttons) display(box_controls) # In[ ]: