0% found this document useful (0 votes)
3 views15 pages

Tutorial2 Audio Signal Analysis

This document outlines a tutorial on audio compression and coding, focusing on analyzing audio signals in temporal and frequency domains using Audacity and Python. It includes instructions for setting up the environment, recording audio, analyzing its spectrum, and understanding psychoacoustic masking phenomena. The tutorial is divided into two parts: audio spectrum analysis and experiments demonstrating frequency and temporal masking effects.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views15 pages

Tutorial2 Audio Signal Analysis

This document outlines a tutorial on audio compression and coding, focusing on analyzing audio signals in temporal and frequency domains using Audacity and Python. It includes instructions for setting up the environment, recording audio, analyzing its spectrum, and understanding psychoacoustic masking phenomena. The tutorial is divided into two parts: audio spectrum analysis and experiments demonstrating frequency and temporal masking effects.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

TUTORIAL 2 - AUDIO COMPRESSION AND CODING

Objectives:

• To analyse audio signal in both temporal and frequency domains


• To investigate how compression and coding physically work on audio signals

Preparations:

• Install Audacity to your [virtual] Linux. You may install by compiling the downloaded
source (recommended) or simply set up with a pre-built package
• Make sure that the plotting tool matplotlib is already available

I. AUDIO SPECTRUM ANALYSIS

We are going to construct a program that will:

1. record audio from your laptop microphone (whatever input Audacity is currently
set to),
2. open Audacity’s Plot Spectrum window,
3. export the recording to WAV, and
4. compute + print key spectrum info (peak/RMS, dominant frequencies, spectral
centroid/rolloff, bandwidth) and save plots.

This uses Audacity’s scriptable commands like Record New Track, Stop, Plot Spectrum,
Get Info, Export.

0) One-time setup in Audacity (Ubuntu)


1. Open Audacity
2. Edit → Preferences → Modules → mod-script-pipe → Enabled
3. Restart Audacity
4. In the main window, set Audio Setup Toolbar → Recording Device to your laptop
mic (important: the script uses whatever Audacity is set to).
1) Install Python deps
sudo apt update
sudo apt install -y python3 python3-pip
pip3 install numpy scipy matplotlib

2) Save this script as audacity_record_and_spectrum.py


#!/usr/bin/env python3
import os
import time
import json
import numpy as np
from [Link] import wavfile
import [Link] as plt

# --- Audacity pipe locations on Linux/macOS (from Audacity's pipe_test.py) ---


# /tmp/audacity_script_pipe.to.<uid> and /tmp/audacity_script_pipe.from.<uid>
# :contentReference[oaicite:2]{index=2}
UID = [Link]()
TONAME = f"/tmp/audacity_script_pipe.to.{UID}"
FROMNAME = f"/tmp/audacity_script_pipe.from.{UID}"
EOL = "\n"

RECORD_SECONDS = 6
EXPORT_WAV = [Link]("mic_recording.wav")
EXPORT_REPORT = [Link]("spectrum_report.txt")
EXPORT_SPECTRUM_PNG = [Link]("[Link]")
EXPORT_SPECTROGRAM_PNG = [Link]("[Link]")

def require_pipes():
if not [Link](TONAME) or not [Link](FROMNAME):
raise RuntimeError(
"Audacity pipes not found.\n"
"Make sure:\n"
" 1) Audacity is running\n"
" 2) mod-script-pipe is Enabled (Edit→Preferences→Modules)\n"
f"Expected:\n {TONAME}\n {FROMNAME}\n"
)

def open_pipes():
# NOTE: Audacity must be running first.
tofile = open(TONAME, "w")
fromfile = open(FROMNAME, "r")
return tofile, fromfile

def send_command(tofile, command: str):


[Link](command + EOL)
[Link]()

def get_response(fromfile) -> str:


# Responses end with a blank line
out = ""
while True:
line = [Link]()
if line == "":
# pipe closed
break
out += line
if line == "\n" and len([Link]()) > 0:
break
return out

def do_command(tofile, fromfile, command: str) -> str:


