Paolo Prandoni, EPFL
Analog vs Digital Transmission

In this notebook we will explore the advantages of digital transmission over analog transmission. We will model the case of transmission over a long (e.g. transoceanic) cable in which several repeaters are used to compensate for the attenuation incurred by the signal.

In [1]:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import IPython
from scipy.io import wavfile

plt.rcParams["figure.figsize"] = (14,4)
In [2]:
def multiplot(*signals):
    for i, s in enumerate(signals):
        plt.subplot(1, len(signals), i+1)
        plt.plot(s);

Analog transmission¶

No description has been provided for this image

When an electrical signal is transmitted over a conductive cable, its power is attenuated in a way that is proportional to the length of the cable; to compensate for this attenuation, the receiving must use an amplifier with a sufficiently high gain.

In standard coaxial copper cables, the attenuation is approximately 1 dB per mile (1.6 Km); a signal will thus incur a 20dB power loss after approximately 32 km or, in terms of voltages, after 32km the signal's amplitude is only 10% of its original value. Fun fact: the unit of measurement called decibel was in fact originally defined as the attenuation induced by one mile of copper cable. (Please have a look at this Wikipedia entry if you are not familiar with the decibel and its usage)

When the first transoceanic telephone lines were being designed in the early decades of the 20th century, with a length in excess of 3200Km, it was clear that a single amplifier at the receiving end would not be enough to recover the transmitted signal. The cable therefore was subdivided into a series of 30Km sections, each followed by an underwater repeating amplifier designed to compensate for the attenuation introduced by each section.

Unfortunately, however, a transmitted signal is not only attenuated but it is also affected by additive noise, which the repeaters amplify as well. If many repeaters are used, the signal to noise ratio at the end of the chain can become too low and the telephone conversation can become unintelligible.

Multiple analog amplifiers¶

Let's formalize the typical setup:

  • the cable is subdivided into 30 km segments, with repeating amplifiers between sections
  • each cable segment attenuates the signal by a factor $1/G$
  • repeater restore the signal's amplitude via a gain factor $G$.

If $x(t)$ is the original signal, the signal at the input of the first repeater will be

$$ x_{1,i}(t) = (1/G)x(t) + \sigma(t) $$

where $\sigma(t)$ is the noise picked up in the first segment.

After the repeater the signal will be

$$ x_{1,o}(t) = Gx_{1,i}(t) = x(t) + G\sigma(t) $$

so that the noise will be $G$ times larger.

At the end of a chain of $N$ repeaters, assuming that the noise is statistically independent, the signal will be approximately equal to

$$ \hat{x}_N(t) = x(t) + NG\sigma(t). $$

No description has been provided for this image

Digital transmission¶

Assume the analog audio signal $x(t)$ has amplitude between $-1$ and $+1$ mV. To prepare the signal for digital transmission:

  1. the appropriate sampling rate $F_s$ is selected (for a voice signal, for instance, $F_s \aaprox 8000~\text{Hz}$)
  2. the analog signal is sampled $F_s$ times per second to obtain the discrete-time sequence $x[n]$
  3. each samples is quantized (i.e. mapped) to one element of a set of possible values $Q$
  4. the quantized samples are converted to a multilevel, piecewise constant analog signal $x_d(t)$

If the sampling frequency is selected properly, no quality is lost because of sampling; the quantization step, howveve, always impacts the signal the quality as we will show presently. The idea is that we are willing to accept this loss in return for the increase robustness when transmitting over long cables.

Quantization¶

First let's define a quantization function that maps input values in $[-1, 1]$ onto a finite set of $2M+1$ values, with $M$ an integer of our choice. To do this, we first rescale the input to the $[-M, M]$ interval and then we drop the fractional part: the input is now mapped onto one of the integers between $-M$ and $M$; finally, we divide the result by $M$ to bring back the quantized signal to its original range.

In [3]:
def quantize(x, M):
    return np.round(x * M) / M

The quantization maps the input to the set $Q = \{-\frac{M}{2M+1}, -\frac{M-1}{2M+1}, \ldots, -\frac{1}{2M+1}, 0, \frac{1}{2M+1}, \ldots, \frac{M-1}{2M+1}, \frac{M}{2M+1}\}$, which contains $2M+1$ possible integer values. Both analog and digital signals have the same range and we can compare them visually:

In [4]:
# the original signal between -1 and 1
x = np.cos(np.arange(0, 13, 0.01))
multiplot(x, quantize(x, 5))
No description has been provided for this image

SNR¶

