{"spec_id":"piano-roll-midi","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' piano-roll-midi: MIDI Piano Roll Visualization\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-06-03\n\nlibrary(ggplot2)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens (Imprint palette)\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\n\n# Imprint sequential colormap for velocity: green (quiet) -> blue (loud)\nCMAP_LOW  <- \"#009E73\"\nCMAP_HIGH <- \"#4467A3\"\n\n# Piano keyboard row background colors\nKEY_WHITE <- if (THEME == \"light\") \"#EBE7DC\" else \"#222220\"\nKEY_BLACK <- if (THEME == \"light\") \"#D3CDB8\" else \"#1D1D1B\"\n\nNOTE_NAMES <- c(\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\")\nBLACK_KEYS <- c(1L, 3L, 6L, 8L, 10L)\n\n# --- Data: 8-measure C-major piano piece (32 beats, 4/4 time) ---------------\n\n# Right hand: eighth notes (0.5 beat each), melodic lines building to climax\nrh_pitches <- c(\n  60, 62, 64, 67, 69, 67, 64, 62,   # m1: gentle ascent/descent\n  60, 64, 67, 72, 71, 69, 67, 65,   # m2: arpeggiated C chord\n  64, 67, 69, 72, 74, 72, 69, 67,   # m3: E-based run\n  65, 67, 69, 72, 71, 72, 69, 67,   # m4: F-based figure\n  64, 67, 71, 72, 74, 76, 74, 72,   # m5: climbing higher\n  71, 72, 74, 76, 77, 79, 77, 76,   # m6: climax in upper register\n  74, 72, 71, 69, 67, 65, 64, 62,   # m7: long descent\n  60, 62, 64, 65, 64, 62, 60, 60    # m8: quiet resolution to C4\n)\nrh_starts    <- seq(0, 31.5, by = 0.5)\nrh_durations <- rep(0.45, 64L)\n\n# Velocity arc: mf start -> ff climax (m6) -> p resolution\nvel_raw <- c(\n  seq(55,  70, length.out = 8L),\n  seq(68,  82, length.out = 8L),\n  seq(80,  90, length.out = 8L),\n  seq(88,  95, length.out = 8L),\n  seq(93, 103, length.out = 8L),\n  seq(103, 118, length.out = 8L),\n  seq(112,  85, length.out = 8L),\n  seq(80,   48, length.out = 8L)\n)\nrh_vel <- pmin(pmax(round(vel_raw + rnorm(64L, 0, 4)), 30L), 127L)\n\n# Left hand: bass root on every quarter beat (C-F-Am-G progression, 2 measures each)\nbass_pitches <- rep(c(48L, 53L, 57L, 55L), each = 8L)\nbass_starts  <- 0:31\nbass_dur     <- rep(0.85, 32L)\nbass_vel     <- pmin(pmax(round(seq(62, 72, length.out = 32L) + rnorm(32L, 0, 3)), 30L), 100L)\n\nnotes_df <- data.frame(\n  start    = c(rh_starts, as.numeric(bass_starts)),\n  end      = c(rh_starts + rh_durations, bass_starts + bass_dur),\n  pitch    = c(rh_pitches, bass_pitches),\n  velocity = c(rh_vel, bass_vel)\n)\n\n# Pitch range with 1-semitone margin\npitch_min  <- min(notes_df$pitch) - 1L\npitch_max  <- max(notes_df$pitch) + 1L\nall_pitches <- seq(pitch_min, pitch_max)\n\n# Separate background row data by key type\nwhite_rows <- all_pitches[!(all_pitches %% 12 %in% BLACK_KEYS)]\nblack_rows <- all_pitches[  all_pitches %% 12 %in% BLACK_KEYS]\n\nbg_white <- data.frame(xmin = 0, xmax = 32, ymin = white_rows - 0.5, ymax = white_rows + 0.5)\nbg_black <- data.frame(xmin = 0, xmax = 32, ymin = black_rows - 0.5, ymax = black_rows + 0.5)\n\n# Y-axis: label only white keys\ny_labels <- paste0(NOTE_NAMES[(white_rows %% 12) + 1], floor(white_rows / 12) - 1)\n\n# Beat / measure grid positions (beat lines exclude measure boundaries)\nbeat_xs    <- (1:31)[!(1:31 %% 4 == 0)]\nmeasure_xs <- seq(0L, 32L, by = 4L)\n\n# Climax highlight: measure 6 (beats 20-24) is the dynamic peak (ff)\nclimax_df <- data.frame(xmin = 20, xmax = 24, ymin = pitch_min - 0.5, ymax = pitch_max + 0.5)\n\nplot_title <- \"piano-roll-midi · r · ggplot2 · anyplot.ai\"\n\n# --- Plot -------------------------------------------------------------------\np <- ggplot() +\n  # Piano keyboard background: white keys\n  geom_rect(\n    data = bg_white,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    fill = KEY_WHITE, color = NA\n  ) +\n  # Piano keyboard background: black keys\n  geom_rect(\n    data = bg_black,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    fill = KEY_BLACK, color = NA\n  ) +\n  # Subtle translucent highlight at dynamic climax (m6, beats 20-24)\n  geom_rect(\n    data = climax_df,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    fill = CMAP_HIGH, color = NA, alpha = 0.10\n  ) +\n  # Beat subdivision lines (very faint)\n  geom_vline(\n    xintercept = beat_xs,\n    color = INK_SOFT, linewidth = 0.12, alpha = 0.35\n  ) +\n  # Measure boundary lines (stronger)\n  geom_vline(\n    xintercept = measure_xs,\n    color = INK, linewidth = 0.4, alpha = 0.5\n  ) +\n  # MIDI notes colored by velocity\n  geom_rect(\n    data = notes_df,\n    aes(\n      xmin = start, xmax = end,\n      ymin = pitch - 0.42, ymax = pitch + 0.42,\n      fill = velocity\n    ),\n    color = PAGE_BG, linewidth = 0.15\n  ) +\n  # \"ff\" annotation marking the dynamic peak\n  annotate(\n    \"text\", x = 22, y = pitch_max - 0.3,\n    label = \"ff\", color = INK, size = 2.5, fontface = \"bold.italic\"\n  ) +\n  # Imprint sequential colormap for velocity (green=quiet, blue=loud)\n  scale_fill_gradient(\n    low    = CMAP_LOW,\n    high   = CMAP_HIGH,\n    limits = c(0, 127),\n    name   = \"Velocity\",\n    breaks = c(0, 32, 64, 96, 127),\n    labels = c(\"pp\", \"p\", \"mf\", \"f\", \"ff\")\n  ) +\n  scale_x_continuous(\n    name   = \"Measure\",\n    breaks = seq(0, 32, by = 4),\n    labels = paste0(\"m\", 1:9),\n    expand = expansion(add = c(0, 0))\n  ) +\n  scale_y_continuous(\n    name   = \"Pitch\",\n    breaks = white_rows,\n    labels = y_labels,\n    expand = expansion(add = c(0.5, 0.5))\n  ) +\n  labs(title = plot_title) +\n  theme_minimal(base_size = 8) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major  = element_blank(),\n    panel.grid.minor  = element_blank(),\n    panel.border      = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.35),\n    axis.title        = element_text(color = INK,      size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.text.y       = element_text(color = INK_SOFT, size = 8),\n    plot.title        = element_text(color = INK,      size = 12, face = \"bold\"),\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK,      size = 9),\n    legend.position   = \"right\",\n    plot.margin       = margin(12, 12, 10, 10)\n  ) +\n  guides(\n    fill = guide_colorbar(\n      barheight = 12,\n      barwidth  = 0.8\n    )\n  )\n\n# --- Save -------------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}