send_command(tofile, command)
return get_response(fromfile)

def parse_json_maybe(s: str):


# Audacity often returns JSON wrapped with extra lines; try to extract.
s = [Link]()
# Find the first '{' or '[' and last matching end.
for start in ["{", "["]:
i = [Link](start)
if i != -1:
candidate = s[i:]
try:
return [Link](candidate)
except Exception:
pass
return None

def dbfs_from_signal(x: [Link]) -> tuple[float, float]:


# x in float [-1,1] ideally; handle int PCM too
x = [Link](np.float64)
peak = [Link]([Link](x)) + 1e-12
rms = [Link]([Link](x * x)) + 1e-12
peak_db = 20 * np.log10(peak)
rms_db = 20 * np.log10(rms)
return peak_db, rms_db

def analyze_wav(path: str):


fs, data = [Link](path)
if [Link] > 1:
data = [Link](axis=1) # mono mixdown for analysis

# normalize to [-1,1] float if int PCM


if [Link]([Link], [Link]):
maxv = [Link]([Link]).max
x = [Link](np.float64) / maxv
else:
x = [Link](np.float64)

duration = len(x) / fs
peak_db, rms_db = dbfs_from_signal(x)

# Windowed FFT
n = int(2 ** [Link](np.log2(min(len(x), fs * 10)))) # up to 10s worth, pow2
n = max(2048, n)
xw = x[:min(len(x), n)]
win = [Link](len(xw))
X = [Link](xw * win)
freqs = [Link](len(xw), 1 / fs)
mag = [Link](X) + 1e-12
mag_db = 20 * np.log10(mag / [Link](mag))

# Dominant frequencies (top 5, ignore DC)


start_bin = 1
top_idx = [Link](mag[start_bin:])[-5:][::-1] + start_bin
top_freqs = freqs[top_idx]
top_mags_db = mag_db[top_idx]

# Spectral features
p = mag ** 2
p_sum = [Link](p) + 1e-24
centroid = float([Link](freqs * p) / p_sum)
bandwidth = float([Link]([Link](((freqs - centroid) ** 2) * p) / p_sum))

cdf = [Link](p) / p_sum


rolloff85 = float(freqs[[Link](cdf, 0.85)])

# Save spectrum plot


[Link]()
[Link](freqs, mag_db)
[Link]("Frequency (Hz)")
[Link]("Magnitude (dB, normalized)")
[Link]("Magnitude Spectrum (FFT)")
[Link](0, min(20000, fs / 2))
[Link](True, which="both", linestyle="--", linewidth=0.5)
plt.tight_layout()
[Link](EXPORT_SPECTRUM_PNG, dpi=160)
[Link]()

# Save spectrogram plot


[Link]()
[Link](x, NFFT=2048, Fs=fs, noverlap=1536)
[Link]("Time (s)")
[Link]("Frequency (Hz)")
[Link]("Spectrogram")
[Link](0, min(20000, fs / 2))
plt.tight_layout()
[Link](EXPORT_SPECTROGRAM_PNG, dpi=160)
[Link]()

report = {
"file": path,
"sample_rate_hz": int(fs),
"duration_s": float(duration),
"peak_dbfs": float(peak_db),
"rms_dbfs": float(rms_db),
"spectral_centroid_hz": float(centroid),
"spectral_bandwidth_hz": float(bandwidth),
"spectral_rolloff_85_hz": float(rolloff85),
"top_frequencies_hz": [float(f) for f in top_freqs],
"top_magnitudes_db_norm": [float(v) for v in top_mags_db],
"plots": {
"spectrum_png": EXPORT_SPECTRUM_PNG,
"spectrogram_png": EXPORT_SPECTROGRAM_PNG,
},
}
return report

def main():
require_pipes()
tofile, fromfile = open_pipes()

# Clean start
do_command(tofile, fromfile, "New:")

# Start recording on a NEW track (uses whatever mic Audacity is set to)
# Transport: Recording → Record New Track :contentReference[oaicite:3]{index=3}
do_command(tofile, fromfile, "Record2ndChoice:")

