Skip to content
Changelog

What Piano shipped.

Every entry in docs/CHANGELOG.md, read at 51ba9d7 and dated by the commit that wrote it.

Atom feed
  1. Piano has no public page, README or LICENSE, so nobody finds it and the MIT claim points at no file

    Piano has a public site at /piano/ on freewilly's model, its facts generated from the repo, deployed with the sample pack, plus LICENSE, README and CONTRIBUTING (design recorded in site/README.md).

    PI111 · 55a9c5c
  2. no installer survives the release workflow, so v0.1.0 cannot be published

    All three installers now package, install and pass the association check in CI: Linux names its executable, Windows lets go of .piano, macOS reads split claims.

    PI110 · 6e4672f
  3. A chosen option is told from the ones beside it by the weight of its text, which reads as no difference

    A chosen option carries the accent and is measured against its neighbours in both themes, which also caught the light accent failing its own text contrast.

    PI109 · 595940f
  4. Open recent still offers a deleted piece, and the system's own list still offers one at the path it has moved from

    A deleted piece leaves Open recent with it, and the system's own list is rebuilt from ours whenever ours changes, so neither offers a file that has gone.

    PI108 · dafae33
  5. Deleting or retitling from chat skips the bin and leaves the practice records behind, where the panel does neither

    With no window a delete says it cannot be undone before doing it, and a correction leaves a note the app applies to the records next launch.

    PI107 · 047a05c
  6. Open recent names a piece by the title it had when it was opened, so a corrected one is listed under its old name

    A corrected piece is offered from Open recent under what it is called now, at the path it moved to, without waiting to be opened again.

    PI106 · 4da6187
  7. Tidying a library that grew by the batch means opening, confirming and closing one row at a time

    Rows can be picked in the panel and then deleted or tagged together, with one question asked of the whole set and one sentence about how it went.

    PI105 · 27ca9bc
  8. Claude Code can write a score into the library but never remove one, nor correct what one says without rewriting it

    correct_score changes what a score says about itself and delete_score takes one out, both through the library module the window writes with.

    PI104 · a9dcd39
  9. Practice history is keyed by a score's id or, where it has none, its title: renaming a piece starts its record over

    A correction gives a piece the id it is filed under, its records move to that key, and deleting says the attempts recorded against it are kept.

    PI103 · d602c8e
  10. A retitled piece keeps the file name and id it was filed under, so the panel, the folder and chat disagree

    Retitling a piece moves it to the name that title gives, asks about a name already taken the way filing does, and says what it is filed as now.

    PI102 · bf0ee6f
  11. Nothing in the app removes a score: a piece filed by mistake leaves the library only through the file manager

    A row offers to delete, asks once naming the piece by title, composer and length, and sends the file to the system's bin, so a wrong click can be put back from there.

    PI101 · 77c6d8a
  12. A piece's title, composer, level and tags can be set only as it is filed, never corrected afterwards

    A row in the library opens the filing form filled in, and saving it writes the corrected title, composer, level, difficulty and tags back into the same file, leaving the notes alone.

    PI100 · d0aae17
  13. A file the library folder cannot take in stays there unlisted, and only the log says why

    The Library panel names every file the folder would not take in and why, and offers to open the ones whose only problem is a name already used.

    PI99 · c01a49c
  14. The empty library tells people to ask Claude Code and never says that a MIDI file of their own can go in

    The empty library names both ways in and offers the second as a door: ask Claude Code for a piece, or open a file already on the disk.

    PI98 · c2ceb47
  15. A MIDI or MusicXML file copied into the library folder is ignored by the index, and nothing on screen says why

    A MIDI or MusicXML file copied into the library folder is imported once and filed as a score beside it, so a folder of downloads becomes a library.

    PI97 · 535c175
  16. An imported piece is filed with a title and nothing else: no composer, no level, no tags, so no filter finds it

    Filing asks for the composer, level and tags in one form, prefilled from the file, so an imported piece is one the list's own filters reach.

    PI96 · 1117e24
  17. An import filed under a title the library already holds overwrites it, with nothing asked and nothing said

    Filing a piece whose id is taken says what is there and offers to number the new one or replace the old, so two pieces with one title both survive.

    PI95 · a5cb1aa
  18. Nothing in the window puts a score into the library: an imported MIDI or MusicXML piece is gone when it closes

    An Add to library button files the open score through the library package, so an imported MIDI or MusicXML piece is listed and playable next launch.

    PI94 · a87cb47
  19. An import whose repeats and jumps expand past 4,000 bars is cut off mid-piece and nothing says it was cut

    An import stopped by the 4,000-bar ceiling now says so in dropped, told apart from one that simply reached the end of the piece.

    PI93 · e2c1073
  20. The installed-app check hands over a .piano only, so the MIDI and MusicXML it registers are never opened end to end

    The installed app is now started with a .piano, a .mid, a .musicxml and a .mxl, and has to say it opened each one.

    PI92 · aec7643
  21. The transport bar's first button is wrapped in two hints, and the outer one can never open

    The transport bar's first button carries one hint instead of two, and a test now covers its tooltip beside the loop button's.

    PI91 · ce1fbdb
  22. A da capo or dal segno is reported and not followed, so an imported piece ends where the writing stops

    A da capo, a dal segno, a to coda and a fine are now followed, so an imported piece plays its jumps the way a reader takes them.

    PI90 · 6b9c889
  23. A .musicxml or .mxl double-clicked in the file manager opens something else, the installer claiming neither

    A .musicxml or .mxl now offers the piano under Open With on Windows, as an Alternate claim on macOS and a MIME type on Linux, taking neither from a notation editor.

    PI89 · c7d6be3
  24. A MusicXML file cannot be opened, so the stave guesses spellings, hands and fingering the file already states

    MusicXML opens as a score, keeping the spellings, hands, fingering and articulation the file wrote down, as .musicxml, .xml or a zipped .mxl.

    PI88 · 1ddc70a
  25. A parts row shows a speaker, an S and an eye, and nothing says which of them silences and which hides

    Hovering or focusing a parts row's speaker, S or eye now says which part it silences, solos or hides, in the button's own words.

    PI87 · 99a608d
  26. The transport bar is a row of icons, and a pointer resting on one is told nothing

    Every icon in the bar says its own name on hover and on focus, read off the button so there is one string to keep right (design superseded: a hint carries its own provider: panels mount alone).

    PI86 · 07ecad1
  27. Moving the stave's zoom slider changes nothing on the page

    The slider magnifies the page now: App passes the setting the stave was always able to use, and a test drives the whole path.

    PI85 · 08e2a06
  28. A library row whose composer is a sentence shows no title: the chips squeeze it to nothing

    Every library row shows its title: the chips are capped at half the row and truncate, and a browser test measures the width rather than assuming it.

    PI84 · e7d0281
  29. The stave is drawn at one size, and on a dense score its notes are too small to read

    The stave has a zoom of its own now, and the page follows the sounding bar sideways as well as down (design superseded: one magnifier showing the open view's slider, not two at once).

    PI83 · 83108ce
  30. The button that leaves the stave shows the same note glyph as the MIDI monitor two places along

    The button now wears a piano and the monitor keeps its note, so the two no longer read as one control (design superseded: jsdom sees an icon: it is inline SVG, not a matter of layout).

    PI82 · 64ae001
  31. Notes in a busy bar are drawn on top of each other, so a page of sixteenths is unreadable

    A bar is now as wide as its figures need, so a page of sixteenths reads instead of stacking its notes on each other.

    PI81 · 344d8e8
  32. Noteheads, clefs and rests stay black in the dark theme, which is the theme the app opens on

    Every glyph on the page now carries a token, so the stave reads in the dark theme instead of sitting black on a dark panel (design superseded: clefs and barlines are the stave's siblings).

    PI80 · 1e8209c
  33. Eighth notes and shorter are drawn with a flag each, so the page shows no beat grouping

    Eighths are beamed by the beat, in threes where the meter is compound, and left flagged where it is odd, which the page says it did.

    PI79 · 52002a1
  34. Every launch loads the engraver and its music font, even for a player who never opens the stave

    The engraver and its font are a chunk of their own, fetched when somebody opens the stave, and the entry chunk is 598 kB again (design recorded in apps/desktop/src/renderer/lib/sheet-draw.ts).

    PI78 · 6e6ea7f
  35. The view infers clefs, accidentals and rests, then shows the guess as if it were the score

    The page now says which of its readings are the app's: the clefs from hands, the key, the sharps it chose, the gaps it drew as rests, the ties it could not mark and the figures it rounded.

    PI77 · 3814f5d
  36. VexFlow draws black on white, unreadable in the dark theme and outside the app's colour system

    The engraving takes its ink, bar lines and stave lines from tokens, and reads in either theme (design superseded: handing colours in is the detour a canvas needs; a stave is SVG, which CSS colours).

    PI76 · ebd9b61
  37. The stave stands still while the piece plays, so the reader loses their place by the second bar

    The stave follows the clock: the sounding bar banded, its notes lit with the roll's own verdicts, and the page turning by system (design recorded in apps/desktop/src/renderer/lib/sheet-follow.ts).

    PI75 · c20832d
  38. Nothing on screen reaches the sheet view: the roll owns the centre of the window

    A button in the bar swaps the centre of the window between the roll and the stave, and the window reopens on whichever was last read.

    PI74 · 2bdf209
  39. The piece has no written form: the falling roll is the only way to read what comes next

    The open piece now reads as staves, one per hand, wrapped to the panel and drawn by VexFlow (design recorded in apps/desktop/src/renderer/components/SheetMusic.tsx).

    PI73 · 1c1b66e
  40. Durations are ticks, so nothing says whether a note is a dotted quarter or what fills the gap after it

    Ticks now read as a figure and its dots, and one bar of one hand as the figures and rests that tile it, each with its leftover (design recorded in packages/score-format/src/time.ts).

    PI72 · 2e2b687
  41. The design system's gallery renders in the app, showing sample controls to whoever opens the piano

    The app ends at its footer: the design system's gallery is a page for whoever builds it, mounted by its own test and nothing else.

    PI71 · e2ba877
  42. Nothing starts the installed app with a score and watches it open that score

    A headless run started with a score says which one it opened, and the association check starts the installed app to read that line.

    PI70 · 5676537
  43. The report panel's copy button hands over every piece's history from a view about one piece

    The report panel's copy hands over the piece on screen, as its neighbour forgets it; the whole history stays the footer's door.

    PI69 · 974f8b5
  44. Nothing exercises the file associations an installer writes, so a wrong macro or plist key ships unnoticed

    An installed piano is read back from the registry, Launch Services and the desktop entry, in CI and before a release (design recorded in scripts/associations.mjs).

    PI68 · 1adb249
  45. Practice history sits unvalidated in browser storage, and nothing in the app exports or deletes it

    Practice history is a validated, versioned file in the profile, carried over from browser storage, with footer doors to save a copy or delete it (design recorded in packages/ipc/src/history.ts).

    PI67 · f8d8d01
  46. Double-clicking a score in the file manager never reaches the piano: no file type is registered

    Scores are saved as .piano, one extension the app claims on every system, while MIDI is only offered under Open With (design recorded in apps/desktop/electron-builder.yml).

    PI66 · 131eae9
  47. validate_score passes a score with the left hand above the right, a chord no hand can reach, or a bar half full

    validate_score and save_score warn beside a valid result of crossed hands, unreachable chords and bars that don't add up (design recorded in packages/score-format/src/warnings.ts).

    PI65 · bfc3a0d
  48. Nothing is published to download: the plugin sends a person without the app to an empty releases page

    A v* tag builds installers on each OS into a draft release, serves the pack from Pages, checks it downloads, then publishes (design recorded in .github/workflows/release.yml).

    PI64 · ccac422
  49. A worked-out arrangement lives for one session: it is derived again at every launch and a correction has nowhere to go

    "Keep this version in the score" writes the worked-out arrangement into the open file, signed; a correction stays (design recorded in packages/score-format/src/arrangement.ts).

    PI63 · 4bd599b
  50. The roll says a note was late but not by how much, so a player who is always a hair early cannot see it

    Each right strike leaves a fading dash above the strike line, early above the middle and late below, so a bias reads as a row (design recorded in apps/desktop/src/renderer/lib/timing-lane.ts).

    PI62 · f010212
  51. The synthesised fallback and the recordings are not matched in loudness or onset, so the handover can be heard

    Measured on the pack, the synth was 7-30 dB loud and 3 ms early; fitted, it is 3 dB off on average and on the hammer (design recorded in apps/desktop/src/renderer/audio/synth-engine.ts).

    PI61 · e8bc2dd
  52. The format-ownership guard reads a split "type Score," import line as a declaration and fails the lint

    The ownership guard now matches a Score type only where a declaration follows the name, so a split import passes and a local declaration still fails.

    PI60 · 15b5821
  53. With the window hidden, Chromium may throttle the timer that wakes the scheduler, and notes then arrive late

    Minimised, the window woke 3 times in 2.5 s; unthrottled, every 25 ms wake stays inside the look-ahead, checked live (design recorded in apps/desktop/src/main/window-preferences.ts).

    PI59 · 159baf4
  54. Nothing in the app saves a score as MIDI, so the way into a DAW that the recording non-goal promises is unreachable

    Save as MIDI (Ctrl+E or the header) writes what is playing, at its level, where asked, then says what MIDI could not keep (design recorded in apps/desktop/src/main/score-export.ts).

    PI58 · 199e419
  55. A new install opens on an empty library, so there is nothing to hear and nothing to try

    Ode to Joy, the Minuet in G and the Prelude in C ship public domain with three authored arrangements each, seeded on first launch (design recorded in apps/desktop/src/main/bundled/index.ts).

    PI55 · a3abdab
  56. The sample pack cannot ship inside the installer, and there is no way to fetch it

    The app offers the pack with its size, downloads it in main as the synth plays, resumes by range, checks each sha256, installs it whole (design recorded in apps/desktop/src/main/pack-download.ts).

    PI54 · a02df6b
  57. Every setting resets on restart: device, theme, calibration and level are chosen again each time

    Device, calibration, theme, level, effects, lead time and strictness live in one validated file main owns; a bad file falls back to defaults, said (design recorded in packages/ipc/src/settings.ts).

    PI53 · 0a0e705
  58. Scores pile up in a folder with no index: nothing lists, searches or filters them

    One library package keeps an index the MCP tools and the app's Library panel both read; scores copied in by hand appear, a torn index is rebuilt (design recorded in packages/library/src/library.ts).

    PI52 · abc9ae7
  59. There is no way to open a file: a score sitting on disk cannot be loaded into the app at all

    Dialog, drop, recent list, command line and Claude Code's library id open a score through one validation; a refused file leaves the open piece (design recorded in apps/desktop/src/main/opener.ts).

    PI51 · 2ded6a7
  60. Nothing proves the premise: no single run goes from a request to a piece actually playing

    A recorded model score goes through the plugin's server into the built app, which opens and plays it; its opening is heard note by note, in CI (design recorded in apps/desktop/src/test/premise.ts).

    PI50 · 2f733d6
  61. A local port that accepts scores and plays them is an open door on the machine

    The link answers on loopback only and its token file is readable by its owner alone; ids from any caller land inside the library, never on a device name, and a record cannot start another program.

    PI49 · f3e6ae5
  62. Asking to play a piece fails whenever the app is closed, which is most of the time

    A request for music with no window open starts the installed app once, waits for it to listen and plays; a running one is brought forward (design recorded in apps/mcp-server/src/launch.ts).

    PI48 · 881fce1
  63. A rejected score comes back as a validation dump the model cannot act on

    A refusal names the path, what arrived, what was wanted and the fix where one exists, grouped and bounded, so a model repairs its own score (design recorded in packages/score-format/src/repair.ts).

    PI47 · 60c5e85
  64. A model writing a score guesses at the format and produces files that almost validate

    The plugin's piano-score skill teaches the format by a complete example that validates, and names the musical mistakes no validator catches.

    PI46 · 2dd8cfb
  65. There is no plugin: the tools only work for someone who wires an MCP server by hand

    The repository is a plugin marketplace whose plugin carries its own bundled server and four commands, and a missing app is answered with where to get it.

    PI45 · ea11474
  66. The MCP server and the app are separate processes with no way to find each other

    A tool call finds the most recently focused live window from its presence file and plays it there, and a version mismatch says which side to update.

    PI44 · b3483c4
  67. Claude Code cannot reach the app: there is no way to send a score or start playback

    The tools say what somebody asks for — validate, save, list, play, seek, practise — and the library is the only thing it touches (design recorded in apps/mcp-server/src/tools.ts).

    PI43 · 9d70688
  68. Every session starts from nothing: no record of what was played, what improved or what still fails

    Every graded attempt is kept against the piece and its notes, and the app names the bars that keep failing (design recorded in apps/desktop/src/renderer/lib/progress.ts).

    PI42 · ceecb1f
  69. A hard bar can only be practised by replaying the whole piece and waiting for it to arrive

    A passage repeats a graded pass at a time, climbing after a clean one, dropping back after a failure, hands apart (design recorded in apps/desktop/src/renderer/lib/drill.ts).

    PI41 · 97e2457
  70. Only scores hand-authored with three arrangements are playable at beginner level

    Five rules work out a simpler version of any score, and a hand-authored arrangement still wins wherever one exists (design recorded in packages/score-format/src/reduce.ts).

    PI40 · 3df13eb
  71. Beginner, intermediate and advanced are only words: nothing says what changes between them

    Each level names a tempo, the hands you take, the window and what a reduction keeps, and choosing one sets them without locking them (design recorded in apps/desktop/src/renderer/lib/levels.ts).

    PI39 · e613206
  72. The player gets no feedback: nothing on screen says which note was right, wrong or late

    The key and the note take the judgement's colour as each strike lands, and a note nobody played goes by outlined instead of vanishing.

    PI38 · acb8236
  73. Nothing says how the attempt went: wrong notes, late notes and missed notes all pass unremarked

    An attempt is graded against a timing window the learner sets, and the report names the bars to go back to, with the touch apart (design recorded in apps/desktop/src/renderer/lib/grading.ts).

    PI37 · e35e20c
  74. Playback runs away from a beginner, who cannot keep up and has nothing to practise against

    The transport holds at the next note the player owes, lights those keys, and carries on when they are struck (design recorded in apps/desktop/src/renderer/lib/practice.ts).

    PI36 · c234042
  75. Input and output latency are unknown, so a player who is in time gets graded late

    Output latency comes from the platform and input latency from a dozen strikes against a click, kept per device and shown plainly (design recorded in apps/desktop/src/renderer/lib/latency.ts).

    PI35 · 5192df8
  76. Without a MIDI keyboard there is no way to play at all, so most people cannot try the app

    Two rows of the typing keyboard play two octaves, shiftable and stated, and both inputs now sound the piano at the pitch pressed (design recorded in apps/desktop/src/renderer/lib/keys-input.ts).

    PI34 · 8c92508
  77. A MIDI keyboard plugged into the machine is invisible: nothing receives a single note

    Web MIDI is granted in main and normalised in the renderer, with hot-plug, a remembered device and a monitor showing raw events (design recorded in apps/desktop/src/renderer/lib/midi-input.ts).

    PI33 · 204cc96
  78. Colours are chosen ad hoc, so the roll is unreadable in one theme and ambiguous under colour blindness

    Note and practice colours are a measured lightness ladder in both themes, so they stay apart when hue does not help (design recorded in apps/desktop/src/renderer/lib/contrast.ts).

    PI32 · 3020e58
  79. Every part sounds and draws at once, so a learner cannot isolate one hand or one voice

    A panel mutes, solos and hides each part and each hand, and the scheduler applies it per note as it hands one over (design recorded in apps/desktop/src/renderer/lib/parts.ts).

    PI31 · d8cd9b4
  80. There are no controls: playback cannot be started, scrubbed, slowed or transposed from the screen

    One bar carries play, restart, loop, time, tempo, transpose, zoom, effects and full screen, showing what the transport did (design recorded in apps/desktop/src/renderer/components/TransportBar.tsx).

    PI30 · 5036c10
  81. The roll has no structure: nothing shows where a bar starts or which bar is playing

    Bar lines, beat lines and numbers come from the time signature map, the current bar is lit, and a drag across the field loops whole bars (design recorded in apps/desktop/src/renderer/lib/bars.ts).

    PI29 · 9a3d794
  82. Notes reach the keyboard and nothing happens: the moment of contact is invisible

    A note reaching the keyboard throws a burst from a fixed pool and flashes its key, fired by the scheduler's own strike event (design recorded in apps/desktop/src/renderer/lib/strikes.ts).

    PI28 · d74e2e4
  83. A dense passage drops frames, so the roll stutters exactly when the music gets interesting

    The roll binary-searches its visible notes and fills them by colour in one path, holding 0.2ms for the 95th frame of a dense fixture (design recorded in apps/desktop/src/renderer/lib/roll-draw.ts).

    PI27 · 802dc0e
  84. Notes do not fall: there is nothing between the score and the screen

    Notes fall onto the keyboard from the audio clock, each in its own key's column, and the keys they strike light up (design recorded in apps/desktop/src/renderer/lib/roll.ts).

    PI26 · abb03ae
  85. There is no keyboard on screen: nothing shows which key a note belongs to or which is sounding

    An 88-key keyboard lays black keys out as a real one does, sizes itself to the window, and shows a state per key from outside (design recorded in apps/desktop/src/renderer/lib/keyboard-geometry.ts).

    PI25 · 794b282
  86. Practising without a beat reference is guesswork, and playback starts with no warning

    The transport clicks every beat of the meter with the downbeat accented, and counts in a bar at practice tempo it marks unscored (design recorded in apps/desktop/src/renderer/audio/beats.ts).

    PI24 · 08e38e4
  87. There is no transport: nothing plays, pauses, seeks, loops or changes tempo

    A transport plays, pauses, seeks, loops, rescales tempo and transposes on the audio clock; each note sounds at the reported tick (design recorded in apps/desktop/src/renderer/audio/transport.ts).

    PI23 · 99fcf1b
  88. One sample per note sounds like a toy: no dynamics, no pedal resonance, no key release

    Velocity picks and crossfades recordings, half pedal damps partially, and every key coming up plays its release recording (design recorded in apps/desktop/src/renderer/audio/keyboard.ts).

    PI22 · c102637
  89. Loading hundreds of megabytes of samples before the first note makes the app feel broken

    The app reads the installed pack through a checked bridge channel, loads it behind the synthesiser and credits it in the footer (design recorded in apps/desktop/src/renderer/audio/pack-bank.ts).

    PI21 · 57e3aaf
  90. There is no piano sound: raw sample libraries are gigabytes of WAV, unusable as they ship

    npm run pack:samples builds a reproducible 16 MB Opus pack from pinned, checksummed Salamander sources, with manifest and licence (design recorded in packages/sample-pack/scripts/build-pack.mts).

    PI20 · 31c5f15
  91. Notes scheduled from timers drift audibly: timing wanders and chords stop landing together

    A look-ahead scheduler stamps each event with its exact audio time; chords share one time and late wake-ups miss no onset (design recorded in apps/desktop/src/renderer/audio/scheduler.ts).

    PI19 · df7e474
  92. There is no way to produce sound, and no seam between the engine and everything that drives it

    PianoEngine fronts a synthesised and a sampled engine that swap mid-piece, and lint keeps Web Audio inside renderer/audio (design recorded in apps/desktop/src/renderer/audio/engine.ts).

    PI18 · 9572e08
  93. The format exchanges with nothing: MIDI files cannot come in and no score can go out

    importMidi reads a MIDI file into a valid score and exportMidi writes one; both list what they dropped, and import marks what it guessed (design recorded in packages/score-format/src/midi.ts).

    PI17 · 871da3f
  94. npm run dev opens two identical windows, and an edit to main spawns another instead of replacing it

    npm run dev opens one window and an edit to main replaces that process rather than adding a second, with dev and smoke runs on separate Electron profiles.

    PI56 · d4e6ae1
  95. There are no reference scores, so nothing proves a format change kept old files readable

    Sixteen reference scores cover the hard cases, each round-trips identically after normalisation, and a checksum stops a frozen fixture being edited to suit its own migration.

    PI16 · 72ef5a2
  96. Nothing validates a score, so a malformed file fails somewhere deep inside the audio engine

    One zod schema produces the types, the runtime validator and the published JSON Schema, and a bad score is refused at the door naming the path and the value.

    PI15 · c314d0e
  97. The format has no version, so a file written today stops opening the moment a field changes

    Every score declares a version that a migration chain walks forward, and a field the format does not define is an error naming the nearest real one.

    PI14 · 190448d
  98. A score carries no title, composer, key or licence, so the library has nothing to list or filter

    Every score carries title, composer, level, tags and provenance, so a library can search and sort it and a packaging check can refuse an unlicensed one.

    PI13 · 87d0718
  99. Nothing in the format says what beginner, intermediate and advanced mean for a given piece

    Three levels resolve from one note set as filters and overrides, so correcting a wrong note in the source corrects every level without a per-level edit.

    PI12 · 218b1ad
  100. There is no way to name a passage, so nobody can ask to play the chorus or bars 12 to 20

    A passage has a stable id that resolves to a tick range, and a bar range resolves through the time signature map to exactly the same ticks.

    PI11 · 2967a4d
  101. Sustain pedal, dynamics and articulation are unrepresented, so playback sounds mechanical

    Pedal is a timed event covering everything between down and up, and dynamics multiply written velocity so an accent stays above its neighbours through a crescendo.

    PI10 · fbf6ec2
  102. A score is one flat list of notes, so the parts panel has nothing to mute, solo or colour

    Notes group into named parts that can be muted, soloed or filtered by hand, and a score declaring no parts gets one implicit part and plays.

    PI9 · d80040b
  103. There is no note model: pitch, length, dynamics, hand and fingering have nowhere to live

    A note carries pitch, spelling, tick length, velocity, voice, hand and finger through a round trip, and one voice overlapping itself is named as an error.

    PI8 · 93d6d6c
  104. There is no time model: nothing says where a note starts, and seconds break the moment tempo changes

    Positions are integer ticks converted through one tempo map, so a mid-piece tempo change lands on its bar and halving the tempo moves nothing.

    PI7 · 6b8d98f
  105. There is no installer: the app only runs in development mode, on the machine that built it

    One command produces a 107 MB Windows installer whose packaged app starts and passes all twelve self-checks, with a CI matrix for macOS and Linux beside it.

    PI6 · 651272f
  106. Nothing checks the code: with no tests, no lint and no gate, a regression only shows up while playing

    Typecheck, lint and 35 tests run from one command, split so the fast half takes under a second and the half that starts Electron or a browser is a gate.

    PI5 · c6270c5
  107. There is no design system: every screen would invent its own colour, spacing and components

    Tailwind and shadcn components render under a dark and a light theme, and every colour resolves to one token file that the React chrome and the canvas both read.

    PI4 · 4bf4c2e
  108. The renderer can reach Node and the file system, and IPC messages carry no type

    The renderer reaches nothing but a named bridge, and every IPC channel is declared once and parsed on arrival in main, proven by a self-check inside a live Electron.

    PI3 · 4284732
  109. The score format has to run inside the app and inside the MCP server, with no duplicated code

    The score format lives in one package that the desktop app and the MCP server both import, and a guard plus one typecheck prove neither keeps a second copy.

    PI2 · f8164d2
  110. There is no application: nothing opens a window, nothing compiles and there is no way to run it

    The app builds and runs: npm run dev opens an Electron window with React reloading, and tsc -b typechecks main, preload, renderer and the build scripts.

    PI1 · 04a4bcf

← Piano's product page