From 1020eb87c478551b2d0ec7a17cf76fa7b01a9624 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 03:40:40 +0000 Subject: [PATCH 1/3] feat(altair): implement piano-roll-midi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regen from quality 90. Addressed: - Fixed canvas: width=620, height=330, scale_factor=4.0 with PIL padding to exact 3200×1800 - Fixed title to piano-roll-midi · python · altair · anyplot.ai (added language token, fixed anyplot.ai) - Fixed output files to plot-{THEME}.png + plot-{THEME}.html - Added full ANYPLOT_THEME support with theme-adaptive chrome (PAGE_BG, INK, INK_SOFT, ELEVATED_BG) - Replaced custom velocity palette with Imprint sequential (#009E73→#4467A3) — fixes perceptual uniformity weakness - Made piano key background rows theme-adaptive (light/dark alternating shading) - Added sys.path fix to prevent altair.py shadowing installed altair package - Added configure_view/configure_axis/configure_legend with full theme tokens Co-Authored-By: Claude Sonnet 4.6 --- .../implementations/python/altair.py | 162 ++++++++++-------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/plots/piano-roll-midi/implementations/python/altair.py b/plots/piano-roll-midi/implementations/python/altair.py index 8d3116952c..a65e939367 100644 --- a/plots/piano-roll-midi/implementations/python/altair.py +++ b/plots/piano-roll-midi/implementations/python/altair.py @@ -1,25 +1,43 @@ -""" pyplots.ai +"""anyplot.ai piano-roll-midi: MIDI Piano Roll Visualization -Library: altair 6.0.0 | Python 3.14.3 -Quality: 90/100 | Created: 2026-03-07 +Library: altair | Python 3.13 +Quality: pending | Created: 2026-06-03 """ +import os +import sys + + +# Prevent this file from shadowing the installed altair package when run from its own directory +_thisdir = os.path.dirname(os.path.abspath(__file__)) +sys.path = [p for p in sys.path if os.path.abspath(p or ".") != _thisdir] +del _thisdir + import altair as alt import numpy as np import pandas as pd +from PIL import Image -# Data: A short melodic phrase with chords (C major progression with melody) -np.random.seed(42) +# 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" -# MIDI note name mapping -NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] +# Piano key row shading (theme-adaptive alternating background) +WHITE_KEY_BG = "#F0EDE6" if THEME == "light" else "#242420" +BLACK_KEY_BG = "#D8D4CC" if THEME == "light" else "#1A1A17" +# Data: C major progression with melody over 8 measures (32 beats) +np.random.seed(42) +NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] -# Build a musical phrase: chords + melody over 8 measures (32 beats in 4/4) notes = [] -# Bass line (lower octave) +# Bass line (A2–F3) bass_pattern = [ (0, 2, 48), (2, 2, 52), @@ -41,7 +59,7 @@ for start, dur, pitch in bass_pattern: notes.append({"start": start, "duration": dur, "pitch": pitch, "velocity": np.random.randint(60, 80)}) -# Chord voicings (mid range) +# Chord voicings (B3–A4) chord_hits = [ (0, [60, 64, 67]), (4, [60, 65, 69]), @@ -56,7 +74,7 @@ for p in pitches: notes.append({"start": start, "duration": 3.5, "pitch": p, "velocity": np.random.randint(50, 75)}) -# Melody (upper range, varying rhythms and velocities) +# Melody (B4–G5, varying rhythms and dynamics) melody = [ (0, 1, 72, 100), (1, 0.5, 74, 90), @@ -97,47 +115,42 @@ df = pd.DataFrame(notes) df["end"] = df["start"] + df["duration"] - -# Create note labels (e.g., C4, D#5) df["note_name"] = df["pitch"].apply(lambda p: f"{NOTE_NAMES[p % 12]}{p // 12 - 1}") -# Only include pitches actually used + 1 semitone margin on each side +# Display pitch range: used pitches ± 1 semitone for piano key context used_pitches = set(df["pitch"].unique()) pitch_min = df["pitch"].min() - 1 pitch_max = df["pitch"].max() + 1 -# Keep used pitches plus their immediate neighbors for context display_pitches = set() for p in used_pitches: display_pitches.update([p - 1, p, p + 1]) -# Clamp to the overall range all_pitches = sorted([p for p in display_pitches if pitch_min <= p <= pitch_max]) -# Black key indicators for background shading -black_key_semitones = {1, 3, 6, 8, 10} - -# Create background rows for piano key coloring -bg_rows = [] -for p in all_pitches: - is_black = (p % 12) in black_key_semitones - bg_rows.append( - {"pitch": p, "note_name": f"{NOTE_NAMES[p % 12]}{p // 12 - 1}", "is_black": is_black, "start": 0, "end": 32} - ) +# Sort order: low pitch at bottom (descending list for Altair nominal axis) +pitch_labels = [f"{NOTE_NAMES[p % 12]}{p // 12 - 1}" for p in reversed(all_pitches)] +# Piano key background rows (black semitones: C#, D#, F#, G#, A#) +BLACK_SEMITONES = {1, 3, 6, 8, 10} +bg_rows = [ + { + "pitch": p, + "note_name": f"{NOTE_NAMES[p % 12]}{p // 12 - 1}", + "is_black": (p % 12) in BLACK_SEMITONES, + "start": 0.0, + "end": 32.0, + } + for p in all_pitches +] bg_df = pd.DataFrame(bg_rows) -# Sort order for y-axis (low pitch at bottom, high at top — reversed for Altair) -pitch_labels = [f"{NOTE_NAMES[p % 12]}{p // 12 - 1}" for p in reversed(all_pitches)] - -# Assign musical layer labels for selection filtering +# Musical layer classification for legend-bound selection df["layer"] = df["pitch"].apply(lambda p: "Bass" if p < 55 else ("Chords" if p < 70 else "Melody")) -# Selection: click legend to highlight a musical layer +# Selections: layer toggle (legend) + measure brush (HTML only) layer_selection = alt.selection_point(fields=["layer"], bind="legend") - -# Selection: interval selection on x-axis for measure zoom (HTML) brush = alt.selection_interval(encodings=["x"]) -# Background: alternating shading for black/white keys +# Layer 1: Alternating piano key row shading background = ( alt.Chart(bg_df) .mark_bar() @@ -145,47 +158,42 @@ x=alt.X("start:Q", scale=alt.Scale(domain=[0, 32])), x2="end:Q", y=alt.Y("note_name:N", sort=pitch_labels), - color=alt.condition(alt.datum.is_black, alt.value("#d4d0c8"), alt.value("#f5f3ef")), + color=alt.condition(alt.datum.is_black, alt.value(BLACK_KEY_BG), alt.value(WHITE_KEY_BG)), ) ) -# Beat grid lines (vertical rules at each beat) +# Layer 2: Beat grid lines (dashed, subtle) beat_positions = pd.DataFrame({"beat": list(range(33))}) beat_grid = ( - alt.Chart(beat_positions).mark_rule(strokeDash=[3, 3], opacity=0.25, color="#8a8580").encode(x=alt.X("beat:Q")) + alt.Chart(beat_positions).mark_rule(strokeDash=[3, 3], opacity=0.25, color=INK_SOFT).encode(x=alt.X("beat:Q")) ) -# Measure lines (stronger lines every 4 beats) +# Layer 3: Measure boundary lines (solid, stronger) measure_positions = pd.DataFrame({"beat": list(range(0, 33, 4))}) -measure_grid = ( - alt.Chart(measure_positions).mark_rule(opacity=0.5, color="#5a554f", strokeWidth=1.5).encode(x=alt.X("beat:Q")) -) - -# Color scale: warm amber-to-crimson palette for velocity -velocity_colors = ["#2c1654", "#5b3a8c", "#9b4dca", "#d4577a", "#f28c38", "#ffd54f"] +measure_grid = alt.Chart(measure_positions).mark_rule(opacity=0.5, color=INK, strokeWidth=1.5).encode(x=alt.X("beat:Q")) -# Piano roll notes with selection-based opacity +# Layer 4: Note bars — Imprint sequential velocity (soft=green #009E73, loud=blue #4467A3) note_bars = ( alt.Chart(df) - .mark_bar(cornerRadius=4, stroke="#1a1a2e", strokeWidth=0.6) + .mark_bar(cornerRadius=3, stroke=INK, strokeWidth=0.5) .encode( x=alt.X( "start:Q", title="Measure", axis=alt.Axis( - labelFontSize=16, - titleFontSize=20, + labelFontSize=10, + titleFontSize=12, values=list(range(0, 33, 4)), labelExpr="'M' + (datum.value / 4 + 1)", ), ), x2="end:Q", - y=alt.Y("note_name:N", sort=pitch_labels, title="Pitch", axis=alt.Axis(labelFontSize=16, titleFontSize=20)), + y=alt.Y("note_name:N", sort=pitch_labels, title="Pitch", axis=alt.Axis(labelFontSize=9, titleFontSize=12)), color=alt.Color( "velocity:Q", title="Velocity", - scale=alt.Scale(range=velocity_colors, domain=[40, 127]), - legend=alt.Legend(titleFontSize=18, labelFontSize=16, orient="right", gradientLength=300), + scale=alt.Scale(range=["#009E73", "#4467A3"], domain=[40, 127]), + legend=alt.Legend(titleFontSize=10, labelFontSize=9, orient="right", gradientLength=150), ), opacity=alt.condition(layer_selection, alt.value(0.95), alt.value(0.15)), tooltip=[ @@ -199,39 +207,49 @@ .add_params(layer_selection) ) -# Layer labels as text annotations on the right edge +# Layer 5: Musical layer labels at right margin layer_label_data = pd.DataFrame( [ - {"x": 32.5, "note_name": "C3", "label": "Bass"}, - {"x": 32.5, "note_name": "E4", "label": "Chords"}, - {"x": 32.5, "note_name": "E5", "label": "Melody"}, + {"x": 32.3, "note_name": "C3", "label": "Bass"}, + {"x": 32.3, "note_name": "E4", "label": "Chords"}, + {"x": 32.3, "note_name": "E5", "label": "Melody"}, ] ) layer_labels = ( alt.Chart(layer_label_data) - .mark_text(align="left", dx=8, fontSize=14, fontWeight="bold", color="#5a554f", fontStyle="italic") + .mark_text(align="left", dx=8, fontSize=9, fontWeight="bold", color=INK_MUTED, fontStyle="italic") .encode(x=alt.X("x:Q"), y=alt.Y("note_name:N", sort=pitch_labels), text="label:N") ) -# Layer everything together +# Title: 46 chars < 67 baseline → fontSize=16 (no scaling needed) +title = "piano-roll-midi · python · altair · anyplot.ai" + +# Compose all five layers with full theme-adaptive configuration chart = ( (background + beat_grid + measure_grid + note_bars + layer_labels) .properties( - width=1500, - height=850, - title=alt.Title( - text="piano-roll-midi · altair · pyplots.ai", - fontSize=28, - anchor="middle", - offset=20, - color="#1a1a2e", - subtitleColor="#5a554f", - ), + width=620, height=330, background=PAGE_BG, title=alt.Title(text=title, fontSize=16, anchor="middle", color=INK) ) - .configure_view(strokeWidth=0) - .configure_axis(grid=False) + .configure_view(fill=PAGE_BG, strokeWidth=0) + .configure_axis(grid=False, domainColor=INK_SOFT, tickColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) + .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) ) -# Save -chart.save("plot.png", scale_factor=3.0) -chart.add_params(brush).save("plot.html") +# Save PNG and pad to exact canvas target (3200 × 1800) +TW, TH = 3200, 1800 +chart.save(f"plot-{THEME}.png", scale_factor=4.0) + +_img = Image.open(f"plot-{THEME}.png").convert("RGB") +_w, _h = _img.size +if _w > TW or _h > TH: + raise SystemExit( + f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " + "Shrink chart .properties(width=, height=) values and re-render." + ) +if _w < TW or _h < TH: + _canvas = Image.new("RGB", (TW, TH), PAGE_BG) + _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) + _canvas.save(f"plot-{THEME}.png") + +# Save interactive HTML (adds measure brush selection) +chart.add_params(brush).save(f"plot-{THEME}.html") From 5ae2e78944ad98423560c30dbdfcb9d80fd8b464 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 03:40:53 +0000 Subject: [PATCH 2/3] chore(altair): add metadata for piano-roll-midi --- .../metadata/python/altair.yaml | 248 ++---------------- 1 file changed, 16 insertions(+), 232 deletions(-) diff --git a/plots/piano-roll-midi/metadata/python/altair.yaml b/plots/piano-roll-midi/metadata/python/altair.yaml index 8b18238de7..1a957eaa41 100644 --- a/plots/piano-roll-midi/metadata/python/altair.yaml +++ b/plots/piano-roll-midi/metadata/python/altair.yaml @@ -1,237 +1,21 @@ +# Per-library metadata for altair implementation of piano-roll-midi +# Auto-generated by impl-generate.yml + library: altair +language: python specification_id: piano-roll-midi created: '2026-03-07T19:47:04Z' -updated: '2026-03-07T20:00:50Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 22805912211 +updated: '2026-06-03T03:40:52Z' +generated_by: claude-sonnet +workflow_run: 26861756737 issue: 4565 -python_version: 3.14.3 -library_version: 6.0.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/altair/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/altair/plot.html -quality_score: 90 +language_version: 3.13.13 +library_version: 6.1.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-dark.html +quality_score: null review: - strengths: - - Expert Altair layer composition combining 5 chart layers (background, beat grid, - measure grid, notes, labels) - - Excellent musical data with three distinct layers (bass, chords, melody) creating - a realistic piano roll - - 'Well-executed improvements from attempt 1: font sizes fixed, axis title corrected, - Altair selections added' - - Custom warm palette creates distinctive DAW-like aesthetic - - Legend-bound selection for interactive layer filtering is a compelling Altair-specific - feature - - Clean, well-structured code with clear musical intent - weaknesses: - - Color palette not as perceptually uniform as viridis/cividis; mid-range purple-pink - transition could be harder for some colorblind users - - Some empty pitch rows between musical layers create vertical dead space (though - this mirrors piano layout) - image_description: 'The plot displays a piano roll visualization of MIDI notes across - 8 measures (M1-M9) on the x-axis and pitch names (G#2 to G#5) on the y-axis. Horizontal - bars represent individual notes, with bar length encoding note duration and color - encoding velocity. The background alternates between warm off-white (#f5f3ef) - and light beige (#d4d0c8) rows to distinguish white and black piano keys. Note - bars are colored using a custom sequential palette from deep purple (#2c1654) - for soft notes (~40 velocity) through magenta, pink, and orange to golden yellow - (#ffd54f) for loud notes (~120 velocity). Each bar has rounded corners (cornerRadius=4) - and a subtle dark stroke. Dashed gray vertical lines mark individual beats with - stronger solid brown lines at measure boundaries every 4 beats. The title reads - "piano-roll-midi · altair · pyplots.ai" centered at top in dark navy. Three musical - layers are visible: bass notes in the lower register (C3-E3), chord voicings in - the mid range (C4-A4 in deep purple), and a melody line in the upper range (C5-G5 - in warmer colors). Italic bold layer labels ("Bass", "Chords", "Melody") appear - on the right edge. A velocity gradient legend is positioned on the right side - showing the full color range from 40-120.' - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: title 28pt, axis titles 20pt, tick - labels 16pt, legend title 18pt, legend labels 16pt. All perfectly readable.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: All text elements fully readable with no collisions. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Most note bars clearly visible. Some 0.5-beat melody notes are small - but distinguishable. - - id: VQ-04 - name: Color Accessibility - score: 3 - max: 4 - passed: true - comment: Custom purple-to-yellow palette has good luminance progression but - mid-range is not as perceptually uniform as viridis. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good width usage. Some empty pitch rows between layers create vertical - dead space, though this mirrors piano layout. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Measure with M1-M9 labels and Pitch with note names are domain-appropriate - descriptive labels. - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Custom warm palette, rounded corners, alternating key backgrounds, - italic layer labels, custom measure numbering. Clearly above defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Grid disabled, custom beat/measure grid with dashed/solid differentiation, - view stroke removed, warm neutral tones. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Three distinct musical layers with velocity dynamics. Layer labels - guide viewer. Interactive selection in HTML. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct horizontal rectangles positioned by pitch and time. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features: note names, key shading, beat/measure grid, velocity - color, auto-fit range.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=time in measures, Y=pitch with note names. Correctly mapped with - proper sort order. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct. Velocity gradient legend with appropriate range. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Bass, chords, melody layers with varying durations and velocities - across full range. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: C major chord progression with melody - musically coherent and neutral. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Realistic MIDI pitches, velocities, and durations across 8 measures. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean linear flow with no functions/classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used (altair, numpy, pandas). - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-structured with clear musical intent. Interactive features are - genuine Altair capabilities. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot.png and plot.html with current API. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert declarative Altair: layered composition, conditional encoding, - proper types, labelExpr, sort, x2 encoding.' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: selection_point with legend binding, selection_interval for brush - zoom, alt.condition for opacity. Distinctly Altair features. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - hover-tooltips - - html-export - - custom-legend - patterns: - - data-generation - dataprep: [] - styling: - - custom-colormap - - grid-styling - - edge-highlighting + strengths: [] + weaknesses: [] From 2c8620487e2251b87150ba2e39866a5d5b20b598 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jun 2026 03:47:34 +0000 Subject: [PATCH 3/3] chore(altair): update quality score 91 and review feedback for piano-roll-midi --- .../implementations/python/altair.py | 6 +- .../metadata/python/altair.yaml | 265 +++++++++++++++++- 2 files changed, 261 insertions(+), 10 deletions(-) diff --git a/plots/piano-roll-midi/implementations/python/altair.py b/plots/piano-roll-midi/implementations/python/altair.py index a65e939367..acf5dcc675 100644 --- a/plots/piano-roll-midi/implementations/python/altair.py +++ b/plots/piano-roll-midi/implementations/python/altair.py @@ -1,7 +1,7 @@ -"""anyplot.ai +""" anyplot.ai piano-roll-midi: MIDI Piano Roll Visualization -Library: altair | Python 3.13 -Quality: pending | Created: 2026-06-03 +Library: altair 6.1.0 | Python 3.13.13 +Quality: 91/100 | Updated: 2026-06-03 """ import os diff --git a/plots/piano-roll-midi/metadata/python/altair.yaml b/plots/piano-roll-midi/metadata/python/altair.yaml index 1a957eaa41..1b51638484 100644 --- a/plots/piano-roll-midi/metadata/python/altair.yaml +++ b/plots/piano-roll-midi/metadata/python/altair.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for altair implementation of piano-roll-midi -# Auto-generated by impl-generate.yml - library: altair language: python specification_id: piano-roll-midi created: '2026-03-07T19:47:04Z' -updated: '2026-06-03T03:40:52Z' +updated: '2026-06-03T03:47:34Z' generated_by: claude-sonnet workflow_run: 26861756737 issue: 4565 @@ -15,7 +12,261 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/piano-rol preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/piano-roll-midi/python/altair/plot-dark.html -quality_score: null +quality_score: 91 review: - strengths: [] - weaknesses: [] + strengths: + - 'Excellent spec compliance — all required features present: piano roll layout, + note names on Y-axis, alternating piano key row shading, dashed beat grid + solid + measure boundary lines, and Imprint sequential velocity colormap' + - Five-layer Altair composition (background rows, beat grid, measure grid, note + bars, layer labels) is idiomatic and sophisticated + - Musical layer labels ('Bass', 'Chords', 'Melody') at right margin add strong data + storytelling and visual hierarchy + - Interactive legend selection (layer_selection) and HTML brush export demonstrate + full use of Altair's distinctive interactivity features + - Theme-adaptive chrome correctly threaded through all elements — both light (#FAF8F1) + and dark (#1A1A17) renders are fully readable with appropriate text colors + - Measure labels (M1–M9) using labelExpr is a thoughtful UX detail that makes beat + positions immediately interpretable + - Canvas correctly padded to 3200×1800 with PIL; fail-loud logic if vl-convert overshoots + is a production-quality guard + weaknesses: + - Y-axis tick label fontsize=9 is on the small side for a 40-row piano roll; with + 4× scale_factor this is ~36 source pixels — readable on desktop but tight on mobile + at 400px width; consider raising to fontSize=10 to match the X-axis tick labels + - 'Black key / white key row contrast is very subtle in the dark render (#1A1A17 + vs #242420 differ by only ~14 luminance units); the piano keyboard metaphor could + be clearer with a slightly stronger delta — consider #2A2A26 for white-key rows + in dark mode' + - The velocity colormap direction (green=soft, blue=loud) is counterintuitive relative + to most DAW conventions (warm=loud, cool=soft); while Imprint sequential is correct, + a note or a reversed domain could better serve musical semantics + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct, not pure white. + Chrome: Title "piano-roll-midi · python · altair · anyplot.ai" in bold dark text, centered, clearly readable. Y-axis label "Pitch" and X-axis label "Measure" in dark ink, readable. Y-axis tick labels (G#2 through G#5 note names) at fontSize=9 — small but readable. Measure labels M1–M9 at fontSize=10 — clearly readable. Velocity colorbar with "Velocity" title and labels "127"/"40" in dark text — all visible. + Data: Note bars rendered in Imprint sequential colormap — green (#009E73) for low velocity (~40) to blue (#4467A3) for high velocity (~127). Alternating row shading: white-key rows at #F0EDE6, black-key rows at #D8D4CC — subtle but visible piano keyboard layout. Dashed beat grid lines at opacity=0.25 and solid measure boundary lines at opacity=0.5 both clearly rendered. Layer labels "Melody", "Chords", "Bass" in italic bold at right margin — readable. + Legibility verdict: PASS — all text readable in light theme, no light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct, not pure black. + Chrome: Title in light text (#F0EFE8) clearly visible against dark background. Y-axis label "Pitch" and X-axis label "Measure" in light ink — readable. Y-axis tick labels in light gray (#B8B7B0) — readable. Measure labels in light gray — readable. Velocity colorbar legend text in light tones — readable. Layer labels "Melody", "Chords", "Bass" in italic, readable. No dark-on-dark text failures detected. + Data: Note bar colors are identical to light render — same green-to-blue gradient confirming only chrome flips between themes. Alternating row shading (white-key rows #242420 vs black-key rows #1A1A17) is very subtle in dark mode — distinguishable on close inspection but less prominent than the light render. + Legibility verdict: PASS — all text readable in dark theme, no dark-on-dark failures. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Title, axis labels, and measure ticks all clearly readable in both + themes. Y-axis note name labels at fontSize=9 are on the small side for + 40 rows but readable at full resolution; slight mobile concern. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text or element overlap detected. Layer labels at right margin + are well-separated from the legend. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: All note bars clearly visible in both themes. Alternating background + rows, beat/measure grid lines all rendered at appropriate opacity. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Imprint sequential colormap (green to blue) is CVD-safe. No red-green-only + encoding. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas correctly padded to 3200×1800. Canvas gate passed. Good proportions + — title at ~60% width, balanced layout with legend and layer labels. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: X-axis 'Measure' with M1–M9 labels, Y-axis 'Pitch' with note names. + Correct title format. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Imprint sequential (green #009E73 → blue #4467A3) used for continuous + velocity data. Plot backgrounds #FAF8F1 light / #1A1A17 dark. Both renders + theme-correct.' + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Piano keyboard alternating background, multi-layer composition, velocity + gradient, musical layer labels at margin — well above generic defaults. + Professional DAW-like aesthetic. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: strokeWidth=0 removes view border; configure_axis(grid=False) with + custom manual grid lines shows deliberate design; cornerRadius=3 on note + bars adds polish; dashed vs solid grid differentiates beat/measure hierarchy. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Musical layer labels guide viewer through Bass/Chords/Melody structure. + Velocity gradient communicates dynamics. Interactive legend selection enables + exploration. Clear focal hierarchy from the melody at top. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Piano roll visualization with horizontal note bars — correct chart + type for MIDI data. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Note names on Y-axis ✓, alternating piano key row shading ✓, dashed + beat grid + solid measure lines ✓, sequential velocity colorscale ✓, auto-fit + pitch range with ±1 semitone margin ✓. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Time (beat) on X-axis, pitch on Y-axis, velocity on color, note duration + as bar width. All mappings correct. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title 'piano-roll-midi · python · altair · anyplot.ai' matches required + format. Velocity colorbar with gradient legend. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: 'Shows bass line, chord voicings, and melody with varying rhythms + and dynamics across 8 measures. Covers all aspects of a piano roll: pitch, + time, duration, velocity.' + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: C major harmonic progression with realistic chord voicings, melodic + phrases, and bass line. MIDI data is musically plausible and neutral. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 8 measures (32 beats), MIDI notes 45–79 (A2 to G5), velocity 50–120. + Appropriate scale for a musical demo. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: No functions or classes. Procedural top-down code with clear sections + (data, background, grid, notes, labels, composition). + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set before any random calls. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports (altair, numpy, pandas, PIL, os, sys) are used. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean Altair layer composition. No fake UI or simulated interactivity. + Sensible variable naming. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html. Correct file naming + convention. + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Proper use of Altair layer composition (+), encoding types (Q/N), + alt.condition for opacity, sort=pitch_labels for nominal axis ordering, + configure_* chaining. All idiomatic. + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: 'Uses Altair-specific interactive features: selection_point with + bind=''legend'' for layer filtering, selection_interval for brush selection + in HTML output, alt.condition for conditional opacity. These are distinctly + Altair capabilities not available in static libraries.' + verdict: APPROVED +impl_tags: + dependencies: + - pillow + techniques: + - layer-composition + - hover-tooltips + - html-export + - colorbar + patterns: + - data-generation + dataprep: [] + styling: + - custom-colormap + - alpha-blending + - edge-highlighting