Project 38: Audio Beat Stamps 🥁¶

Difficulty: 🔴 Advanced

Run cells top to bottom. Each step builds on the previous one and saves real files under outputs/audio_beat_stamps/.

← Back to index

Description¶

Stamp a procedural drum pattern onto a WAV file and visualize the beat grid.

Libraries & Modules¶

  • numpy
  • scipy.io.wavfile or wave
  • matplotlib

Python Concepts You'll Practice¶

Signal synthesis, timing grids, audio file I/O

🎛️ Parameter Variations¶

  • BPM, BARS
  • Pattern strings like x-x-
In [1]:
# Install dependencies for this project (safe to re-run)
import sys
!{sys.executable} -m pip install pillow numpy matplotlib wordcloud qrcode[pil] python-barcode fpdf2 jinja2 folium plotly pandas scipy ipywidgets -q
Skipping pip install (already installed)
In [2]:
# Interactive Jupyter setup — run this cell first
%matplotlib inline

from pathlib import Path
from IPython.display import display, Image as IPImage, HTML, Markdown, Audio, IFrame

OUTPUT_DIR = Path("outputs") / "audio_beat_stamps"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
print(f"✅ Outputs folder: {OUTPUT_DIR.resolve()}")
✅ Outputs folder: C:\Users\Hansel Yan\Projects\CodeItAll\outputs\audio_beat_stamps

Step 1 — Synthesize kick + hihat hits¶

In [3]:
import numpy as np

# 🎛️ TWEAK THESE
BPM = 96
BARS = 2
SR = 22050
KICK_PAT = "x---x---x---x---"
HAT_PAT = "x-x-x-x-x-x-x-x-"

def env_noise(n, decay=0.001):
    t = np.arange(n) / SR
    return (np.random.randn(n) * np.exp(-t / decay)).astype(np.float64)

def kick(n):
    t = np.arange(n) / SR
    freq = np.linspace(140, 40, n)
    return 0.9 * np.sin(2 * np.pi * freq * t) * np.exp(-t * 8)

step = int(SR * 60 / BPM / 4)
total = step * 16 * BARS
mix = np.zeros(total)
rng = np.random.default_rng(0)

def stamp(pattern, sound_fn, gain=1.0):
    for bar in range(BARS):
        for i, ch in enumerate(pattern):
            if ch.lower() == "x":
                pos = (bar * 16 + i) * step
                burst = sound_fn(step)
                end = min(pos + len(burst), total)
                mix[pos:end] += gain * burst[: end - pos]

stamp(KICK_PAT, kick, 0.9)
stamp(HAT_PAT, lambda n: env_noise(n, 0.004), 0.25)
mix = mix / max(1e-9, np.max(np.abs(mix)))
print("Audio samples:", len(mix), "duration sec:", len(mix) / SR)
Audio samples: 110240 duration sec: 4.999546485260771

Step 2 — Write WAV + beat plot¶

In [4]:
import matplotlib.pyplot as plt
from scipy.io import wavfile

audio = (mix * 32767).astype(np.int16)
wav_path = OUTPUT_DIR / "beat_stamps.wav"
wavfile.write(str(wav_path), SR, audio)

fig, ax = plt.subplots(figsize=(10, 3))
ax.plot(mix[: SR], color="#111111", linewidth=0.6)
ax.set_title("Audio Beat Stamps — first second")
ax.set_yticks([])
fig.tight_layout()
png = OUTPUT_DIR / "beat_waveform.png"
fig.savefig(png, dpi=140)
plt.show()
print(f"Saved {wav_path.name} and {png.name}")
No description has been provided for this image
Saved beat_stamps.wav and beat_waveform.png

✅ Project complete¶

Check your output folder: outputs/audio_beat_stamps/

Next: Project 39: Choropleth Lite · Index