[Link](RECORD_SECONDS)

# Stop recording (Extra → Stop) :contentReference[oaicite:4]{index=4}


do_command(tofile, fromfile, "Stop:")

# Select all audio


do_command(tofile, fromfile, "SelectAll:")

# Show Plot Spectrum window inside Audacity (Analyze → Plot


Spectrum) :contentReference[oaicite:5]{index=5}
do_command(tofile, fromfile, "PlotSpectrum:")

# Get track info (JSON)


info = do_command(tofile, fromfile, "GetInfo: Type=Tracks Format=JSON")
info_json = parse_json_maybe(info)

# Export selected audio to WAV (Export2


command) :contentReference[oaicite:6]{index=6}
do_command(tofile, fromfile, f'Export2: Filename="{EXPORT_WAV}" NumChannels=1')

# Close pipes
[Link]()
[Link]()

# Analyze exported WAV in Python


report = analyze_wav(EXPORT_WAV)
report["audacity_tracks_info_json"] = info_json

# Save and print report


with open(EXPORT_REPORT, "w", encoding="utf-8") as f:
[Link]([Link](report, indent=2))

print("\n=== DONE ===")


print(f"Exported WAV: {EXPORT_WAV}")
print(f"Report JSON: {EXPORT_REPORT}")
print(f"Spectrum PNG: {EXPORT_SPECTRUM_PNG}")
print(f"Spectrogram: {EXPORT_SPECTROGRAM_PNG}")
print("\nKey info:")
print(f" Sample rate: {report['sample_rate_hz']} Hz")
print(f" Duration: {report['duration_s']:.3f} s")
print(f" Peak: {report['peak_dbfs']:.2f} dBFS")
print(f" RMS: {report['rms_dbfs']:.2f} dBFS")
print(f" Centroid: {report['spectral_centroid_hz']:.1f} Hz")
print(f" Bandwidth: {report['spectral_bandwidth_hz']:.1f} Hz")
print(f" Rolloff 85%: {report['spectral_rolloff_85_hz']:.1f} Hz")
print(" Dominant freqs (Hz):", ", ".join(f"{f:.1f}" for f in report["top_frequencies_hz"]))

if __name__ == "__main__":
main()

3) Run it (with Audacity already open)


python3 audacity_record_and_spectrum.py

You’ll see:

• an Audacity Plot Spectrum window pop up (for the selected audio)


• files created in your current folder:
o mic_recording.wav
o spectrum_report.txt (JSON with all metrics)
o [Link]
o [Link]

Notes (important)

• The recording source is whatever Audacity has selected as the input device.
• If you want “compression learning” next: record once, then in Audacity export
MP3/AAC at different bitrates, re-import them, and run the exact same analysis
script on each version to compare.

II. MASKING EFFECT TONES

This tutorial walks your students through hands-on experiments that make psychoacoustic
masking phenomena visible and audible using Audacity on Ubuntu.
Background: What Are We Demonstrating?
Frequency masking (simultaneous masking) occurs when a loud tone makes nearby
frequencies inaudible at the same time. A strong 1 kHz tone, for example, raises the
hearing threshold for tones at 900 Hz or 1100 Hz — the weaker signal is "masked" and can
be removed without perceptible quality loss. This is the principle exploited by MP3 and
AAC codecs.

Temporal masking occurs across time. A loud sound raises the hearing threshold both
just before it (pre-masking, ~20 ms) and for a significant period after it (post-masking, up
to ~200 ms). A soft click buried in the tail of a loud clap simply disappears perceptually.

Prerequisites

Install Audacity

sudo apt update


sudo apt install audacity

Verify the version:

audacity --version

You'll also want Python 3 with NumPy and SciPy for generating precise test signals before
importing them into Audacity:

sudo apt install python3-pip


pip3 install numpy scipy soundfile matplotlib

Part 1: Frequency Masking

Step 1 — Generate the Test Signals

Create a Python script freq_masking.py that builds three audio files:

