Radio is the one thing I'd like to play with, but I have not. For several reasons, this interest of mine always end up buried deep at in-tray, and perhaps I'll never fiddle with it, after all.
For a signal to travel over radio waves, it must be modulated first, which means having it transposed to a frequency bandwidth and format suitable to the transmission medium. Modulation techniques are closely related to telecommunications and information theory, and it is possible to play with modulation within the limits of a computer. This is a theme that I must confess I don't understand much, but I am mesmerized by it nevertheless. And to be honest, I find incredible how a couple simple calculations can make the miracle.
In this post, I am going to illustrate the AM modulation techniques, using WAV audio files. To begin with, we need a baseband signal, that is, some audio that fills all available bandwidth from 0Hz. I have recorded my own, ugly voice in order to torture your ears:
The bandwidth or frequency spectrum of this speech is:
The yellow part of the spectrum is the relevant one, since the Y axis is logarithmic (dB). 20 decibels means a difference of 100 times, so the spectrum contents above 5000Hz is mostly "digital noise", not actual content. In the next graphs I will crop out the bottom of the graph. (If you are curious or don't trust my graphs, you can always download the WAV files and look into their spectrum yourself.)
AM modulation demands that signal is restricted to a bandwidth, so the modulated signal will also "fit" in a predetermined frequency band. So, we need to apply a low-pass filter on my voice, and we define the cut-off at 1000Hz. The result is my voice in lower quality.
The spectrum of this filtered signal is:
I have used an Audacity's low-pass filter plug-in, which is not perfect but it is enough to illustrate our points. In other posts, I write a filter from scratch, in Python.
Amplitude modulation is just multiplying the original signal by a sinusoidal wave, the carrier. In this case, I have chosen a 3000Hz carrier, well above the cut-off of original signal, which is 1000Hz. The carrier noise itself is:
Modulation result (voice multiplied by whistle) is:
Note that it is almost impossible to understand the speech. To be exact, this modulation is named AM-SC (Supressed Carrier), for a reason that I will clarify a bit later.
Modulation makes my voice to exchange band from 0-1000 to 2000-4000Hz. Actually, AM modulation creates two "copies" of the original signal, one at 2000-3000Hz band and another at 3000-4000Hz. So, normal AM "wastes" bandwidth because modulated signal occupies twice as much of bandwidth:
Nevertheless, AM is still quite useful, because I can "move" my voice to frequencies suitable for electromagnetic transmission. And, even more important, modulation allows allocation of multiple signals on the same medium, each one occupying a different band, and each one can be recovered separately.
The "normal", broadcasting AM radio modulation sounds like this:
In this modulation, we can hear the carrier, while we could not in AM-SC. The spectrum is similar to AM-SC but it has an additional sharp "spike" at carrier frequency:
The following Python program was employed to generate the modulated AM and AM-SC audios:
#!/usr/bin/env python import wave, struct, math baseband = wave.open("paralelepipedo_lopass.wav", "r") amsc = wave.open("amsc.wav", "w") am = wave.open("am.wav", "w") carrier = wave.open("carrier3000.wav", "w") for f in [am, amsc, carrier]: f.setnchannels(1) f.setsampwidth(2) f.setframerate(44100) for n in range(0, baseband.getnframes()): base = struct.unpack('h', baseband.readframes(1)) / 32768.0 carrier_sample = math.cos(3000.0 * (n / 44100.0) * math.pi * 2) signal_am = signal_amsc = base * carrier_sample signal_am += carrier_sample signal_am /= 2 amsc.writeframes(struct.pack('h', signal_amsc * 32767)) am.writeframes(struct.pack('h', signal_am * 32767)) carrier.writeframes(struct.pack('h', carrier_sample * 32767))
AM-SC is simply the original signal (whose band has been restricted by a lowpass filter) multiplied by the carrier, which is simply a sinusoidal wave.
AM modulation is the same as AM-SC, except that carrier is added afterwards. This allows the AM receiver to be much simpler, as we will see.
Well, it's time to proof that we can extract the original voice from these modulated audios. The AM-SC demodulation is as simple as multiplying the signal again by the same carrier (this technique is called "product detection"), and then passing the result through a lowpass filter.
These are the demodulated audios, first without lowpass filtering:
And with lowpass filtering:
As said, AM-SC demodulation is almost equal to modulation. Multiplying a signal twice by the same carrier throws the voice back to its original band (0-1000Hz), and creates a new copy around 6000Hz which needs to be supressed by the lowpass filter.
This is the AM-SC demodulator code:
modulated = wave.open("amsc.wav", "r") demod_amsc_ok = wave.open("demod_amsc_ok.wav", "w") demod_amsc_nok = wave.open("demod_amsc_nok.wav", "w") demod_amsc_nok2 = wave.open("demod_amsc_nok2.wav", "w") for f in [demod_amsc_ok, demod_amsc_nok, demod_amsc_nok2]: f.setnchannels(1) f.setsampwidth(2) f.setframerate(44100) for n in range(0, modulated.getnframes()): signal = struct.unpack('h', modulated.readframes(1)) / 32768.0 carrier = math.cos(3000.0 * (n / 44100.0) * math.pi * 2) carrier_phased = math.sin(3000.0 * (n / 44100.0) * math.pi * 2) carrier_freq = math.cos(3100.0 * (n / 44100.0) * math.pi * 2) base = signal * carrier base_nok = signal * carrier_phased base_nok2 = signal * carrier_freq demod_amsc_ok.writeframes(struct.pack('h', base * 32767)) demod_amsc_nok.writeframes(struct.pack('h', base_nok * 32767)) demod_amsc_nok2.writeframes(struct.pack('h', base_nok2 * 32767))
The new copy of the information at 6000Hz, that I call "demodulation noise", is puzzling to some readers. They put the demodulated signal in an Excel graph and don't understand why such a high-frequency component is there.
Here is the spectrum of the demodulated AM-SC audio, before the necessary low-pass filtering. The yellow part is the "good" part:
It is interesting to use a bit of math to clarify this point. This applies for AM and QAM as well. The original audio information f(t) is modulated (multiplied) by a cosine wave, the carrier. So the transmitted signal is f(t).cos(t). Demodulation transforms it into f(t).cos(t).cos(t). If we remember the high-school trigonometric identities, we can convert the last form into f(t).[1+cos(2t)] and to f(t)+[f(t).cos(2t)].
Now it is clear (if you are comfortable with formulas) that the demodulated signal is a sum of two parts: the decoded f(t) audio (that we want) and audio "modulated" at 2t (that is unwanted noise). Since the carrier's frequency is much higher than the audio's bandwidth, the unwanted part falls around twice the carrier's frequency; it is easy to get rid of it using the crudest low-pass filter.
One little big problem of AM-SC demodulation: the carrier wave generated at receiver must be EXACTLY equal to transmitter, both in frequency and phase. Any difference will wreck the demodulation. In the code above, I simulated two demodulation problems: carrier with wrong phase (90 degrees behind), and with wrong frequency (100Hz higher). The results are very bad, as you can hear below (already lowpassed):
It is very difficult to generate a local carrier at receiver with these constraints. That's why broadcast AM radio sends the carrier together with modulated signal, while AM-SC is seldom employed in practice. (*)
Now, let's see the broadcast AM radio demodulator:
modulated = wave.open("am.wav", "r") f = demod_am = wave.open("demod_am.wav", "w") f.setnchannels(1) f.setsampwidth(2) f.setframerate(44100) for n in range(0, modulated.getnframes()): signal = struct.unpack('h', modulated.readframes(1)) / 32768.0 signal = abs(signal) demod_am.writeframes(struct.pack('h', signal * 32767))
Appreciate how simple it is; note the absence of transcendental functions. AM can be demodulated (detected) by a simple diode, which cuts off the negative part of wave. It is called "envelope detector". That's why you can build a galena radio with just a diode and a capacitor; the diode implements abs() and the capacitor is the low-pass filter.
An AM receptor is not forced to use an envelope detector; it can use the product detector, with some advantages: improved noise resistance and compatibility with AM-SC and AM-SSB signals. The carrier phase is easy to find out in AM since it is sent along by the transmitter.
This is the demodulation result:
The remaining noise is likely due to the abs() detector in demodulation. In a real-world AM radio, the noise generated by detector would be at far higher frequency, and the lowpass filter would remove it easily and completely. (Remember that we have modulated to a very low frequency, 3000Hz).
The broadcast AM modulation technique — add carrier to modulated output — is equivalent to add a large DC bias to the original audio. This guarantees that carrier is always multiplied by a positive value, and modulated output is always in phase with the carrier. The AM signal phase does not carry any useful information, and the receptor may disregard it.
In AM-SC, the original content is modulated "as it is". When the content has negative sign, the carrier is multiplied by a negative number and the modulated output has opposite phase of the carrier. So, the phase of the AM-SC signal does carry some information, namely the sign of the original content.
If you try to demodulate AM-SC with a detector (a diode or abs()), the phase and sign information are lost. The result will be very off, even though it might resemble the original audio. If it was human voice, it will sound like Donald Duck, because the wrongful demodulation doubles the frequency and adds harmonics.
AM looks much better than AM-SC at this point, but note that that AM-SC is more efficient — meaning that it can send more content over the same bandwidth — since it uses phase to send useful information while AM does not.
Another problem of AM is handling of content with very low frequency components. The DC bias needs to be removed somehow, and the low frequency content will be discarded altogether. AM-SC can send content down to 0Hz with no problem. This is not an issue for audio but it is for video.
Both AM and AM-SC modulations waste bandwidth, because they generate two "copies" of the same content around carrier. Moreover, AM sends the carrier all along with content. We can say that an AM transmitter spends 75% of power just sending "garbage" which does not increase range. It is there just to nanny AM receivers. A legacy from the time when people actively used galenas, rusty razors as makeshift detectors, etc.
But there are ways to improve AM efficiency. Since AM-SC signal has two copies, we can just filter out one of them, sending only the other. This is called AM-SSB (supressed side band).
The spectrum will be obviously different from AM-SC, the sideband below 3000Hz is almost absent:
This is how AM-SSB sounds like (lower band removed, only upper band is kept):
Not much different from AM-SC; and in fact the AM-SC demodulator can cope with AM-SSB signal without any changes:
By sending only one copy of original content, and no carrier, AM-SSB saves bandwidth and power. It has a second big advantage over AM-SC. See what happens when we demodulate AM-SSB with an out-of-phase carrier:
The voice has been recovered perfectly. So, an AM-SSB receiver does not need to worry with carrier's phase.
Only the demodulator frequency is important, and it must be watched closely; any frequency deviation causes audio distortion pretty much like AM-SC:
AM-SSB is the standard modulation for ham radio, police, aviation, analog TV video etc. And the same technique is employed in FDM multiplexers and countless other devices that need to fit many signals into a single channel.
AM-SSB, like broadcast AM, has a problem with very low frequencies in content, regardless of how it is implemented. Analog TV needs low-freq to be preserved, so it actually employs VSB modulation (Vestigial Sideband). In VSB, enough of the to-be-suppressed sideband is left, so the low-frequency content can be detected as AM-SC, with a smooth transition to AM-SSB for higher frequencies.
Removal of the unneeded sideband in transmission can be done with an analog filter, but such filter is difficult and expensive to build. There are some techniques to generate the AM-SSB signal directly, without filtering the modulator output.
One interesting technique, employed by radios that are capable of DSP, is the Hilbert Transform. This transform changes the audio phase by 90 degrees. It is not simple delay; it is a phase change on every frequency present in the signal.
The Hilbert-transformed audio sounds the same because we can't hear phase differences. But, thanks to trigonometric identities, modulating this transformed signal with a phased carrier and adding it to AM-SC cancels out the lower-band "copy", leaving the upper-band. We get a perfect AM-SSB signal without any filtering.
The Hilbert Transform is most easily described and implemented in frequency domain (Fast Fourier Transform), but it can also be implemented as a FIR filter (convolution in time domain). Such filter will be "noncausal" (that is, depends on "future" samples), in practice this means that the filter yields a time-delayed result. It is easy to compensate for this, but it must not be forgotten.
The following Python script implements an AM-SSB modulator and implements Hilbert as a FIR filter.
#!/usr/bin/env python import wave, struct, math, numpy hlen = 500.0 hilbert_impulse = [ (1.0 / (math.pi * t / hlen)) \ for t in range(int(-hlen), 0) ] hilbert_impulse.extend() hilbert_impulse.extend( [ (1.0 / (math.pi * t / hlen)) \ for t in range(1, int(hlen+1)) ] ) hilbert_impulse = [ x * 1.0 / hlen for x in hilbert_impulse ] baseband = wave.open("amssb_hilbert_orig.wav", "r") amssbh = wave.open("amssb_hilbert.wav", "w") for f in [amssbh]: f.setnchannels(1) f.setsampwidth(2) f.setframerate(44100) base =  base.extend([ struct.unpack('h', baseband.readframes(1)) / 32768.0 \ for n in range(0, baseband.getnframes()) ]) base.extend([ 0 for n in range(0, int(hlen)) ]) base_hilbert = numpy.convolve(base[:], hilbert_impulse) for n in range(0, len(base) - int(hlen)): carrier_cos = math.cos(3000.0 * (n / 44100.0) * math.pi * 2) carrier_sin = -math.sin(3000.0 * (n / 44100.0) * math.pi * 2) s1 = base[n] * carrier_cos # time-shifted signal because Hilbert transform is noncausal s2 = base_hilbert[n + int(hlen)] * carrier_sin signal_amssbh = s1 + s2 amssbh.writeframes(struct.pack('h', signal_amssbh * 32767))
The spectrum analysis shows that the AM-SSB generated by this technique is "better", that is, the lower sideband was more effectively wiped out than it had been by simple filtering:
Producing an AM-SSB signal like this, with two phased carriers, suggests that AM-SSB is actually a special case of analog QAM (Quadrature Amplitude Modulation). QAM allows to transmit two signals at the same time, and we are sending two signals: the original audio and the Hilbert-transformed audio.
Both signals are interdependent and equivalent in information content, so we are, in a sense, sending two copies of the same thing — even though the intention is the removal of a sideband, not redundancy. The QAM phase angle is not fixed, it rotates all the time (and measuring the speed of this rotation would be a way to recover the original information).
Viewing AM-SSB as QAM also explains why the AM-SSB demodulator can always detect the original audio, even if the carrier is out of phase with the transmitter. Since the same audio is being sent twice, over two orthogonal carriers, the demodulator can always find one or another — or, most likely, a mix of both.
(*) A surprising usage of AM-SC is the FM stereo. The second audio channel is encoded within the audio signal, with a 38KHz carrier. A 19KHz pilot tone is transmitted all the time so the receiver can generate a local carrier with perfect frequency and phase. Of couse this implies that FM audio can not be more treble than 19KHz.