Obviously the quantized signal is distorted with respect to the smooth original. To quantify this distortion we can compute the so-called Signal to Noise Ratio (SNR); this is the power of the original signal divided by the power of the error introduced by quantization and it is usually expressed on a logarithmic scale in decibels (dB)

In [5]:
def SNR(original, distorted):
    # power of the error
    err = np.sum((original - distorted) ** 2)
    # power of the signal
    sig = np.sum(original ** 2)
    # SNR in dBs
    return 10 * np.log10(sig / err)
In [6]:
SNR(x, quantize(x, 5))
Out[6]:
np.float64(22.437782203667474)

Clearly, quantization over 11 possible level (for $M=5$) leads to a rather low SNR; in practical applications we would use at least 8 bits per sample, which provides a higher SNR:

In [7]:
# 8 bits means 255 levels; 2M+1 = 255 => M = 127
SNR(x, quantize(x, 127))
Out[7]:
np.float64(50.101509835276794)

Finally, let's listen to the effects of quantization using an actual audio signal. We can read in an audio file from disk using the wavfile.read() function, which returns the audio data and the playback rate needed by the playback function:

In [8]:
rate, s = wavfile.read('speech.wav')
# let's normalize the signal over the [-1, 1] interval
s = s / np.max(np.abs(s))
plt.plot(s);
No description has been provided for this image

Can we hear the effects of quantization? (Use headphones!)

In [9]:
IPython.display.Audio(s, rate=rate)
Out[9]:
Your browser does not support the audio element.
In [10]:
IPython.display.Audio(quantize(s, 127), rate=rate)
Out[10]:
Your browser does not support the audio element.

Indeed, the quantized version has a lower quality than the original but, as we will see, this is a small price to pay when we take the effects of the communication channel into account.

Let's also compute for later the SNR for the audio quantized over 255 levels:

In [11]:
SNR(s, quantize(s, 127))
Out[11]:
np.float64(36.415461820105264)

Performance comparison¶

Let's first define some functions that model the transmission of audio over a cable segment terminated by a repeater.

Additive noise¶

Each cable section will introduce attenuation and additive noise. The attenuation is specified in dB and the noise level is parametrized in terms of the signal to noise ratio at the end of the cable section. Remember that the SNR is defined as $10\log_{10}(\sigma_x^2 / \sigma_0^2)$, where $\sigma_x^2$ is the power of the signal and $\sigma_0^2$ the power of the noise. Since the amplitude of the signal at the end of the cable section will be attenuated, we take that into account to compute the amplitude of the noise.

We model the noise as a discrete-time white process where every sample is a random number drawn from a uniform distribution over the interval $[-A, A]$; the power of this signal is equal to the second moment of the distribution, namely, $\sigma_0^2 = A^2/3$.

In [12]:
def cable_section(x, att_db, snr_db):
    # amplitude attenuation from dB value
    att = 10 ** (-att_db / 20)    
    # noise amplitude from SNR at the end of the section
    A = att / (10 ** (snr_db / 20)) * np.sqrt(3)
    noise = np.random.uniform(-A, A, len(x))
    return x * att + noise

Let's check that the SNR is indeed close to the theoretical value; the difference is due to the fact that the input signal's energy is less than one:

In [13]:
SNR(x, cable_section(x, 0, 50))
Out[13]:
np.float64(47.21438152917617)

Amplification and regeneration¶

The repeating amplifier simply multiplies the input by the appropriate gain factor; here too we specify the gain in dB:

In [14]:
def repeater(x, gain_db):
    gain = 10 ** (gain_db / 20)
    return x * gain

For digital signals, the repeater will also perform signal regeneration: since the digital encoding only uses a fixed number of levels, which are known, the repeating amplifier can threshold the output signal and eliminate the additive noise (provided that the noise is not too large). Since this happens at each stage, even after several repeaters the trasmission will remain noise-free.

In [15]:
def regen(x, M):
    # to regenerate an M-level signal between -1 and 1, first map it to [-M, M]
    #  and then discard the fractional part
    return np.round(x * M) / M

One-stage test¶

Let's look at a simple example. Here we show the transmitted signal and the signal at the exit of the first repeater for analog transmission; as you can see the noise is amplified along with the signal:

In [16]:
# simple signal
xa = np.cos(np.arange(0, 13, 0.01))

# 20 dB attenuation and a SNR of 25dB at the end of the section
ATT_DB, SRN_DB = 20, 25

# analog transmission
xa_t = cable_section(xa, ATT_DB, SRN_DB)
xa_r = repeater(xa_t, ATT_DB)
multiplot(xa, xa_t, xa_r)
No description has been provided for this image

