{"spec_id":"heatmap-chromagram","library":"makie","language":"julia","code":"# anyplot.ai\n# heatmap-chromagram: Music Chromagram (Pitch Class Distribution over Time)\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 88/100 | Created: 2026-06-24\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint sequential colormap for energy (single-polarity continuous data)\nconst ANYPLOT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\n\n# Data: chromagram of a I-V-vi-IV progression in C major\nconst PITCH_NAMES = [\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\"]\nconst N_PITCH     = 12\nconst N_FRAMES    = 120  # 12 seconds at 10 fps\n\ntime_seconds = collect(range(0.0, 12.0, length=N_FRAMES))\n\n# Chord tones (1-based pitch indices)\n# C major: C(1), E(5), G(8) | G major: D(3), G(8), B(12)\n# A minor: C(1), E(5), A(10) | F major: C(1), F(6), A(10)\nconst CHORD_TONES = [\n    [1, 5, 8],\n    [3, 8, 12],\n    [1, 5, 10],\n    [1, 6, 10],\n]\n\nframes_per_chord = N_FRAMES ÷ 4\n\n# Build chromagram matrix: chroma[frame, pitch_class]\nchroma = zeros(N_FRAMES, N_PITCH)\n\nfor f in 1:N_FRAMES\n    chord_idx = min(4, div(f - 1, frames_per_chord) + 1)\n    tones = CHORD_TONES[chord_idx]\n    for p in 1:N_PITCH\n        if p in tones\n            chroma[f, p] = 0.60 + 0.40 * rand()\n        else\n            circ_dist = minimum(min(abs(p - t), 12 - abs(p - t)) for t in tones)\n            chroma[f, p] = 0.20 * exp(-0.9 * circ_dist) + 0.06 * rand()\n        end\n    end\nend\n\n# Plot\nfig = Figure(\n    size            = (1200, 1200),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"heatmap-chromagram · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Time (seconds)\",\n    ylabel             = \"Pitch Class\",\n    xlabelsize         = 16,\n    ylabelsize         = 16,\n    xticklabelsize     = 14,\n    yticklabelsize     = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridvisible       = false,\n)\n\nhm = heatmap!(ax, time_seconds, collect(1:N_PITCH), chroma; colormap = ANYPLOT_SEQ)\n\nax.yticks = (collect(1:N_PITCH), PITCH_NAMES)\n\n# Chord boundary markers and region labels\nvlines!(ax, [3.0, 6.0, 9.0]; color = INK_SOFT, linewidth = 1.5, linestyle = :dash)\n\nfor (xc, label) in zip([1.5, 4.5, 7.5, 10.5], [\"C\", \"G\", \"Am\", \"F\"])\n    text!(ax, xc, 12.7; text = label, color = INK, fontsize = 14, align = (:center, :bottom))\nend\n\nylims!(ax, 0.35, 13.6)\n\ncb = Colorbar(\n    fig[1, 2], hm;\n    label          = \"Energy\",\n    labelsize      = 16,\n    ticklabelsize  = 13,\n    labelcolor     = INK,\n    ticklabelcolor = INK_SOFT,\n    tickcolor      = INK_SOFT,\n    width          = 25,\n)\n\ncolgap!(fig.layout, 15)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}