{"spec_id":"piano-roll-midi","library":"makie","language":"julia","code":"# anyplot.ai\n# piano-roll-midi: MIDI Piano Roll Visualization\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-06-03\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: green (#009E73) → blue (#4467A3), lookup inverted so blue=soft\nconst VEL_CMAP = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\nconst NOTE_NAMES = [\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\"]\n\n# Piano keyboard row backgrounds\nconst WHITE_KEY_BG = THEME == \"light\" ? RGBAf(0.980, 0.973, 0.945, 1.0) : RGBAf(0.130, 0.130, 0.112, 1.0)\nconst BLACK_KEY_BG = THEME == \"light\" ? RGBAf(0.900, 0.891, 0.858, 1.0) : RGBAf(0.082, 0.082, 0.070, 1.0)\n\n# Data: C major generative phrase over 4 measures (4/4, 16 beats total)\n# Each tuple: (pitch, start, duration, velocity)\nnotes_raw = [\n    # Measure 1 — ascending melody\n    (60, 0.0,  0.5,  68),  # C4\n    (62, 0.5,  0.5,  74),  # D4\n    (64, 1.0,  0.5,  80),  # E4\n    (67, 1.5,  0.5,  86),  # G4\n    (69, 2.0,  1.0,  92),  # A4\n    (67, 3.0,  0.5,  80),  # G4\n    (65, 3.5,  0.5,  74),  # F4\n    # Measure 1 — chord accompaniment\n    (48, 0.0,  2.0,  52),  # C3\n    (52, 0.0,  2.0,  48),  # E3\n    (55, 0.0,  2.0,  48),  # G3\n    (53, 2.0,  2.0,  52),  # F3\n    (57, 2.0,  2.0,  48),  # A3\n    (60, 2.0,  2.0,  48),  # C4 chord\n    # Measure 2 — rising to climax\n    (64, 4.0,  0.5,  84),  # E4\n    (65, 4.5,  0.5,  80),  # F4\n    (67, 5.0,  1.0,  96),  # G4\n    (69, 6.0,  0.5,  88),  # A4\n    (71, 6.5,  0.5,  92),  # B4\n    (72, 7.0,  1.0, 102),  # C5 peak\n    # Measure 2 — chord accompaniment\n    (55, 4.0,  2.0,  54),  # G3\n    (59, 4.0,  2.0,  48),  # B3\n    (62, 4.0,  2.0,  48),  # D4\n    (50, 6.0,  2.0,  54),  # D3\n    (53, 6.0,  2.0,  48),  # F3\n    (57, 6.0,  2.0,  48),  # A3\n    # Measure 3 — descending return\n    (71, 8.0,  0.5,  90),  # B4\n    (69, 8.5,  0.5,  84),  # A4\n    (67, 9.0,  0.5,  78),  # G4\n    (65, 9.5,  0.5,  74),  # F4\n    (64, 10.0, 1.0,  82),  # E4\n    (62, 11.0, 0.5,  72),  # D4\n    (60, 11.5, 0.5,  68),  # C4\n    # Measure 3 — chord accompaniment\n    (48, 8.0,  2.0,  52),  # C3\n    (52, 8.0,  2.0,  48),  # E3\n    (55, 8.0,  2.0,  48),  # G3\n    (50, 10.0, 2.0,  52),  # D3\n    (53, 10.0, 2.0,  48),  # F3\n    (57, 10.0, 2.0,  48),  # A3\n    # Measure 4 — resolution\n    (67, 12.0, 0.5,  78),  # G4\n    (65, 12.5, 0.5,  74),  # F4\n    (64, 13.0, 1.0,  84),  # E4\n    (62, 14.0, 0.5,  70),  # D4\n    (60, 14.5, 1.5,  92),  # C4 final\n    # Measure 4 — final tonic chord\n    (48, 12.0, 4.0,  58),  # C3\n    (52, 12.0, 4.0,  54),  # E3\n    (55, 12.0, 4.0,  54),  # G3\n    (60, 12.0, 4.0,  50),  # C4\n]\n\npitches    = [n[1] for n in notes_raw]\nstarts     = Float32[n[2] for n in notes_raw]\ndurations  = Float32[n[3] for n in notes_raw]\nvelocities = [n[4] for n in notes_raw]\n\npitch_min    = minimum(pitches) - 1   # 47\npitch_max    = maximum(pitches) + 1   # 73\ntotal_beats  = 16.0f0\n\n# Plot\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 13,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"piano-roll-midi · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Time (beats)\",\n    ylabel            = \"Pitch\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 12,\n    yticklabelsize    = 11,\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\n# Background rows: alternate white/black key shading\nfor pitch in pitch_min:pitch_max\n    bg = mod(pitch, 12) in (1, 3, 6, 8, 10) ? BLACK_KEY_BG : WHITE_KEY_BG\n    poly!(ax, Rect2f(0.0f0, pitch - 0.5f0, total_beats, 1.0f0); color = bg)\nend\n\n# Vertical beat grid lines\nfor beat in 0:Int(total_beats)\n    is_measure = mod(beat, 4) == 0\n    lw    = is_measure ? 1.8f0 : 0.7f0\n    alpha = is_measure ? 0.40f0 : 0.18f0\n    vlines!(ax, Float32(beat);\n        color     = RGBAf(INK.r, INK.g, INK.b, alpha),\n        linewidth = lw,\n    )\nend\n\n# Note rectangles, colored by velocity\nnote_rects  = [Rect2f(starts[i], pitches[i] - 0.42f0, durations[i] - 0.04f0, 0.84f0)\n               for i in eachindex(notes_raw)]\nnote_colors = [get(VEL_CMAP, 1.0 - velocities[i] / 127.0) for i in eachindex(notes_raw)]\n\npoly!(ax, note_rects;\n    color       = note_colors,\n    strokecolor = RGBAf(INK.r, INK.g, INK.b, 0.55f0),\n    strokewidth = 0.7f0,\n)\n\n# Y-axis ticks: show names for white-key pitches only (avoid crowding)\nwhite_pitches = [p for p in pitch_min:pitch_max if !(mod(p, 12) in (1, 3, 6, 8, 10))]\nax.yticks = (white_pitches, [string(NOTE_NAMES[mod(p, 12) + 1], div(p, 12) - 1) for p in white_pitches])\n\n# X-axis ticks: beats 0, 4, 8, 12, 16\nax.xticks = (0:4:16, string.(0:4:16))\n\n# Measure number labels at the top of each measure\nfor m in 1:4\n    x_pos = (m - 1) * 4.0f0 + 0.12f0\n    text!(ax, x_pos, Float32(pitch_max) + 0.3f0;\n        text     = \"Meas. $m\",\n        fontsize = 11,\n        color    = INK_SOFT,\n        align    = (:left, :center),\n    )\nend\n\nxlims!(ax, -0.3f0, total_beats + 0.3f0)\nylims!(ax, pitch_min - 0.5f0, pitch_max + 0.8f0)\n\n# Velocity colorbar\nColorbar(fig[1, 2];\n    colormap        = reverse(VEL_CMAP),\n    limits          = (0.0, 127.0),\n    label           = \"Velocity\",\n    labelsize       = 13,\n    labelcolor      = INK,\n    ticksize        = 5,\n    tickcolor       = INK_SOFT,\n    ticklabelsize   = 11,\n    ticklabelcolor  = INK_SOFT,\n    ticks           = [0, 32, 64, 96, 127],\n    width           = 18,\n    topspinecolor   = INK_SOFT,\n    bottomspinecolor = INK_SOFT,\n    leftspinecolor  = INK_SOFT,\n    rightspinecolor = INK_SOFT,\n)\n\ncolsize!(fig.layout, 1, Relative(0.93))\n\nsave(joinpath(@__DIR__, \"plot-$(THEME).png\"), fig; px_per_unit = 2)\n"}