diff --git a/plots/piano-roll-midi/implementations/r/ggplot2.R b/plots/piano-roll-midi/implementations/r/ggplot2.R new file mode 100644 index 0000000000..8ce58d6f3a --- /dev/null +++ b/plots/piano-roll-midi/implementations/r/ggplot2.R @@ -0,0 +1,195 @@ +#' anyplot.ai +#' piano-roll-midi: MIDI Piano Roll Visualization +#' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 89/100 | Created: 2026-06-03 + +library(ggplot2) +library(ragg) + +set.seed(42) + +# Theme tokens (Imprint palette) +THEME <- Sys.getenv("ANYPLOT_THEME", "light") +PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" +ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" +INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" +INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" + +# Imprint sequential colormap for velocity: green (quiet) -> blue (loud) +CMAP_LOW <- "#009E73" +CMAP_HIGH <- "#4467A3" + +# Piano keyboard row background colors +KEY_WHITE <- if (THEME == "light") "#EBE7DC" else "#222220" +KEY_BLACK <- if (THEME == "light") "#D3CDB8" else "#1D1D1B" + +NOTE_NAMES <- c("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") +BLACK_KEYS <- c(1L, 3L, 6L, 8L, 10L) + +# --- Data: 8-measure C-major piano piece (32 beats, 4/4 time) --------------- + +# Right hand: eighth notes (0.5 beat each), melodic lines building to climax +rh_pitches <- c( + 60, 62, 64, 67, 69, 67, 64, 62, # m1: gentle ascent/descent + 60, 64, 67, 72, 71, 69, 67, 65, # m2: arpeggiated C chord + 64, 67, 69, 72, 74, 72, 69, 67, # m3: E-based run + 65, 67, 69, 72, 71, 72, 69, 67, # m4: F-based figure + 64, 67, 71, 72, 74, 76, 74, 72, # m5: climbing higher + 71, 72, 74, 76, 77, 79, 77, 76, # m6: climax in upper register + 74, 72, 71, 69, 67, 65, 64, 62, # m7: long descent + 60, 62, 64, 65, 64, 62, 60, 60 # m8: quiet resolution to C4 +) +rh_starts <- seq(0, 31.5, by = 0.5) +rh_durations <- rep(0.45, 64L) + +# Velocity arc: mf start -> ff climax (m6) -> p resolution +vel_raw <- c( + seq(55, 70, length.out = 8L), + seq(68, 82, length.out = 8L), + seq(80, 90, length.out = 8L), + seq(88, 95, length.out = 8L), + seq(93, 103, length.out = 8L), + seq(103, 118, length.out = 8L), + seq(112, 85, length.out = 8L), + seq(80, 48, length.out = 8L) +) +rh_vel <- pmin(pmax(round(vel_raw + rnorm(64L, 0, 4)), 30L), 127L) + +# Left hand: bass root on every quarter beat (C-F-Am-G progression, 2 measures each) +bass_pitches <- rep(c(48L, 53L, 57L, 55L), each = 8L) +bass_starts <- 0:31 +bass_dur <- rep(0.85, 32L) +bass_vel <- pmin(pmax(round(seq(62, 72, length.out = 32L) + rnorm(32L, 0, 3)), 30L), 100L) + +notes_df <- data.frame( + start = c(rh_starts, as.numeric(bass_starts)), + end = c(rh_starts + rh_durations, bass_starts + bass_dur), + pitch = c(rh_pitches, bass_pitches), + velocity = c(rh_vel, bass_vel) +) + +# Pitch range with 1-semitone margin +pitch_min <- min(notes_df$pitch) - 1L +pitch_max <- max(notes_df$pitch) + 1L +all_pitches <- seq(pitch_min, pitch_max) + +# Separate background row data by key type +white_rows <- all_pitches[!(all_pitches %% 12 %in% BLACK_KEYS)] +black_rows <- all_pitches[ all_pitches %% 12 %in% BLACK_KEYS] + +bg_white <- data.frame(xmin = 0, xmax = 32, ymin = white_rows - 0.5, ymax = white_rows + 0.5) +bg_black <- data.frame(xmin = 0, xmax = 32, ymin = black_rows - 0.5, ymax = black_rows + 0.5) + +# Y-axis: label only white keys +y_labels <- paste0(NOTE_NAMES[(white_rows %% 12) + 1], floor(white_rows / 12) - 1) + +# Beat / measure grid positions (beat lines exclude measure boundaries) +beat_xs <- (1:31)[!(1:31 %% 4 == 0)] +measure_xs <- seq(0L, 32L, by = 4L) + +# Climax highlight: measure 6 (beats 20-24) is the dynamic peak (ff) +climax_df <- data.frame(xmin = 20, xmax = 24, ymin = pitch_min - 0.5, ymax = pitch_max + 0.5) + +plot_title <- "piano-roll-midi · r · ggplot2 · anyplot.ai" + +# --- Plot ------------------------------------------------------------------- +p <- ggplot() + + # Piano keyboard background: white keys + geom_rect( + data = bg_white, + aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), + fill = KEY_WHITE, color = NA + ) + + # Piano keyboard background: black keys + geom_rect( + data = bg_black, + aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), + fill = KEY_BLACK, color = NA + ) + + # Subtle translucent highlight at dynamic climax (m6, beats 20-24) + geom_rect( + data = climax_df, + aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), + fill = CMAP_HIGH, color = NA, alpha = 0.10 + ) + + # Beat subdivision lines (very faint) + geom_vline( + xintercept = beat_xs, + color = INK_SOFT, linewidth = 0.12, alpha = 0.35 + ) + + # Measure boundary lines (stronger) + geom_vline( + xintercept = measure_xs, + color = INK, linewidth = 0.4, alpha = 0.5 + ) + + # MIDI notes colored by velocity + geom_rect( + data = notes_df, + aes( + xmin = start, xmax = end, + ymin = pitch - 0.42, ymax = pitch + 0.42, + fill = velocity + ), + color = PAGE_BG, linewidth = 0.15 + ) + + # "ff" annotation marking the dynamic peak + annotate( + "text", x = 22, y = pitch_max - 0.3, + label = "ff", color = INK, size = 2.5, fontface = "bold.italic" + ) + + # Imprint sequential colormap for velocity (green=quiet, blue=loud) + scale_fill_gradient( + low = CMAP_LOW, + high = CMAP_HIGH, + limits = c(0, 127), + name = "Velocity", + breaks = c(0, 32, 64, 96, 127), + labels = c("pp", "p", "mf", "f", "ff") + ) + + scale_x_continuous( + name = "Measure", + breaks = seq(0, 32, by = 4), + labels = paste0("m", 1:9), + expand = expansion(add = c(0, 0)) + ) + + scale_y_continuous( + name = "Pitch", + breaks = white_rows, + labels = y_labels, + expand = expansion(add = c(0.5, 0.5)) + ) + + labs(title = plot_title) + + theme_minimal(base_size = 8) + + theme( + plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), + panel.background = element_rect(fill = PAGE_BG, color = NA), + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + panel.border = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.35), + axis.title = element_text(color = INK, size = 10), + axis.text = element_text(color = INK_SOFT, size = 8), + axis.text.y = element_text(color = INK_SOFT, size = 8), + plot.title = element_text(color = INK, size = 12, face = "bold"), + legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3), + legend.text = element_text(color = INK_SOFT, size = 8), + legend.title = element_text(color = INK, size = 9), + legend.position = "right", + plot.margin = margin(12, 12, 10, 10) + ) + + guides( + fill = guide_colorbar( + barheight = 12, + barwidth = 0.8 + ) + ) + +# --- Save ------------------------------------------------------------------- +ggsave( + filename = sprintf("plot-%s.png", THEME), + plot = p, + device = ragg::agg_png, + width = 8, + height = 4.5, + units = "in", + dpi = 400 +) diff --git a/plots/piano-roll-midi/metadata/r/ggplot2.yaml b/plots/piano-roll-midi/metadata/r/ggplot2.yaml new file mode 100644 index 0000000000..66fbbee30e --- /dev/null +++ b/plots/piano-roll-midi/metadata/r/ggplot2.yaml @@ -0,0 +1,240 @@ +library: ggplot2 +language: r +specification_id: piano-roll-midi +created: '2026-06-03T03:44:41Z' +updated: '2026-06-03T04:01:51Z' +generated_by: claude-sonnet +workflow_run: 26862063235 +issue: 4565 +language_version: 4.4.1 +library_version: 3.5.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/r/ggplot2/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/r/ggplot2/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 89 +review: + strengths: + - 'Perfect spec compliance: all four required features (note names, black/white + key rows, beat+measure grid differentiation, velocity colormap) correctly implemented' + - Musically authentic data with realistic C-major progression, proper MIDI note + numbers, and convincing dynamic arc (mf→ff climax→p resolution) + - 'Effective storytelling: climax highlight overlay + ff annotation create a clear + narrative focal point' + - Correct Imprint sequential palette (green→blue) for continuous velocity data; + full theme-adaptive chrome with no dark-on-dark or light-on-light failures + weaknesses: + - 'Piano keyboard row contrast is low in both themes (light: #EBE7DC vs #D3CDB8; + dark: #222220 vs #1D1D1B) — spec asks for white and dark alternation but rows + look nearly identical; increase contrast (e.g., light theme KEY_BLACK → #C8C2AE) + for better piano keyboard readability' + - ff annotation at size=2.5 (~7pt on 3200×1800 canvas) is small — bump to size=3.5–4 + for better catalog-scale visibility + - Beat subdivision lines (alpha=0.35, linewidth=0.12) may be imperceptible on some + displays — raise alpha to 0.5 or linewidth to 0.18 + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct light surface. + Chrome: Title "piano-roll-midi · r · ggplot2 · anyplot.ai" in bold dark ink, clearly readable. Y-axis label "Pitch" and X-axis label "Measure" in dark ink at 10pt. Tick labels (note names C3–G5 on Y; m1–m9 on X) in INK_SOFT at 8pt — all readable. Legend "Velocity" title and pp/p/mf/f/ff labels visible. + Data: MIDI note rectangles colored on Imprint sequential (green #009E73 for low velocity → blue #4467A3 for high velocity). Alternating beige row shading (white keys #EBE7DC, black keys #D3CDB8 — subtle contrast). Beat lines faint (alpha=0.35), measure boundaries darker. Climax overlay (m6) in translucent blue. "ff" annotation at peak. + Legibility verdict: PASS — all text readable, no light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct dark surface. + Chrome: Title and axis labels render in light ink (#F0EFE8), tick labels in #B8B7B0 — fully readable against dark background. Legend text readable. No dark-on-dark failures detected. + Data: MIDI note colors IDENTICAL to light render — Imprint sequential green→blue unchanged. Row alternation visible but very subtle in dark theme (#222220 vs #1D1D1B). "ff" annotation legible in dark context. + Legibility verdict: PASS — all text readable in dark theme, no dark-on-dark issues. + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All text at correct style-guide sizes; ff annotation at size=2.5 + (~7pt) is slightly small for canvas + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text or element collisions + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: Note rectangles clearly visible; beat lines intentionally faint (alpha=0.35) + may be imperceptible + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Imprint sequential (green→blue) is CVD-safe + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: 3200x1800 canvas confirmed, generous margins, legend well-placed + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Pitch with note names, Measure with m1-m9, correct title format + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: imprint_seq (green→blue) for velocity; correct backgrounds; identical + data colors across themes + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Domain-specific piano key row shading, dynamic labels on colorbar, + climax highlight overlay show clear design intent + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Custom beat/measure lines replace default grid; elevated legend; + piano row alternation — but row contrast is subtle + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Velocity arc narrated by color; climax highlight and ff annotation + create clear focal point + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct piano roll: horizontal rectangles at pitch x time' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Note names on Y-axis, black/white key rows, beat+measure grid differentiation, + sequential velocity colormap + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=time, Y=pitch, fill=velocity — all correct + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Correct title format; velocity colorbar with pp/p/mf/f/ff labels + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Onset, duration, pitch, velocity; right-hand melody + left-hand bass; + full dynamic arc + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Authentic C-major piano piece; C-F-Am-G progression; realistic MIDI + velocity progression + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 96 notes in 20-200 range; pitch C3-G5 appropriate; velocity 30-127 + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Flat script, no functions or classes + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: set.seed(42) present + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only ggplot2 and ragg imported, both used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean layered grammar, no fake interactivity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot-{THEME}.png via ragg::agg_png at 3200x1800 + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Multiple geom layers, scale_fill_gradient, guide_colorbar, expansion() + — idiomatic ggplot2 grammar + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Good layered composition but no especially distinctive ggplot2 features + beyond standard geoms + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - colorbar + - annotations + - layer-composition + patterns: + - data-generation + dataprep: [] + styling: + - custom-colormap + - alpha-blending + - edge-highlighting