diff --git a/plots/piano-roll-midi/implementations/python/plotnine.py b/plots/piano-roll-midi/implementations/python/plotnine.py index fd31e00202..6518397f5d 100644 --- a/plots/piano-roll-midi/implementations/python/plotnine.py +++ b/plots/piano-roll-midi/implementations/python/plotnine.py @@ -1,9 +1,11 @@ -""" pyplots.ai +""" anyplot.ai piano-roll-midi: MIDI Piano Roll Visualization -Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 91/100 | Created: 2026-03-07 +Library: plotnine 0.15.5 | Python 3.13.13 +Quality: 91/100 | Updated: 2026-06-03 """ +import os + import numpy as np import pandas as pd from plotnine import ( @@ -20,7 +22,7 @@ ggplot, guide_colorbar, labs, - scale_fill_cmap, + scale_fill_gradient, scale_x_continuous, scale_y_continuous, theme, @@ -28,11 +30,26 @@ ) -# Data - A short chord progression with melody (C major -> F major -> G major -> C major) +# Theme tokens (Imprint palette, theme-adaptive chrome) +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +# Piano key shading (theme-adaptive) — dark values boosted for visible contrast over #1A1A17 +WHITE_KEY_BG = "#EDE9DE" if THEME == "light" else "#272720" +BLACK_KEY_BG = "#D8D4C8" if THEME == "light" else "#0D0D0B" +BEAT_LINE = "#C4C0B4" if THEME == "light" else "#2C2C29" +MEASURE_LINE = "#8A8780" if THEME == "light" else "#4A4A46" +OCTAVE_LINE = "#ABA89C" if THEME == "light" else "#363632" + +# Data — C major → F major → G major → C major chord progression with melody np.random.seed(42) notes = [ - # Measure 1: C major chord + melody (opening — mf) + # Measure 1: C major chord + melody (mf) (0.0, 2.0, 48, 80), # C3 bass (0.0, 2.0, 52, 70), # E3 (0.0, 2.0, 55, 70), # G3 @@ -42,17 +59,17 @@ (2.0, 1.0, 65, 105), # F4 (3.0, 0.5, 64, 85), # E4 (3.5, 0.5, 62, 80), # D4 - # Measure 2: F major chord + melody (building — f to ff) + # Measure 2: F major chord + melody (f to ff) (4.0, 2.0, 53, 75), # F3 bass (4.0, 2.0, 57, 65), # A3 (4.0, 2.0, 60, 65), # C4 (4.0, 1.0, 65, 110), # F4 melody (5.0, 0.5, 67, 95), # G4 (5.5, 0.5, 69, 100), # A4 - (6.0, 1.5, 72, 115), # C5 high point - CLIMAX + (6.0, 1.5, 72, 115), # C5 — CLIMAX (7.5, 0.5, 69, 80), # A4 # Measure 3: G major chord + descending melody (diminuendo) - (8.0, 2.0, 50, 85), # D3 bass (G/D inversion — tighter pitch range) + (8.0, 2.0, 50, 85), # D3 bass (G/D inversion) (8.0, 2.0, 55, 70), # G3 (8.0, 2.0, 59, 70), # B3 (8.0, 1.0, 71, 105), # B4 melody @@ -61,13 +78,13 @@ (10.0, 1.0, 65, 95), # F4 (11.0, 0.5, 64, 80), # E4 (11.5, 0.5, 62, 75), # D4 - # Measure 4: C major resolution (ending — p, fading) + # Measure 4: C major resolution (p, fading) (12.0, 2.0, 48, 90), # C3 bass (12.0, 2.0, 52, 75), # E3 (12.0, 2.0, 55, 75), # G3 - (12.0, 3.0, 60, 110), # C4 melody - long resolution + (12.0, 3.0, 60, 110), # C4 — long resolution (14.0, 1.0, 64, 70), # E4 - (15.0, 1.0, 60, 60), # C4 ending - soft fade + (15.0, 1.0, 60, 60), # C4 — soft fade ] df = pd.DataFrame(notes, columns=["start", "duration", "pitch", "velocity"]) @@ -75,56 +92,48 @@ df["ymin"] = df["pitch"] - 0.4 df["ymax"] = df["pitch"] + 0.4 -# MIDI note name mapping note_names = ["C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"] -pitch_min = df["pitch"].min() - 1 -pitch_max = df["pitch"].max() + 1 +pitch_min = int(df["pitch"].min()) - 1 # 47 +pitch_max = int(df["pitch"].max()) + 1 # 73 -# Background rows for black/white key distinction +# Background rows — theme-adaptive black/white key shading black_key_semitones = {1, 3, 6, 8, 10} -bg_rows = [] -for p in range(pitch_min, pitch_max + 1): - semitone = p % 12 - is_black = semitone in black_key_semitones - bg_rows.append({"ymin": p - 0.5, "ymax": p + 0.5, "fill_color": "#e6e4ef" if is_black else "#f8f7fc"}) - +bg_rows = [ + {"ymin": p - 0.5, "ymax": p + 0.5, "fill_color": BLACK_KEY_BG if p % 12 in black_key_semitones else WHITE_KEY_BG} + for p in range(pitch_min, pitch_max + 1) +] bg_df = pd.DataFrame(bg_rows) -# Y-axis labels: show only white-key pitches and pitches with notes to reduce crowding -used_pitches = set(df["pitch"].unique()) -pitch_labels_map = {p: f"{note_names[p % 12]}{p // 12 - 1}" for p in range(pitch_min, pitch_max + 1)} -# Show white keys that have data, plus octave C notes for orientation -white_key_semitones = {0, 2, 4, 5, 7, 9, 11} -label_pitches = sorted( - {p for p in used_pitches if p % 12 in white_key_semitones} - | {p for p in range(pitch_min, pitch_max + 1) if p % 12 == 0} -) -label_names = [pitch_labels_map[p] for p in label_pitches] +# Y-axis: C (octave markers) and G (dominant) only — avoids adjacent-label crowding +label_pitches = sorted(p for p in range(pitch_min, pitch_max + 1) if p % 12 in {0, 7}) +label_names = [f"{note_names[p % 12]}{p // 12 - 1}" for p in label_pitches] # Measure structure total_beats = 16 measure_lines = [0, 4, 8, 12, 16] beat_lines = [b for b in range(total_beats + 1) if b not in measure_lines] -# Measure labels at top with chord names +# Chord labels at measure tops measure_labels = pd.DataFrame( - {"x": [2, 6, 10, 14], "label": ["I (C)", "IV (F)", "V (G)", "I (C)"], "y": [pitch_max + 0.8] * 4} + {"x": [2, 6, 10, 14], "label": ["I (C)", "IV (F)", "V (G)", "I (C)"], "y": [pitch_max + 1.8] * 4} ) -# Dynamic markings to enhance storytelling -dynamic_labels = pd.DataFrame({"x": [2, 6.5, 10, 14.5], "label": ["mf", "ff", "dim.", "p"], "y": [pitch_min - 0.3] * 4}) +# Dynamic markings below piano roll +dynamic_labels = pd.DataFrame({"x": [2, 6.5, 10, 14.5], "label": ["mf", "ff", "dim.", "p"], "y": [pitch_min - 0.6] * 4}) -# Horizontal separators at octave boundaries (every C note) +# Octave boundary lines at each C note octave_cs = [p for p in range(pitch_min, pitch_max + 1) if p % 12 == 0] octave_lines = pd.DataFrame( {"y": [c - 0.5 for c in octave_cs], "xstart": [-0.3] * len(octave_cs), "xend": [total_beats + 0.3] * len(octave_cs)} ) -# Plot using theme_void as base for maximum control (plotnine-distinctive) +title = "piano-roll-midi · python · plotnine · anyplot.ai" + +# Plot plot = ( ggplot() - # Background rows - alternating shading for black/white keys + # Background rows — black/white key distinction + geom_rect( bg_df, aes(xmin=-0.3, xmax=total_beats + 0.3, ymin="ymin", ymax="ymax"), @@ -132,50 +141,51 @@ color=None, show_legend=False, ) - # Beat grid lines (subtle) - + geom_vline(xintercept=beat_lines, color="#d4d2e0", size=0.25, linetype="dotted") - # Measure grid lines (stronger) - + geom_vline(xintercept=measure_lines, color="#9895b0", size=0.5, linetype="solid") - # Octave boundary lines + # Beat grid (subtle dotted) + + geom_vline(xintercept=beat_lines, color=BEAT_LINE, size=0.25, linetype="dotted") + # Measure boundaries (solid) + + geom_vline(xintercept=measure_lines, color=MEASURE_LINE, size=0.5, linetype="solid") + # Octave boundary lines (dashed) + geom_segment( - octave_lines, aes(x="xstart", xend="xend", y="y", yend="y"), color="#b0adc5", size=0.35, linetype="dashed" + octave_lines, aes(x="xstart", xend="xend", y="y", yend="y"), color=OCTAVE_LINE, size=0.35, linetype="dashed" + ) + # Note rectangles — Imprint sequential colormap (green=soft → blue=loud) + + geom_rect(df, aes(xmin="start", xmax="end", ymin="ymin", ymax="ymax", fill="velocity"), color=INK, size=0.3) + # Climax annotation + + annotate("text", x=7.6, y=72 + 1.0, label="← climax", size=3.0, color="#AE3030", fontstyle="italic", ha="left") + # Chord labels at top of each measure + + geom_text(measure_labels, aes(x="x", y="y", label="label"), size=4.0, color=INK_SOFT, fontstyle="italic") + # Dynamic markings below + + geom_text(dynamic_labels, aes(x="x", y="y", label="label"), size=3.5, color=INK_MUTED, fontstyle="italic") + # Imprint sequential cmap: #009E73 (soft/piano) → #4467A3 (loud/forte) + + scale_fill_gradient( + low="#009E73", high="#4467A3", limits=(55, 120), name="Velocity", guide=guide_colorbar(nbin=200) ) - # Note rectangles with velocity color mapping - + geom_rect(df, aes(xmin="start", xmax="end", ymin="ymin", ymax="ymax", fill="velocity"), color="#2d2a3e", size=0.3) - # Climax annotation using plotnine annotate - + annotate("text", x=7.6, y=72 + 1.0, label="← climax", size=9, color="#e05634", fontstyle="italic", ha="left") - # Measure chord labels at top - + geom_text(measure_labels, aes(x="x", y="y", label="label"), size=11, color="#4a4568", fontstyle="italic") - # Dynamic markings below the piano roll - + geom_text(dynamic_labels, aes(x="x", y="y", label="label"), size=9, color="#7a7590", fontstyle="italic") - # Color scale: viridis for perceptual uniformity and colorblind safety - + scale_fill_cmap(cmap_name="inferno", limits=(55, 120), name="Velocity", guide=guide_colorbar(nbin=200)) + scale_y_continuous(breaks=label_pitches, labels=label_names, expand=(0.02, 0.02)) + scale_x_continuous(breaks=measure_lines, labels=["0", "4", "8", "12", "16"], expand=(0.01, 0.01)) - + coord_cartesian(xlim=(-0.3, total_beats + 0.3), ylim=(pitch_min - 1.2, pitch_max + 1.5)) - + labs(x="Time (beats)", y="Pitch (note)", title="piano-roll-midi · plotnine · pyplots.ai") - # Start from theme_void for full control, then add back what we need + + coord_cartesian(xlim=(-0.3, total_beats + 0.3), ylim=(pitch_min - 1.5, pitch_max + 2.5)) + + labs(x="Time (beats)", y="Pitch", title=title) + theme_void() + theme( - figure_size=(16, 9), - plot_title=element_text(size=24, weight="bold", color="#2d2a3e", margin={"b": 15}), - axis_title_x=element_text(size=20, color="#4a4568", margin={"t": 10}), - axis_title_y=element_text(size=20, color="#4a4568", margin={"r": 10}), - axis_text_x=element_text(size=16, color="#4a4568"), - axis_text_y=element_text(size=16, color="#4a4568"), - axis_ticks_major=element_line(color="#9895b0", size=0.5), - axis_ticks_length=4, + figure_size=(8, 4.5), + plot_title=element_text(size=12, weight="bold", color=INK, margin={"b": 8}), + axis_title_x=element_text(size=10, color=INK, margin={"t": 6}), + axis_title_y=element_text(size=10, color=INK, margin={"r": 6}), + axis_text_x=element_text(size=8, color=INK_SOFT), + axis_text_y=element_text(size=8, color=INK_SOFT), + axis_ticks_major=element_line(color=INK_SOFT, size=0.4), + axis_ticks_length=3, legend_position="right", - legend_title=element_text(size=16, color="#4a4568"), - legend_text=element_text(size=14, color="#4a4568"), - legend_background=element_rect(fill="#f8f7fc", color=None), - legend_key_height=40, - legend_key_width=12, - panel_background=element_rect(fill="#f8f7fc", color=None), - plot_background=element_rect(fill="white", color=None), + legend_title=element_text(size=8, color=INK), + legend_text=element_text(size=8, color=INK_SOFT), + legend_background=element_rect(fill=ELEVATED_BG, color=None), + legend_key_height=30, + legend_key_width=10, + panel_background=element_rect(fill=PAGE_BG, color=None), + plot_background=element_rect(fill=PAGE_BG, color=None), plot_margin=0.02, ) ) # Save -plot.save("plot.png", dpi=300, verbose=False) +plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) diff --git a/plots/piano-roll-midi/metadata/python/plotnine.yaml b/plots/piano-roll-midi/metadata/python/plotnine.yaml index dcfce384ba..07aecf677b 100644 --- a/plots/piano-roll-midi/metadata/python/plotnine.yaml +++ b/plots/piano-roll-midi/metadata/python/plotnine.yaml @@ -1,82 +1,127 @@ library: plotnine +language: python specification_id: piano-roll-midi created: '2026-03-07T19:46:37Z' -updated: '2026-03-07T20:15:24Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 22805912195 +updated: '2026-06-03T03:55:27Z' +generated_by: claude-sonnet +workflow_run: 26861817386 issue: 4565 -python_version: 3.14.3 -library_version: 0.15.3 -preview_url: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/plotnine/plot.png -preview_html: null +language_version: 3.13.13 +library_version: 0.15.5 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/plotnine/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/plotnine/plot-dark.png +preview_html_light: null +preview_html_dark: null quality_score: 91 review: strengths: - - Excellent data storytelling with chord labels, dynamic markings, and climax annotation - creating a musical narrative - - Cohesive purple-themed design with inferno colormap that feels domain-appropriate - - Complete spec compliance with all required features present - - Realistic musical data with proper I-IV-V-I progression showing varied durations - and dynamics - - Clean use of theme_void() as a base for full visual control + - Piano-key alternating backgrounds with correct theme adaptation (WHITE_KEY_BG/BLACK_KEY_BG + tokens) — genuinely distinctive domain-aware design + - Three-tier grid hierarchy (dotted beat lines, solid measure boundaries, dashed + octave lines) creates elegant visual structure + - Chord progression labels (I, IV, V, I) and dynamic markings (mf, ff, dim., p) + provide real musical narrative context + - 'Imprint sequential colormap (#009E73 soft → #4467A3 loud) applied correctly for + velocity encoding — proper imprint_seq use' + - 'Complete spec compliance: note names on Y-axis, velocity colorbar, beat/measure + grid, pitch range auto-fitted, all required features present' + - Flat KISS code structure with all font sizes explicitly set via element_text — + no functions or classes, fully reproducible with hardcoded data + - Climax annotation (#AE3030 italic) correctly highlights the musical peak at C5 + in measure 2 + - Both themes render correctly with no dark-on-dark or light-on-light failures — + all chrome tokens thread through properly weaknesses: - - Y-axis pitch labels slightly crowded where adjacent pitches are both labeled - image_description: The plot displays a MIDI piano roll visualization on a light - purple/white background. Horizontal colored rectangles represent musical notes, - positioned by pitch (y-axis, labeled C3 through C5 with note names) and time (x-axis, - 0-16 beats). The inferno colormap maps velocity from dark purple (soft, ~60) to - yellow (loud, ~120). Alternating row shading distinguishes black keys (slightly - darker lavender) from white keys (lighter). Vertical dotted lines mark beats, - solid gray lines mark measure boundaries at beats 0, 4, 8, 12, 16. Chord labels - (I (C), IV (F), V (G), I (C)) appear at the top, and dynamic markings (mf, ff, - dim., p) at the bottom. A red italic "← climax" annotation marks the C5 note. - The title reads "piano-roll-midi · plotnine · pyplots.ai" in bold dark text. A - velocity colorbar legend is on the right side. + - 'In dark render, black-key rows (#0D0D0B) against the #1A1A17 background provide + very little contrast — the piano key distinction is barely perceptible in dark + mode, making the chromatic structure hard to read. Consider boosting BLACK_KEY_BG + dark value slightly (e.g. #151510) while keeping it below #1A1A17.' + - 'LM-02: The plotnine implementation uses the grammar of graphics well but a piano + roll of this type can be replicated in matplotlib with similar effort — no distinctively + plotnine-only feature (e.g. stat_* transforms, facet_grid, position adjustments) + is leveraged.' + - 'DE-03: The data storytelling through chord labels and dynamic markings is good + but the visual hierarchy between the melody notes (higher pitch, high velocity) + and accompaniment chords (lower pitch, lower velocity) is implicit rather than + visually reinforced — e.g., a subtle size or alpha difference between melody and + bass notes would strengthen the focal point.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct. The piano roll rows alternate between two subtle warm grays: lighter (#EDE9DE) for white keys and slightly darker (#D8D4C8) for black keys. The contrast between these two shading values is clearly visible and creates the piano-keyboard-style visual structure. + Chrome: Bold title "piano-roll-midi · python · plotnine · anyplot.ai" is readable in dark #1A1A17 ink. Y-axis tick labels (C3, G3, C4, G4, C5) and X-axis tick labels (0, 4, 8, 12, 16) are clearly readable in #4A4A44 (INK_SOFT). X-axis label "Time (beats)" and Y-axis label "Pitch" are readable in #1A1A17. Chord labels (I (C), IV (F), V (G), I (C)) and dynamic markings (mf, ff, dim., p) are visible in muted ink tones. Velocity colorbar labeled "Velocity" with 60/80/100/120 ticks is readable. + Data: Note rectangles rendered from #009E73 (green, low velocity ~60) to #4467A3 (blue, high velocity ~115). The C5 "climax" note at beat 6–7.5 is deep blue (velocity 115), clearly the peak. Bass notes (C3, E3, G3) are rendered in mid-green. Melody notes show the crescendo to climax and subsequent diminuendo. "← climax" annotation in #AE3030 italic marks the peak. Measure lines (solid), beat lines (dotted), octave lines (dashed) provide clear structural grid. + Legibility verdict: PASS — all text readable, correct theme colors throughout. + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct. Row alternation is present: white-key rows (#272720) are subtly lighter than the background, black-key rows (#0D0D0B) are darker — nearly invisible at #0D0D0B. The piano key distinction is much more subtle in dark mode than light mode. + Chrome: Title in light #F0EFE8 ink — clearly readable. Y-axis and X-axis tick labels in #B8B7B0 — readable. Axis labels "Pitch" and "Time (beats)" in light ink — readable. Chord labels in #B8B7B0 — readable. Dynamic markings in #A8A79F — small but legible. Velocity colorbar readable. No dark-on-dark failures detected. + Data: Note rectangle colors are identical to light render (#009E73 to #4467A3 gradient) — Imprint data colors are theme-independent, confirmed. The "← climax" annotation in #AE3030 is visible against the dark background. Measure and beat grid lines are visible as slightly lighter/darker lines. + Legibility verdict: PASS — all text readable with appropriate light-on-dark chrome. Minor note: black-key row distinction (#0D0D0B vs #1A1A17 background) is barely perceptible in dark mode. criteria_checklist: visual_quality: - score: 28 + score: 29 max: 30 items: - id: VQ-01 name: Text Legibility - score: 8 + score: 7 max: 8 passed: true - comment: 'All font sizes explicitly set: title=24, axis titles=20, ticks=16, - legend=16/14' + comment: 'All font sizes explicitly set (title 12pt, axis labels 10pt, tick + labels 8pt, geom_text annotations 3.5-4mm). All text readable in both themes. + Minor: dynamic markings (INK_MUTED at size=3.5mm) are at the minimum viable + size but still legible.' - id: VQ-02 name: No Overlap - score: 5 + score: 6 max: 6 passed: true - comment: Slight crowding on y-axis where adjacent pitches have labels close - together + comment: No overlapping text or data elements. Chord labels well-separated + at measure midpoints. Dynamic markings well-placed below piano roll. Climax + annotation positioned above C5 note with adequate clearance. - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Note rectangles clearly visible with good height and contrast + comment: Note rectangles clearly visible in both themes with dark border outline. + Varying widths (duration) and colors (velocity) clearly distinguishable. + 35 notes at appropriate scale. - id: VQ-04 name: Color Accessibility - score: 4 - max: 4 + score: 2 + max: 2 passed: true - comment: Inferno colormap is perceptually uniform and colorblind-safe + comment: Velocity colormap green-to-blue (imprint_seq) is CVD-safe (green + and blue distinguish well under deuteranopia/protanopia). No red-green sole + signal. Dark note outlines provide redundant structural encoding. - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: Good 16:9 proportions, minor extra whitespace above chord labels + comment: 3200x1800 landscape. Piano roll fills the canvas well with appropriate + margins. Colorbar legend positioned right. coord_cartesian ensures note + rectangles don't overflow. No clipping detected at any edge. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Time (beats) and Pitch (note) are descriptive with units + comment: Title 'piano-roll-midi · python · plotnine · anyplot.ai' correct + format. X-axis 'Time (beats)' — descriptive with units. Y-axis 'Pitch' with + note names on ticks (C3, G3, C4, G4, C5). All informative. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Continuous velocity data uses imprint_seq (low=#009E73, high=#4467A3) + — correct. Background #FAF8F1 light / #1A1A17 dark — correct. Data colors + identical across both renders. All chrome tokens theme-adaptive. Piano key + shading colors are structural (not data colors) — acceptable.' design_excellence: - score: 16 + score: 14 max: 20 items: - id: DE-01 @@ -84,22 +129,28 @@ review: score: 6 max: 8 passed: true - comment: Cohesive purple color scheme with inferno colormap, chord numerals - and dynamic markings + comment: 'Strong design clearly above defaults: piano key alternating backgrounds + with theme adaptation, chord progression labels, dynamic markings, climax + annotation in semantic red, three-tier grid hierarchy. Shows genuine domain + expertise and intentional visual design.' - id: DE-02 name: Visual Refinement - score: 5 + score: 4 max: 6 passed: true - comment: theme_void base with selective restoration, subtle grid hierarchy, - polished legend + comment: theme_void() as base removes all default chrome. Three-tier grid + hierarchy (dotted beat lines 0.25, solid measure lines 0.5, dashed octave + lines 0.35) is well-executed visual refinement. Clean colorbar, proper whitespace + (plot_margin=0.02). Above minimal-refinement default. - id: DE-03 name: Data Storytelling - score: 5 + score: 4 max: 6 passed: true - comment: Musical narrative with climax annotation, dynamic markings, chord - progression labels + comment: 'Visual hierarchy: I-IV-V-I chord labels create harmonic narrative. + mf→ff→dim→p dynamic markings tell the intensity arc. Climax annotation with + red color draws the eye to the musical peak. Good storytelling but melody/bass + distinction not visually reinforced (no size/alpha differentiation).' spec_compliance: score: 15 max: 15 @@ -109,26 +160,34 @@ review: score: 5 max: 5 passed: true - comment: Correct piano roll with horizontal rectangles by pitch and time + comment: 'Correct piano roll visualization: horizontal rectangles for notes, + Y=pitch, X=time, width=duration, color=velocity. Piano keyboard row structure + in background.' - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All spec features: key shading, note names, grid lines, velocity - coloring, auto-fit range' + comment: 'All required features: note names on Y-axis (C3/G3/C4/G4/C5), alternating + black/white key backgrounds, beat grid (dotted) and measure grid (solid), + velocity colormap (sequential green-to-blue), pitch range auto-fitted with + margin.' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=time (beats), Y=pitch (note names), all data visible + comment: X=time in beats, Y=pitch (MIDI numbers 48-72 with note names), color=velocity, + width=duration. All 4 measures (16 beats) visible. Chord progression and + melody fully shown. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Correct title format, velocity colorbar legend present + comment: Title 'piano-roll-midi · python · plotnine · anyplot.ai' matches + required format exactly. Velocity colorbar labeled correctly. Y-axis displays + note names (spec requirement for human-readable pitch labels). data_quality: score: 15 max: 15 @@ -138,20 +197,27 @@ review: score: 6 max: 6 passed: true - comment: Chord progressions, varying durations, varying velocities, bass+melody - separation + comment: 'Shows all piano roll features: chord voicings (multiple simultaneous + notes), melodic runs (eighth/quarter notes), varying note durations, full + velocity range (60-115), bass + melody register coverage, 4-measure phrase + structure with chord progression.' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real I-IV-V-I chord progression in C major with melodic contour + comment: I-IV-V-I chord progression (C→F→G→C) is a canonical Western music + structure. Velocity values (60-115) are realistic for a mf to ff musical + phrase. Note durations (0.5-3.0 beats) are musically plausible. The climax + at measure 2 (ff, C5) is musically authentic. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: MIDI pitches 48-72, velocities 60-115, durations 0.5-3.0 beats + comment: MIDI values 48-72 (C3-C5) appropriate range for a 2-octave piano + arrangement. Velocity 60-115 within MIDI range (0-127). 16 beats = 4 measures + in 4/4 time. All values factually correct for MIDI piano roll context. code_quality: score: 10 max: 10 @@ -161,60 +227,77 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports, data, dataframes, plot, save' + comment: 'Flat script: tokens → data → derived data → plot layers → save. + No functions or classes. Clear organization with comments marking sections.' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: np.random.seed(42) set, data is deterministic + comment: np.random.seed(42) set. Data is fully hardcoded tuples — deterministic + regardless of seed. Fully reproducible. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports are used + comment: 'All imports from plotnine are used: ggplot, aes, geom_rect, geom_segment, + geom_text, geom_vline, annotate, scale_fill_gradient, scale_x_continuous, + scale_y_continuous, coord_cartesian, labs, guide_colorbar, theme, theme_void, + element_text, element_line, element_rect. numpy, pandas, os all used.' - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean, well-organized, appropriate complexity + comment: Pythonic, appropriate complexity for the visualization. No fake interactivity. + List comprehensions for bg_rows and label generation. Multiple DataFrames + for different geom layers is the correct plotnine pattern. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with dpi=300, current API + comment: Saves as plot-{THEME}.png using plot.save() with correct dpi=400, + width=8, height=4.5, units='in'. Current plotnine 0.15.5 API. library_mastery: - score: 7 + score: 8 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 passed: true - comment: Proper grammar of graphics with theme_void base for full control + comment: 'Expertly uses plotnine''s grammar of graphics: multiple geom layers + on different data frames (bg_df for backgrounds, df for notes, measure_labels, + dynamic_labels, octave_lines), scale functions for both axes and fill, coord_cartesian + for clipping, guide_colorbar for legend, fine-grained theme() customization. + Idiomatic plotnine.' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: theme_void, scale_fill_cmap with guide_colorbar, annotate, per-layer - data + comment: 'Uses some plotnine-specific features: multiple data frame layers + in a single ggplot() chain, guide_colorbar() for colorbar customization, + annotate() for text overlays, element_text/element_rect/element_line theme + components. The piano roll structure itself could be replicated in matplotlib + — no uniquely-plotnine statistical transformation (stat_*, position_*, facet_*) + is leveraged.' verdict: APPROVED impl_tags: dependencies: [] techniques: + - colorbar - annotations - layer-composition - - colorbar + - manual-ticks patterns: - data-generation + - iteration-over-groups dataprep: [] styling: - minimal-chrome - custom-colormap - - grid-styling - edge-highlighting