• A masker: a loud 1 kHz pure tone


• A probe: a quiet 1.1 kHz pure tone (the signal to be masked)
• A combined track: both tones playing simultaneously
python
import numpy as np
import soundfile as sf
import [Link] as plt

SR = 44100 # sample rate


DURATION = 2.0 # seconds
t = [Link](0, DURATION, int(SR * DURATION), endpoint=False)

# --- Masker: 1 kHz at -10 dBFS (loud) ---


masker_freq = 1000
masker_amp = 0.316 # approx -10 dBFS
masker = masker_amp * [Link](2 * [Link] * masker_freq * t)

# --- Probe: 1.1 kHz at -40 dBFS (quiet) ---


probe_freq = 1100
probe_amp = 0.01 # approx -40 dBFS
probe = probe_amp * [Link](2 * [Link] * probe_freq * t)

# --- Combined ---


combined = masker + probe
combined /= [Link]([Link](combined)) # normalize to prevent clipping

[Link]("[Link]", masker, SR)


[Link]("[Link]", probe, SR)
[Link]("[Link]", combined, SR)

# --- Spectrum plot so students can see both tones ---


N = len(combined)
freqs = [Link](N, 1/SR)
spectrum = [Link]([Link](combined)) / N

[Link](figsize=(10, 4))
[Link](freqs, 20 * np.log10(spectrum + 1e-10))
[Link](500, 2000)
[Link](-80, 0)
[Link]("Frequency (Hz)")
[Link]("Amplitude (dBFS)")
[Link]("Combined Signal Spectrum — Can You Hear Both Tones?")
[Link](masker_freq, color='red', linestyle='--', label='Masker 1 kHz')
[Link](probe_freq, color='blue', linestyle='--', label='Probe 1.1 kHz')
[Link]()
plt.tight_layout()
[Link]("freq_masking_spectrum.png", dpi=150)
[Link]()

print("Files written: [Link], [Link], [Link]")

Run it:
python3 freq_masking.py

Step 2 — Load and Compare in Audacity

1. Open Audacity: audacity &


2. File → Import → Audio — import all three WAV files. Each appears as its own track.
3. Use Solo buttons to listen to each track individually:
a. [Link] alone: you should clearly hear the 1.1 kHz tone.
b. [Link]: the probe becomes inaudible or severely degraded despite
still existing in the data.

Step 3 — Visualize with the Spectrogram

Click the track name dropdown on [Link] → Spectrogram. Then:

• Edit → Preferences → Tracks → Spectrograms


o Set Window size: 4096
o Set Frequency gain: 20 dB
o Set Maximum frequency: 4000 Hz

You will see a bright horizontal band at 1 kHz and a much fainter one at 1.1 kHz, illustrating
how the masker dominates.
Step 4 — Find the Masking Threshold Experimentally

Use Audacity's Generate → Tone to add probe tones at increasing amplitudes until
students can just barely hear the probe over the masker. Record this threshold amplitude
— this is the masked threshold for that frequency separation.

Repeat with probe frequencies at 800 Hz, 900 Hz, 1.05 kHz, 1.2 kHz, and 2 kHz. Plot the
results to reconstruct the masking curve by hand.

Discussion prompt: Why is the masking curve asymmetric — i.e., why does masking
extend further upward in frequency than downward?

Part 2: Temporal Masking

Step 1 — Generate Temporal Masking Signals

Create temporal_masking.py:

python
import numpy as np
import soundfile as sf

SR = 44100
t_full = [Link](0, 3.0, int(SR * 3.0), endpoint=False)
signal = [Link](len(t_full))

# --- Masker burst: 1 kHz tone, 200 ms, starting at t=0.5s ---
t_start_masker = int(0.5 * SR)
t_end_masker = int(0.7 * SR)
masker_amp = 0.8
masker_freq = 1000
for i in range(t_start_masker, t_end_masker):
t_i = i / SR
signal[i] += masker_amp * [Link](2 * [Link] * masker_freq * t_i)

# --- Helper to add a probe tone at a given time offset ---


