Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155#!/usr/bin/env python3"""poster.py — hellsine "track data" PNG.
A single landscape poster of everything the all-sine engine emitted: · header — engine identity + stats · spectrogram — the whole master, section boundaries marked · waveform — same time axis, sections tinted · drum grid — every kick + snare onset from struct.json
Reads the master WAV + the engine's struct.json. All-sine, so thespectrogram is the proof: discrete partials, no broadband sample hash.
Usage: python3 pop/hellsine/bin/poster.py [master.wav] [struct.json] [out.png]"""
import jsonimport sysimport wavefrom pathlib import Path
import numpy as npimport matplotlib
matplotlib.use("Agg")import matplotlib.pyplot as plt
HOME = Path.home()DEFAULT_DIR = HOME / "Documents" / "Shelf" / "hellsine"WAV = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_DIR / "hellsine-MASTER.wav"STRUCT = (Path(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_DIR / ".hellsine-pre.assets" / "struct.json")OUT = Path(sys.argv[3]) if len(sys.argv) > 3 else HOME / "Desktop" / "hellsine-track-data.png"
# ── palette — hellsine: citrus brand + hardcore heat ────────────────────BG = "#0a0a12"PANEL = "#11111c"GRID = "#23232f"INK = "#f3f0d8"DIM = "#7d7d8f"CITRUS = "#c6ff4f"KICKC = "#ff5a3c" # hot — the HOLE kickSNAREC = "#5fe8b8" # mint — the new backbeat snareSECCOL = ["#ffd166", "#ff5a3c", "#5fe8b8", "#8ab4ff", "#ff7ed4", "#c6ff4f"]
# ── load struct ─────────────────────────────────────────────────────────d = json.loads(STRUCT.read_text())bpm = d["bpm"]sections = d["sections"]kicks = [e["t"] for e in d["events"]["kick"]]snares = [e["t"] for e in d["events"]["snare"]]total = d["totalSec"]
# ── load wav → mono float ───────────────────────────────────────────────with wave.open(str(WAV), "rb") as w: sr = w.getframerate() ch = w.getnchannels() frames = w.readframes(w.getnframes())audio = np.frombuffer(frames, dtype=np.int16).astype(np.float32) / 32768.0if ch > 1: audio = audio.reshape(-1, ch).mean(axis=1)dur = len(audio) / sr
# ── figure ──────────────────────────────────────────────────────────────fig = plt.figure(figsize=(16, 11), dpi=150, facecolor=BG)gs = fig.add_gridspec(4, 1, height_ratios=[0.5, 2.3, 1.0, 1.1], hspace=0.32, left=0.06, right=0.97, top=0.95, bottom=0.06)
def style(ax): ax.set_facecolor(PANEL) for s in ax.spines.values(): s.set_color(GRID) ax.tick_params(colors=DIM, labelsize=8)
def section_marks(ax, y_label=None): for i, s in enumerate(sections): c = SECCOL[i % len(SECCOL)] ax.axvline(s["startSec"], color=c, lw=1.0, alpha=0.55) if y_label is not None: ax.text(s["startSec"] + 0.6, y_label, s["name"], color=c, fontsize=8.5, fontfamily="monospace", va="top", alpha=0.95)
# ── header ──────────────────────────────────────────────────────────────ax0 = fig.add_subplot(gs[0])ax0.axis("off")ax0.text(0, 0.78, "hellsine", color=CITRUS, fontsize=40, fontfamily="monospace", fontweight="bold", va="top")ax0.text(0.001, 0.10, "all-sine hardcore engine · D natural minor · " "John Williams leitmotif structure", color=INK, fontsize=12, fontfamily="monospace", va="top")stats = (f"{bpm} BPM {d['totalBars']} bars " f"{int(dur // 60)}:{dur % 60:05.2f} " f"{len(sections)} sections {len(kicks)} kicks " f"{len(snares)} snares {sr // 1000} kHz")ax0.text(1.0, 0.78, stats, color=DIM, fontsize=10.5, fontfamily="monospace", va="top", ha="right")ax0.text(1.0, 0.30, "every voice a sum of sines — kicks, pads, brass, " "steam-noise + snare crack", color=DIM, fontsize=9.5, fontfamily="monospace", va="top", ha="right", style="italic")
# ── spectrogram ─────────────────────────────────────────────────────────ax1 = fig.add_subplot(gs[1])style(ax1)ax1.specgram(audio, NFFT=2048, Fs=sr, noverlap=1024, cmap="magma", vmin=-120, vmax=-20)ax1.set_ylim(0, 12000)ax1.set_ylabel("frequency (Hz)", color=DIM, fontsize=9, fontfamily="monospace")ax1.set_xlim(0, dur)section_marks(ax1, y_label=11600)ax1.set_title("spectrogram — discrete sine partials, no broadband hash", color=INK, fontsize=10, fontfamily="monospace", loc="left", pad=8)
# ── waveform ────────────────────────────────────────────────────────────ax2 = fig.add_subplot(gs[2])style(ax2)step = max(1, len(audio) // 4000)env = np.abs(audio[: len(audio) // step * step].reshape(-1, step)).max(axis=1)t = np.linspace(0, dur, len(env))ax2.fill_between(t, -env, env, color=CITRUS, lw=0, alpha=0.85)ax2.set_ylim(-1.05, 1.05)ax2.set_xlim(0, dur)ax2.set_yticks([])for i, s in enumerate(sections): end = s["endSec"] if "endSec" in s else ( sections[i + 1]["startSec"] if i + 1 < len(sections) else dur) ax2.axvspan(s["startSec"], end, color=SECCOL[i % len(SECCOL)], alpha=0.07)section_marks(ax2)ax2.set_title("waveform envelope", color=INK, fontsize=10, fontfamily="monospace", loc="left", pad=6)
# ── drum onset grid ─────────────────────────────────────────────────────ax3 = fig.add_subplot(gs[3])style(ax3)ax3.set_xlim(0, dur)ax3.set_ylim(-0.6, 1.6)ax3.set_yticks([0, 1])ax3.set_yticklabels(["snare", "kick"], color=INK, fontsize=9, fontfamily="monospace")for tk in kicks: ax3.plot([tk, tk], [0.62, 1.38], color=KICKC, lw=1.4, alpha=0.9)for ts in snares: ax3.plot([ts, ts], [-0.38, 0.38], color=SNAREC, lw=1.2, alpha=0.9)section_marks(ax3, y_label=1.55)ax3.set_xlabel("time (s)", color=DIM, fontsize=9, fontfamily="monospace")ax3.set_title(f"onset grid — {len(kicks)} HOLE kicks (halftime) · " f"{len(snares)} backbeat snares", color=INK, fontsize=10, fontfamily="monospace", loc="left", pad=6)
OUT.parent.mkdir(parents=True, exist_ok=True)fig.savefig(OUT, facecolor=BG)print(f"→ {OUT}")