diff --git a/plots/piano-roll-midi/implementations/python/letsplot.py b/plots/piano-roll-midi/implementations/python/letsplot.py index 8dc3547974..678c4d809e 100644 --- a/plots/piano-roll-midi/implementations/python/letsplot.py +++ b/plots/piano-roll-midi/implementations/python/letsplot.py @@ -1,53 +1,63 @@ -""" pyplots.ai +""" anyplot.ai piano-roll-midi: MIDI Piano Roll Visualization -Library: letsplot 4.8.2 | Python 3.14.3 -Quality: 90/100 | Created: 2026-03-07 +Library: letsplot 4.10.1 | Python 3.13.13 +Quality: 90/100 | Updated: 2026-06-03 """ import os +import shutil import numpy as np import pandas as pd from lets_plot import * +from lets_plot.export import ggsave as export_ggsave LetsPlot.setup_html() -# Data - A chord progression with melody (Cmaj - Am - F - G pattern, 8 measures) +# 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" + +# Black key row background — slightly offset from PAGE_BG for subtle contrast +BLACK_KEY_BG = "#E4E3DC" if THEME == "light" else "#252522" +BEAT_LINE = "rgba(26,26,23,0.18)" if THEME == "light" else "rgba(240,239,232,0.18)" +MEASURE_LINE = "rgba(26,26,23,0.50)" if THEME == "light" else "rgba(240,239,232,0.50)" + +# Data — Cmaj–Am–F–G chord progression with melody, 8 measures np.random.seed(42) -# Black key semitone positions within an octave (1=C#, 3=D#, 6=F#, 8=G#, 10=A#) black_semitones = {1, 3, 6, 8, 10} black_key_pitches = {p for p in range(0, 128) if (p % 12) in black_semitones} -# Chord progression: C major - A minor - F major - G major (repeated twice) -# Mix of whole-note and half-note accompaniment for rhythmic variety chords = [ - # Measure 1: C major - building (half notes for movement) + # Measure 1: C major — building (half notes) (0, 2, [48, 52, 55], [55, 50, 45]), (2, 2, [48, 52, 55], [60, 55, 50]), - # Measure 2: A minor - softer + # Measure 2: A minor — softer (4, 4, [45, 52, 57, 60], [42, 40, 38, 48]), - # Measure 3: F major - growing (half notes) + # Measure 3: F major — growing (half notes) (8, 2, [53, 57, 60], [65, 60, 55]), (10, 2, [53, 57, 60], [72, 68, 62]), - # Measure 4: G major - strong + # Measure 4: G major — strong (12, 4, [47, 50, 55, 59], [82, 78, 72, 88]), - # Measure 5: C major - restart softer (half notes) + # Measure 5: C major — restart softer (half notes) (16, 2, [48, 52, 55], [48, 42, 38]), (18, 2, [48, 52, 55], [52, 48, 42]), - # Measure 6: A minor - quiet + # Measure 6: A minor — quiet (20, 4, [45, 52, 57, 60], [40, 38, 35, 45]), - # Measure 7: F major - building to climax (half notes) + # Measure 7: F major — building to climax (half notes) (24, 2, [53, 57, 60], [75, 70, 65]), (26, 2, [53, 57, 60], [85, 80, 75]), - # Measure 8: G major -> resolve fortissimo + # Measure 8: G major — fortissimo resolve (28, 4, [47, 50, 55, 59], [98, 92, 88, 105]), ] -# Melody with passing tones through D4-F4 range to fill the gap melody_notes = [ - # Measure 1-2: ascending phrase through mid-range (mf) (0, 1, 72, 85), (1, 0.5, 74, 75), (1.5, 0.5, 76, 70), @@ -56,35 +66,32 @@ (3.5, 0.5, 72, 70), (4, 1, 69, 90), (5, 0.5, 67, 70), - (5.5, 0.5, 65, 65), # F4 - fills gap - (6, 1, 64, 75), # E4 - fills gap - (7, 0.5, 62, 55), # D4 - fills gap - (7.5, 0.5, 64, 60), # E4 - fills gap - # Measure 3-4: responding phrase, ascending through gap (f) - (8, 0.5, 62, 70), # D4 - (8.5, 0.5, 64, 75), # E4 - (9, 0.5, 65, 80), # F4 - (9.5, 0.5, 67, 85), # G4 + (5.5, 0.5, 65, 65), + (6, 1, 64, 75), + (7, 0.5, 62, 55), + (7.5, 0.5, 64, 60), + (8, 0.5, 62, 70), + (8.5, 0.5, 64, 75), + (9, 0.5, 65, 80), + (9.5, 0.5, 67, 85), (10, 1, 72, 95), (11, 1, 77, 105), (12, 1, 76, 90), (13, 0.5, 74, 75), (13.5, 0.5, 72, 68), (14, 2, 76, 100), - # Measure 5-6: lyrical descent through mid-range (pp -> mp) (16, 0.5, 79, 95), (16.5, 0.5, 76, 70), (17, 1, 72, 80), (18, 0.5, 69, 60), (18.5, 0.5, 67, 55), - (19, 0.5, 65, 50), # F4 - (19.5, 0.5, 64, 48), # E4 - (20, 1, 62, 55), # D4 - (21, 0.5, 64, 50), # E4 - (21.5, 0.5, 65, 55), # F4 + (19, 0.5, 65, 50), + (19.5, 0.5, 64, 48), + (20, 1, 62, 55), + (21, 0.5, 64, 50), + (21.5, 0.5, 65, 55), (22, 1, 67, 60), (23, 1, 69, 58), - # Measure 7-8: climax and resolution (ff -> fff) (24, 0.5, 72, 100), (24.5, 0.5, 74, 95), (25, 0.5, 76, 110), @@ -95,7 +102,6 @@ (30, 2, 72, 110), ] -# Build note list with role labels for visual hierarchy starts, durations, pitches, velocities, roles = [], [], [], [], [] for beat, dur, chord_pitches, chord_vels in chords: @@ -116,19 +122,17 @@ df = pd.DataFrame({"start": starts, "duration": durations, "pitch": pitches, "velocity": velocities, "role": roles}) df["end"] = df["start"] + df["duration"] -# Melody notes are taller to stand out visually +# Melody notes slightly taller for visual hierarchy df["pitch_top"] = np.where(df["role"] == "Melody", df["pitch"] + 0.45, df["pitch"] + 0.35) df["pitch_bottom"] = np.where(df["role"] == "Melody", df["pitch"] - 0.45, df["pitch"] - 0.35) -# Note labels for tooltips note_names_all = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] df["note_name"] = [f"{note_names_all[p % 12]}{p // 12 - 1}" for p in df["pitch"]] -# Determine pitch range pitch_min = df["pitch"].min() - 1 pitch_max = df["pitch"].max() + 1 -# Background rows for black keys - much more visible contrast +# Black key row shading across full width all_pitches = list(range(pitch_min, pitch_max + 1)) black_pitches_in_range = [p for p in all_pitches if p in black_key_pitches] bg_rows = pd.DataFrame( @@ -140,20 +144,17 @@ } ) -# Y-axis labels: only show note names for white keys +# Y-axis labels — white keys only y_breaks = [p for p in all_pitches if p not in black_key_pitches] white_note_letters = {0: "C", 2: "D", 4: "E", 5: "F", 7: "G", 9: "A", 11: "B"} y_labels = [f"{white_note_letters[p % 12]}{p // 12 - 1}" for p in y_breaks] -# Beat and measure grid lines beat_lines = pd.DataFrame({"x": [float(b) for b in range(0, 33)]}) measure_lines = pd.DataFrame({"x": [float(m) for m in range(0, 33, 4)]}) -# Separate melody and accompaniment for layered rendering df_accomp = df[df["role"] == "Accompaniment"].copy() df_melody = df[df["role"] == "Melody"].copy() -# Section labels for dynamic arc storytelling sections = pd.DataFrame( { "x": [2.0, 10.0, 18.0, 26.0], @@ -162,83 +163,83 @@ } ) -# Role annotations role_labels = pd.DataFrame({"x": [31.5, 31.5], "y": [76.0, 52.0], "label": ["Melody", "Accomp."]}) +title = "piano-roll-midi · python · letsplot · anyplot.ai" + # Plot plot = ( ggplot() - # Black key background shading - subtle distinction + # Black key row shading — stronger contrast than previous + geom_rect( data=bg_rows, mapping=aes(xmin="xmin", xmax="xmax", ymin="pitch_bottom", ymax="pitch_top"), - fill="#E8E8EC", + fill=BLACK_KEY_BG, color="rgba(0,0,0,0)", - alpha=0.6, + alpha=0.8, ) - # Beat grid lines (light) - + geom_vline(data=beat_lines, mapping=aes(xintercept="x"), color="#E0E0E0", size=0.3) - # Measure grid lines (stronger) - + geom_vline(data=measure_lines, mapping=aes(xintercept="x"), color="#9E9E9E", size=1.0) - # Accompaniment notes (more transparent, thinner) + # Beat grid lines (subtle) + + geom_vline(data=beat_lines, mapping=aes(xintercept="x"), color=BEAT_LINE, size=0.3) + # Measure grid lines (stronger — mark bars) + + geom_vline(data=measure_lines, mapping=aes(xintercept="x"), color=MEASURE_LINE, size=0.8) + # Accompaniment notes — semi-transparent + geom_rect( data=df_accomp, mapping=aes(xmin="start", xmax="end", ymin="pitch_bottom", ymax="pitch_top", fill="velocity"), - color="#FFFFFF", + color=PAGE_BG, size=0.3, - alpha=0.75, + alpha=0.80, tooltips=layer_tooltips().line("@note_name").line("vel: @velocity").line("beat: @start — @end"), ) - # Melody notes (opaque, taller, with border) + # Melody notes — fully opaque, taller, dark border for hierarchy + geom_rect( data=df_melody, mapping=aes(xmin="start", xmax="end", ymin="pitch_bottom", ymax="pitch_top", fill="velocity"), - color="#1A1A2E", - size=0.6, + color=INK, + size=0.5, alpha=1.0, tooltips=layer_tooltips().line("@note_name").line("vel: @velocity").line("beat: @start — @end"), ) - # Section labels showing dynamic arc - + geom_text(data=sections, mapping=aes(x="x", y="y", label="label"), size=11, color="#666666", fontface="italic") - # Role annotations on right side - + geom_text(data=role_labels, mapping=aes(x="x", y="y", label="label"), size=10, color="#444444", fontface="bold") - # Perceptually uniform color scale for velocity with refined legend - + scale_fill_viridis( + # Section labels showing dynamic arc (pp → fff) + + geom_text(data=sections, mapping=aes(x="x", y="y", label="label"), size=4, color=INK_MUTED, fontface="italic") + # Role labels on right edge + + geom_text(data=role_labels, mapping=aes(x="x", y="y", label="label"), size=3.5, color=INK_SOFT, fontface="bold") + # Imprint sequential colormap — quiet (green) → loud (blue), single-polarity + + scale_fill_gradient( + low="#009E73", + high="#4467A3", name="Velocity", limits=[30, 127], - option="plasma", - direction=1, - guide=guide_colorbar(barwidth=12, barheight=280), + guide=guide_colorbar(barwidth=8, barheight=140), ) + scale_x_continuous(name="Time (beats)", breaks=[0, 4, 8, 12, 16, 20, 24, 28, 32]) + scale_y_continuous(name="Pitch", breaks=y_breaks, labels=y_labels) + coord_cartesian(xlim=[-0.5, 33], ylim=[pitch_min - 0.5, pitch_max + 2.5]) - + labs(title="piano-roll-midi · letsplot · pyplots.ai") + + labs(title=title) + theme_minimal() + theme( - plot_title=element_text(size=26, face="bold", color="#1A1A2E"), - axis_title_x=element_text(size=20, color="#333333"), - axis_title_y=element_text(size=20, color="#333333"), - axis_text_x=element_text(size=16, color="#555555"), - axis_text_y=element_text(size=14, color="#555555"), - axis_line=element_line(color="#CCCCCC", size=0.5), - axis_ticks=element_line(color="#CCCCCC", size=0.3), - legend_title=element_text(size=18), - legend_text=element_text(size=14), + plot_title=element_text(size=16, face="bold", color=INK), + axis_title_x=element_text(size=12, color=INK), + axis_title_y=element_text(size=12, color=INK), + axis_text_x=element_text(size=10, color=INK_SOFT), + axis_text_y=element_text(size=11, color=INK_SOFT), + axis_line=element_line(color=INK_SOFT, size=0.5), + axis_ticks=element_line(color=INK_SOFT, size=0.3), + legend_title=element_text(size=10, color=INK), + legend_text=element_text(size=10, color=INK_SOFT), + legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), legend_position="right", panel_grid_major=element_blank(), panel_grid_minor=element_blank(), - panel_background=element_rect(fill="#FAFAFA", color="#BBBBBB", size=0.3), - plot_background=element_rect(fill="#FFFFFF"), + panel_background=element_rect(fill=PAGE_BG, color=INK_SOFT, size=0.3), + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), ) - + ggsize(1600, 900) + + ggsize(800, 450) ) # Save -ggsave(plot, "plot.png", scale=3, path=".") -ggsave(plot, "plot.html", path=".") +export_ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=4) +export_ggsave(plot, filename=f"plot-{THEME}.html", path=".") if os.path.exists("lets-plot-images"): - import shutil - shutil.rmtree("lets-plot-images") diff --git a/plots/piano-roll-midi/metadata/python/letsplot.yaml b/plots/piano-roll-midi/metadata/python/letsplot.yaml index db3fad3fb6..2c2fada8ac 100644 --- a/plots/piano-roll-midi/metadata/python/letsplot.yaml +++ b/plots/piano-roll-midi/metadata/python/letsplot.yaml @@ -1,44 +1,59 @@ library: letsplot +language: python specification_id: piano-roll-midi created: '2026-03-07T19:47:43Z' -updated: '2026-03-07T20:16:30Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 22805912229 +updated: '2026-06-03T03:48:42Z' +generated_by: claude-sonnet +workflow_run: 26862001395 issue: 4565 -python_version: 3.14.3 -library_version: 4.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/letsplot/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/letsplot/plot.html +language_version: 3.13.13 +library_version: 4.10.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/letsplot/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/letsplot/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/letsplot/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/letsplot/plot-dark.html quality_score: 90 review: strengths: - - Excellent data storytelling with dynamic arc labels (pp to fff) and melody/accompaniment - visual hierarchy - - 'Comprehensive spec compliance: all required features including black key shading, - beat/measure grids, note names, velocity color mapping' - - Musically realistic data with a proper I-vi-IV-V chord progression and meaningful - dynamic contour - - Good use of lets-plot tooltips for interactive HTML version - - Clean, well-organized code with deterministic data + - Excellent piano-roll-midi implementation with authentic DAW-style alternating + black/white key row shading + - 'Imprint sequential colormap (#009E73 → #4467A3) correctly used for velocity (single-polarity + continuous data)' + - Full theme-adaptive chrome in both renders — all text readable in light and dark + themes + - Musical storytelling through section labels (pp—Building → fff—Climax) and velocity + gradient communicates the dynamic arc + - 'Correct visual hierarchy: melody notes have darker border and taller height vs. + accompaniment; custom beat/measure grid lines with distinct weights' + - 'All spec requirements met: note names on Y-axis (white keys only), alternating + row shading, beat/measure grid, auto-fit pitch range' + - Interactive HTML tooltips via layer_tooltips() leverage letsplot's distinctive + interactive capability + - Perfect code structure (KISS) with seed set, clean imports, correct canvas size + (ggsize(800,450) scale=4 → 3200×1800) weaknesses: - - Y-axis tick text slightly small at 14pt - - Some eighth-note melody rectangles are quite small at higher pitches - - Black key row shading contrast could be slightly stronger - image_description: The plot displays a MIDI piano roll with horizontal rectangles - on a pitch-by-time grid. The x-axis shows "Time (beats)" from 0 to 32, and the - y-axis shows "Pitch" with white-key note names from A2 to G5. Notes are colored - using a plasma colormap — dark purple/blue for low velocity (soft) through pink/orange - to bright yellow for high velocity (loud). A velocity colorbar legend appears - on the right side ranging from approximately 40 to 120+. Black key rows are indicated - by subtle light gray horizontal bands across the full width. Thin light-gray vertical - lines mark individual beats, with thicker gray lines at measure boundaries (every - 4 beats). Melody notes (upper register, C5-G5 range) are taller with dark borders - and full opacity, while accompaniment notes (lower register, A2-C4) are slightly - thinner and more transparent. Section labels in italic at the top read "pp — Building", - "f — Response", "pp — Restart", and "fff — Climax". Bold role labels "Melody" - and "Accomp." appear on the right side. The title reads "piano-roll-midi · letsplot - · pyplots.ai" in bold at the top. The overall palette is clean and modern with - a white background and light gray panel. + - 'DE-02: Spines are all kept (panel border via panel_background element_rect with + color=INK_SOFT) — removing or softening the top and right spines would add further + visual refinement; for a piano-roll this is a minor issue since the frame helps + orient the pitch rows' + - 'LM-02: Interactive HTML tooltips are used but the layer_tooltips pattern is relatively + straightforward — a more distinctive letsplot feature (e.g., geom_livemap or animated + transitions) would score higher' + - 'VQ-01: Section labels at the top (geom_text size=4) and role labels (size=3.5) + are on the small side relative to the main axis text; readable but could be bumped + to size=5 for better legibility at mobile scales' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct light surface. Alternating lighter (white keys, #FAF8F1) and slightly darker gray (#E4E3DC) horizontal rows correctly mirror a piano keyboard layout. + Chrome: Title "piano-roll-midi · python · letsplot · anyplot.ai" in bold dark text (~80% width — expected for the mandatory long title format). X-axis label "Time (beats)" and Y-axis label "Pitch" in dark ink, axis tick labels in INK_SOFT gray. Section labels (pp—Building, f—Response, pp—Restart, fff—Climax) in italic muted gray at top of plot. Role labels (Melody, Accomp.) in bold INK_SOFT on right margin. All text clearly readable against the light background. + Data: Note rectangles colored with imprint_seq gradient (green #009E73 for low velocity ~30-48, transitioning to blue #4467A3 for high velocity ~105-127). Accompaniment notes appear as semi-transparent long bars in lower register; melody notes are taller, fully opaque with dark INK border in upper register. Beat lines (subtle) and measure lines (stronger) provide DAW-style temporal grid. Velocity colorbar on right with ELEVATED_BG legend frame. + Legibility verdict: PASS — all text elements readable against warm off-white background. No light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark surface. Alternating near-black (white keys, #1A1A17) and slightly less-dark (#252522) rows correctly rendering the keyboard pattern. + Chrome: Title in light F0EFE8 text — clearly readable. Axis labels and tick labels in B8B7B0 light gray — readable. Section labels in A8A79F muted light gray — readable. Role labels "Melody" and "Accomp." in B8B7B0 — readable. Legend box fill is #242420 (elevated dark bg) with light text. + Data: Velocity gradient colors are identical to light render — #009E73 green for soft notes, #4467A3 blue for loud notes (climax at measures 7-8 clearly visible in deep blue). Data colors are theme-invariant as required. + Legibility verdict: PASS — no dark-on-dark failures. All title, axis labels, tick labels, and annotation text are light-colored and readable against the warm near-black background. criteria_checklist: visual_quality: score: 28 @@ -49,42 +64,56 @@ review: score: 7 max: 8 passed: true - comment: All font sizes explicitly set (title=26, axes=20, ticks=16/14). Y-axis - tick text at 14pt slightly small but readable. + comment: All font sizes explicitly set (title 16pt, axis titles 12pt, tick + labels 10-11pt). Readable in both themes. Section labels (geom_text size=4 + mm) and role labels (size=3.5 mm) are slightly small but readable. Minor + deduction for small annotation text at mobile scale. - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping elements. Y-axis labels well-spaced showing only white - keys. + comment: No text or element overlap. Role labels placed in right margin beyond + beat 32, section labels above data area. No collisions. - id: VQ-03 name: Element Visibility score: 5 max: 6 passed: true - comment: Note rectangles clearly visible with good melody/accompaniment differentiation. - Some eighth-note melody rectangles quite small. + comment: Accompaniment notes (long bars) clearly visible. Short-duration melody + notes (eighth notes at some positions) appear as small rectangles — inherent + to short durations, but reduces overall visibility score slightly. - id: VQ-04 name: Color Accessibility - score: 4 - max: 4 + score: 2 + max: 2 passed: true - comment: Plasma colormap is perceptually uniform and colorblind-safe. + comment: 'Imprint sequential palette (#009E73 to #4467A3) is CVD-safe. Luminance + range adequate across velocity. Melody vs. accompaniment distinguished by + opacity and border.' - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Good proportions at 1600x900. Plot fills canvas well with balanced - margins. + comment: Canvas gate passed (3200x1800 via ggsize(800,450) scale=4). Plot + fills canvas well with balanced margins. Colorbar on right, section labels + in top margin. No content cut off. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: X-axis includes units (beats). Y-axis uses note names. Title correct - format. + comment: 'X-axis: ''Time (beats)'' with units. Y-axis: ''Pitch'' appropriate + for note-name axis. Title format correct.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Continuous velocity uses imprint_seq (low=#009E73, high=#4467A3). + Light background #FAF8F1, dark #1A1A17. All chrome tokens theme-adaptive. + Data colors identical across themes. Full compliance.' design_excellence: score: 15 max: 20 @@ -94,22 +123,27 @@ review: score: 6 max: 8 passed: true - comment: Plasma colormap, clear visual hierarchy between melody and accompaniment, - custom panel colors. Above defaults. + comment: 'Strong design: alternating black/white key rows, semantic beat/measure + grid, velocity colormap storytelling, melody/accompaniment visual hierarchy + with distinct borders and opacity, musical dynamic labels. Clearly above + defaults.' - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: Custom beat/measure grid lines, panel background customized, black - key shading. Good but not every detail polished. + comment: Panel grid replaced with semantic beat/measure lines, row shading + added, legend styled with ELEVATED_BG. Panel border (all 4 spines) kept + — appropriate for piano roll but top/right removal would add further polish. - id: DE-03 name: Data Storytelling score: 5 max: 6 passed: true - comment: Section labels show dynamic arc, velocity coloring shows dynamics, - melody/accompaniment separation creates hierarchy. + comment: 'Strong musical narrative: section labels explicitly narrate pp—Building + → f—Response → pp—Restart → fff—Climax dynamic arc. Velocity gradient visually + reinforces the journey from soft green notes to loud blue climax. Melody/accompaniment + separation creates clear visual layers.' spec_compliance: score: 15 max: 15 @@ -119,26 +153,31 @@ review: score: 5 max: 5 passed: true - comment: 'Correct piano roll: horizontal rectangles on pitch-time grid.' + comment: Correct piano roll with horizontal note rectangles, velocity-colored, + beat/measure grid, alternating key row shading. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All spec features present: note rectangles, note names, velocity - color, black key shading, beat/measure grids.' + comment: 'All spec features: note names on Y-axis (white keys only with correct + names), alternating black/white key shading, beat divisions with stronger + measure lines, sequential colormap for velocity, auto-fit pitch range with + margin.' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=time, Y=pitch with note names, color=velocity. All correct. + comment: X=time in beats (start/end), Y=pitch (MIDI→note name), bar width=duration, + color=velocity. All correctly mapped. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title in correct format. Velocity colorbar legend properly labeled. + comment: 'Title: ''piano-roll-midi · python · letsplot · anyplot.ai'' — correct + format. Velocity colorbar with descriptive label.' data_quality: score: 15 max: 15 @@ -148,20 +187,25 @@ review: score: 6 max: 6 passed: true - comment: Shows chord progression, varied rhythms, wide velocity range, different - durations, multi-octave pitch spread. + comment: 'Shows all aspects: melody and accompaniment layers, varying note + durations (eighth to whole notes), full dynamic range pp to fff, chord voicings + with multiple simultaneous pitches, 8-measure progression.' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real I-vi-IV-V chord progression with meaningful dynamic arc. + comment: Realistic Cmaj→Am→F→G chord progression — very common in popular + music. Middle C = MIDI 60 is correct. Dynamic markings (pp, f, fff) are + standard musical notation. Neutral, non-controversial music theory content. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: MIDI pitches A2-G5, velocities 35-127, 8 measures. All realistic. + comment: MIDI velocity range 0-127 correctly applied (30-127 in data). MIDI + note numbers correct for piano range (A2=45 to G5=79). 8 measures at 4 beats + each = 32 beats total is factually correct for 4/4 time. code_quality: score: 10 max: 10 @@ -171,31 +215,36 @@ review: score: 3 max: 3 passed: true - comment: 'Flat structure: imports, data, plot, save. No functions or classes.' + comment: 'Flat script: imports → tokens → data → plot → save. No functions + or classes.' - 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 mostly deterministic but seed ensures + reproducibility. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: 'All imports used: os, numpy, pandas, lets_plot.' + comment: 'All imports used: os (env/path), shutil (cleanup), numpy, pandas, + lets_plot, export_ggsave.' - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Well-organized, clear variable names, appropriate complexity. + comment: Clean, well-organized. Data generation is necessarily verbose for + a rich musical example. No fake functionality. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png via ggsave with scale=3. Current API. + comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current letsplot API. + Cleanup of lets-plot-images directory. library_mastery: score: 7 max: 10 @@ -205,28 +254,32 @@ review: score: 4 max: 5 passed: true - comment: Proper ggplot grammar with aes, geom layers, scales, themes, coord_cartesian, - ggsize. + comment: 'Good idiomatic ggplot grammar: layer composition with multiple geom_rect/geom_vline/geom_text, + scale_fill_gradient, coord_cartesian, theme_minimal + custom theme. layer_tooltips() + is idiomatic letsplot pattern.' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses layer_tooltips() for interactive hover info and HTML dual export. + comment: layer_tooltips() with structured multi-line tooltip content is a + distinctive letsplot capability. HTML output with interactive tooltips sets + this apart. Multiple geom layers with different data sources is well-leveraged. verdict: APPROVED impl_tags: dependencies: [] techniques: - layer-composition + - colorbar - annotations - hover-tooltips - html-export - - custom-legend + - manual-ticks patterns: - data-generation + - iteration-over-groups dataprep: [] styling: - custom-colormap - alpha-blending - - grid-styling - edge-highlighting