Hey,
A new quark, an attempt at music notation in SC, not organized around a notation backend. Has been in the lab for the past few months alongside metasonic-score, a sibling built around the same format-neutral premise.
It is now useful enough that I would like feedback on it: GitHub - smoge/Rastrum: Music notation for SuperCollider: a format-neutral score model with LilyPond, MusicXML and JSON writers. · GitHub
Start here to get started with the public API: tutorial.scd is a tested, linear walk through the API. It builds a score, writes the same musical structure in several forms, and plays it. Its examples are meant to be evaluated in sequence.
For reading the implementation, developers can start here: Classes/Duration.sc, whose header lists the source files by their design layer.
Why a “format-neutral” model?
I wrote LilyCollider and the FOMUS quark, and presented both at the SuperCollider Symposium a while ago. I’ve been working with music notation, but nothing I felt was generally useful for other people. This one feels a bit different, with a relevant distinction this time: the old ones delegated the central notation model to an external system. That is the assumption I have since revised: a backend, given the chance, will mistake itself for the music.
When a library is built around one output format, that format’s vocabulary becomes the library’s internal model. Durations turn into strings, tuplets into backend calls, and typographic instructions into score objects. By the time a second backend is wanted, the model usually has to be rebuilt rather than extended. That’s a mistake some projects got into.
Rastrum had LilyPond and MusicXML writers from the first commit (plus a custom JSON format to communicate with my other projects). If a musical concept can be expressed properly by only one of them, the mismatch appears while the model is still cheap to revise.
More generally, Rastrum distinguishes between score facts supplied by the composer, notation structure derived from those facts, and backend-specific interpretation. Preparation and validation happen before a writer or playback projection receives the score, so a backend should not have to guess what the model meant.
ScoreJSON is the contract between the two sibling projects. It is a versioned interchange format local to Rastrum and metasonic-score, and I am not proposing it as a general notation standard. Today Rastrum reads and writes ScoreJSON. metasonic-score reads and checks it, with the Haskell-to-Rastrum path still being built. This lets the two projects preserve their own types and operations rather than requiring either one to adopt the other’s internal model. At the same time, since metasonic-score is more mature and has better tooling, this bridge is also useful to keep Rastrum safer even with dynamic typing and minimal tooling.
Rastrum represents a score as a tree of musical objects: scores, staves, measures, voices, tuplets, notes, chords, and rests. That tree can be written as LilyPond, MusicXML, or ScoreJSON (a format specific to this project and metasonic-score), also projected into SuperCollider events and patterns for playback. Note: none of those outputs is the model’s native form. The score tree contains no LilyPond, MusicXML, JSON, or playback syntax. That’s the design principle.
Installation instructions and a compact reference are in README. Keeping the executable examples in the repository, where they are tested, rather than duplicating them in this post. Go there to see them, so there is no risk of stale code getting forgotten here in the forum posts.
Time, pitch, intervals, and meter: Durations are exact rationals, which is why Rastrum depends on the Rational quark. Pitches retain their letter, accidental, and octave rather than collapsing into MIDI numbers. Quarter-tone alterations and interval qualities use the same machinery rather than a separate microtonal pitch type. Pitch and interval arithmetic follows an affine-space model*. Pitches are points and intervals are vectors. Subtracting one pitch from another produces the signed spelled interval between them. Adding an interval to a pitch produces another pitch, and intervals can be combined with other intervals. Two pitches cannot be added because pitch space has no privileged origin. Transposition therefore preserves letter motion as well as chromatic displacement. A Meter also carries a metric hierarchy, not only the pair printed as a time signature. Rastrum uses that hierarchy when deriving notation such as beam groups and readable tied note heads. The playback events represent sounding onsets rather than a literal traversal of the written note heads.
Rastrum also uses doctests. These examples are executable rather than decorative. The doctest runner evaluates each expression and compares the result with the documented answer. The verification tooling (a simple sclang script, nothing more than that) is included in the repository: tools/doctest.scd.
That bit can be useful for other projects. It was useful for me, at least.