def add_probe(sig, offset_s, duration_s=0.05, freq=2000, amp=0.02):
start = int(offset_s * SR)
end = start + int(duration_s * SR)
for i in range(start, min(end, len(sig))):
t_i = i / SR
sig[i] += amp * [Link](2 * [Link] * freq * t_i)

# --- Pre-masking probe: 20 ms BEFORE masker onset ---


add_probe(signal, offset_s=0.48, freq=2000, amp=0.015)

# --- Post-masking probes at increasing delays after masker ends ---


add_probe(signal, offset_s=0.71, freq=2000, amp=0.015) # 10 ms after
add_probe(signal, offset_s=0.75, freq=2000, amp=0.015) # 50 ms after
add_probe(signal, offset_s=0.85, freq=2000, amp=0.015) # 150 ms after
add_probe(signal, offset_s=1.0, freq=2000, amp=0.015) # 300 ms after (should be
audible)

# Normalize
signal /= [Link]([Link](signal))
[Link]("temporal_masking.wav", signal, SR)
print("Written: temporal_masking.wav")
bash
python3 temporal_masking.py

Step 2 — Analyze in Audacity

Import temporal_masking.wav into Audacity.

Zoom into the waveform around the masker region (use Ctrl+Scroll or the magnifier tool).
You will physically see the small probe blips before and after the loud masker burst.

Switch the track to Spectrogram view to observe all events across time and frequency
simultaneously.

Step 3 — Listening Test

Have students listen carefully and answer:


Delay from
Probe position Audible?
masker
Pre-masking probe −20 ms Usually not
Post-masking probe
+10 ms Usually not
1
Post-masking probe
+50 ms Borderline
2
Post-masking probe
+150 ms Borderline / yes
3
Post-masking probe
+300 ms Clearly audible
4

Step 4 — Isolate Individual Probes Using Audacity Labels

1. Analyze → Label Sounds (or place labels manually with Ctrl+B).


2. Select only the region containing a probe, then use Effect → Amplify to boost it by
+20 dB to confirm it exists in the signal — but is masked at normal playback level.

Discussion prompt: Pre-masking seems to violate causality. How does the auditory
system produce it, and why is its window so short (~20 ms) compared to post-masking
(~200 ms)?

Part 3: Connecting Masking to Codec Design

Demonstrating MP3 Compression Artifacts

1. Export [Link] as an MP3 at a very low bitrate to exaggerate codec


decisions:
a. File → Export → Export as MP3
b. In the format options, set bitrate to 32 kbps
2. Re-import the MP3 alongside the original WAV.
3. Use Edit → Duplicate on the WAV track, then Effect → Invert on the duplicate.
4. Mix the inverted WAV with the MP3 track (Tracks → Mix and Render). The result is
the error signal — what the codec discarded, which should sound like low-level
noise.
5. Amplify the error signal by +30 dB to make it audible. Notice how the noise
concentrates in frequency bands where masking was assumed to hide it.

Summary: What Students Should Take Away


The experiments above demonstrate the three key facts that perceptual audio codecs
exploit:

1. Simultaneous frequency masking allows a codec to discard or coarsely quantize


signal components near a dominant frequency, because the ear's critical band
filtering makes nearby tones inaudible when a louder tone dominates.
2. Post-masking allows the codec to use coarser quantization immediately after a
loud transient — for up to ~200 ms, quantization noise remains below the elevated
threshold.
3. Pre-masking (the narrower ~20 ms window) is less exploited by codecs since it
requires look-ahead buffering, but it explains why transient coding artifacts tend to
appear before a percussive hit rather than after when codecs make errors.

Audacity's spectrogram, label, and amplify tools give students an empirical, signal-level
view of phenomena that are usually described only mathematically — bridging the gap
between psychoacoustics theory and practical compression engineering.

III. EXERCISE
Write a program to record an audio from PC’s microphone, and then calculate and
visualize the following for the entire signal:
1. Frequency masking level
2. Temporal masking level
3. Combined masking level
4. Processed energy level

You might also like