Clearly the noise is preserved by the amplifier. Now let's see what happens with a digital encoding: although the signal is corrupted by noise, the repeater can regenerate it exactly:

In [17]:
M = 5
xd = quantize(np.cos(np.arange(0, 13, 0.01)), M)
xd_t = cable_section(xd, ATT_DB, SRN_DB)
# regenerate after amplification
xd_r = regen(repeater(xd_t, ATT_DB), M)
multiplot(xd, xd_t, xd_r)
No description has been provided for this image

Multi-stage transmission¶

Let's now cascade several segments. First the analog transmission chain:

In [18]:
def analog_tx(x, num_repeaters, attenuation, noise_amplitude):
    for n in range(0, num_repeaters):
        x = repeater(cable_section(x, attenuation, noise_amplitude), attenuation)
    return x

and then the digital transmission chain, where we regenerate the signal after each repeater:

In [19]:
def digital_tx(x, num_repeaters, attenuation, noise_amplitude, levels=127):
    x = quantize(x, levels)
    for n in range(0, num_repeaters):
        x = regen(repeater(cable_section(x, attenuation, noise_amplitude), attenuation), levels)
    return x

Let's compare transmission schemes with some realistic values: we use about 100 amplifier as we would in a 3000Km cable, each with a 20dB attenuation/gain and each with a realistic SNR of 55dB:

In [20]:
NUM_REPEATERS = 100
ATT_SEC = 20
SNR_SEC = 55

ya = analog_tx(s, NUM_REPEATERS, ATT_SEC, SNR_SEC)
print ('Analog trasmission: SNR = %f dB' % SNR(s, ya))    

M = 127
yd = digital_tx(s, NUM_REPEATERS, ATT_SEC, SNR_SEC, M)
print ('Digital trasmission: SNR = %f dB' % SNR(s, yd))    
Analog trasmission: SNR = 18.146029 dB
Digital trasmission: SNR = 36.415462 dB

As you can see, while the analog transmission incurs a significant loss of quality, the SNR after digital transmission has not changed at all! And now this difference should be very easy to hear:

In [21]:
IPython.display.Audio(ya, rate=rate)
Out[21]:
Your browser does not support the audio element.
In [22]:
IPython.display.Audio(yd, rate=rate)
Out[22]:
Your browser does not support the audio element.

Increasing the noise¶

The signal regeneration mechanism will work only if the noise amplitude is smaller than the interval between quantization level. When this is not the case, the regenerator will make a mistake and these mistakes will introduce additional distortion which, ultimately, will negate the benefits of digital transmission.

For instance, if the noise is too large, the voice signal becomes rather unintelligible in both cases:

In [23]:
SNR_SEC_BAD = 35

ya = analog_tx(s, NUM_REPEATERS, ATT_SEC, SNR_SEC_BAD)
print ('Analog trasmission: SNR = %f dB' % SNR(s, ya))    

M = 127
yd = digital_tx(s, NUM_REPEATERS, ATT_SEC, SNR_SEC_BAD, M)
print ('Digital trasmission: SNR = %f dB' % SNR(s, yd))    
Analog trasmission: SNR = -1.829731 dB
Digital trasmission: SNR = -1.964130 dB
In [24]:
IPython.display.Audio(ya, rate=rate)
Out[24]:
Your browser does not support the audio element.
In [25]:
IPython.display.Audio(yd, rate=rate)
Out[25]:
Your browser does not support the audio element.

Quantization vs Noise¶

Note however that, in the case of digital transmission, we can reduce the number of quantization levels until the regenerator is again able to combat the noise. The resulting transmission SNR will be lower, but it will be lower in a controlled fashion, determined by the characteristic of the quantizer. For instance, using the settings above, we can reduce the number of levels to 14 and obtain the same SNR as for simple quantization:

In [26]:
M = 14
SNR(s, quantize(s, M))
Out[26]:
np.float64(17.272934280056717)
In [27]:
yd = digital_tx(s, NUM_REPEATERS, ATT_SEC, SNR_SEC_BAD, M)
print ('Digital trasmission: SNR = %f dB' % SNR(s, yd))    
Digital trasmission: SNR = 17.272934 dB

The audio quality is not great but decidedly better than the one obtained by analog transmission:

In [28]:
IPython.display.Audio(yd, rate=rate)
Out[28]:
Your browser does not support the audio element.
In [29]:
IPython.display.Audio(ya, rate=rate)
Out[29]:
Your browser does not support the audio element.