Musical Language Notation


Musical Notation Language is a compiler / interpreter that compiles sheet music instead of written English instructions. The files which are compiled are Musical Notation (MN), with the “.mn” file extension. Another valid extension should be “.mnl” to help avoid conflicts.

Design

Aside from being a functional programming language, MN has some additional design goals that hope to act as constraints on a programmer and composer.

  1. Writing MN must feel like programming with musical notation.
  2. MN source files must be playable by an average musician.
  3. A playing musician or band should theoretically be able to follow along with the operations being played, even if the complexity is practically impossible to follow.
  4. Control flow should be handled using music notation’s predefined loops and jumps, accepting that the minimal modifications made to musical notation may require some explanation to a musician on how to read.
  5. Use only standard musical notation, with the only expansions on grammar being the minimal additions that allow for Turing-complete control flow.
  6. Keep expressive elements free.

Free Elements of Musical Expression

In line with the design goals, many more expressive elements that are usually solely in control of the player and the sheet music is just a suggestion or artist intent remain free from any computational meaning.

ElementWhy
DynamicsInterpretation of intensity or emotion within the piece
OctavesInstrument range and playability
Key & RootExact tones shouldn’t affect logic
ClefsReadability, not computation
InstrumentationPieces should be composed for any or all instruments
Breath marks and pausesPerformative expression, and a player’s ability to breathe
Visual style (noteheads, etc)Aesthetic, possibly usable for commenting.

Computation

For MN to be Turing complete, it must support the following:

  1. Memory
  2. Conditional jumps
  3. Loops

Memory

Memory is conceptual, not visible on the score. Musical elements refer to or modify stored values like variables.

Flow control

Critically, flow control is done using musical structures.

Conditional Branches The primary addition to MN grammar is the Questa Volta. These are akin to the prima volta, seconda volta, etc. Instead of a single number, the Questa Volta has a condition which must be met to play the section of code under the volta.

Questa volta comparisons are only the standard comparison operators that you should be familiar with. They only trivially compare basic variable types. If you want a more complex comparison, you need to define a comparison function that returns an integer that can then be checked by a questa volta.

Jumps Jumps in code are done with the recognisable Italian phrases deriving from the common “Da capo al coda”, with any mix of “capo”, “fine”, “coda”, and “segno”. The text “Da segno” goes to the first preceding segno, whereas “da capo” goes to the first succeeding segno. Doppia variants of the segno and coda can be used appropriately, and marks such as the coda and segno can be labelled for completion.

Loops Loops are canonically done with repeat bars (|: … :|) and the label “x∞”. This label is called “Giocare x Infinitum” for Italian consistency. These marks intend for the musician to play between the repeat bars an infinite number of times, or until a condition in a questa volta is met and within that questa volta there is a jump out of the loop. Alternative forms are available, but this is the preferred loop terminology because of its musical minimalism and readability.

Motifs

Motifs are the musical notation equivalent of a word. Motifs are either commands, variables, or raw data. There are three ways to separate motifs. Here they are listed by hierarchy:

  • Slur marks as phrases define motifs, possibly across barlines but not across pieces or through flow control. Slur marks not as phrases can still be used.
  • Bar lines separate motifs
  • Accents define the start of a new motif. Specifically: accent, marcato, or violin bow directions The usage of accents suggests a divergence from leaving expressive marks free, but it is a necessary limitation of the language and the best way I could come up with to separate motifs between bar lines.

Motifs contain both rhythmic and melodic data. Rhythmic is when a note is playing, and melodic is the note’s relation to the root, defined in the key and mode.

Command motifs

These are operators, and often expect following variables, otherwise they operate on the value stored in the global register. They are only melodic, rhythmic data is ignored for commands.

Variables

Any motif that is not identified as a command or raw data are considered variables. Variable declaration is implied, and unused variables are ignored and considered as musical additions. Variable types are also implied, there is no type safety. Variables are only melodic, so rhythmic variations are also possible. Variables can also be bound to stave text so that they can be referenced in questa voltas.

Raw data

Raw data can be defined either in rhythmic binary or melodic binary. Staccato at the start of a motif declares melodic binary, and the motif may not be an entire byte. Staccatisimo defines rhythmic binary. For rhythmic binary, the shortest note division within the motif is considered the baseline, and then when a note is playing the bit is set. when there is a rest, the bit is not set. Variables are automatically assumed to be unsigned, so creating a negative number requires negating raw data.

Compdef

The compdef composition defines what motifs the compiler expects. It contains the command set motif, type motifs, and the motifs of the global input output streams. You can change compdef for any set of compositions by either including your own complete compdef, or having a compdef file where each motif is labelled with system text. If a command definition motif only includes rests, it is effectively deleted.

All of the components mentioned here are in order as they appear in compdef.

Command Set

In these command descriptions, there is a global variable that is called the register. If omitted from a command call, the x parameter is assumed to be this global register.

CommandDescription
TYPE x type_motifDeclares the variable x of type type_motif.
FREE xResets a motif, makes it so that the motif doesn’t exist anymore.
MOV x yAssigns source to destination, moving the resource of y to x.
SET x yAssigns source to destination, keeping y in tact if it is a variable. If y is a temporary literal, it’s essentially a set.
CALL [params]For system text function calling, requires accompanying system text “Play [piece]” and passes the following variables into it.
ADD x yadds y to x
SUB x ySubtracts y from x
MUL x yAdds x to itself y times
DIV x yDivides x by y
MOD x yThe modulo operation
INC xIncrements the value of x by one
DEC xDecrements x by one
NEG xMultiplies x by -1
ABS xMakes the value of x absolute; removes the sign
AND x yBitwise and, stores the value in x
OR x yBitwise or, stores the value in x
XOR x yBitwise xor, stores the value in x
NOT xBitwise not on the variable x
SHL x yBit shifts x leftwards y times
SHR x yBit shifts x rightwards y times
These are not compiler commands and are more akin to overridable functions in c++ with defined behaviours depending on the expected type of a variable.

Input Output

The input and output motifs are also defined in compdef, but they are not commands. They are global input and output stream variables that can be set to or got from in order to. Their labels are “Input” and “Output”.

Type definitions

There is no type safety in MN, and type definitions are hints as to how the variable should be interpreted in cases where it is ambiguous.

Typedefinition
u_intThe default declared type of any variable.
intA signed integer
charA unicode character
exponentThe exponent component of a complete float
floata float variable

FLOAT command

Floats are handled uniquely. If you TYPE x float, you’re defining x as a zero float that you can assign other floats to, but to actually define a float you need to use the following command, ordered here after the type definitions for some reason.

CommandDescription
FLOAT x yexpects an exponent in y and “loads” it onto an expected float or u_int x, stores in x and makes x of float type.

Functions

A program written in MNL will probably have several function calls, especially to improve the clarity of the musical piece and to reduce the number of loops and jumps in a single piece.

MNL operates on a strict one-function one-piece structure. We can define several pieces inside of a single file using a terminating bar line on the end of each piece.

The first bar of any piece defines a reference motif that can be used to call a piece musically. System text over this also defines a name reference for CALL calling. CALL calling is optional and ideally should be used as a shorthand.

Following the first bars are the definitions of the parameters, either as only variable motifs or TYPE x typedef pairs. Each piece maintains it’s own global register, and the first parameter of the function call is automatically loaded into the register.

Parameters are always passed by reference so the function can actually “return” values.

A typical function call might look like:

CALL (system text: “play func1”) motif1

or

func_motif (system text: “play func1”) motif1

which are identical, but the former uses the shorthand call function instead of the func_motif. In the latter, the system text is optional and is just a direction to the performer to actually play the piece, which the composer might not want them to do if it’s a trivial or otherwise boring function.

The intention here is that CALL is to be used when players of the piece actually need to play that piece, and the function motif can be used whenever a player could understand what that function does because it is relatively simple.

Then, the function itself might look like:

(the clef and key) func_motif (system text: func1) [commands]

The function definition itself doesn’t need a parameter because the function’s register is a reference to the main program’s motif1. The func can then perform some complex operation on that motif’s value.

If you call a function without parameters, the function’s register is a reference to main’s register, which might be fine but things might get confusing.

Compilation

MNL compiles with a proprietary MNLFormat musical notation format which lays out a written piece in a way that’s easily interpreted by a compiler. As additional tools, a converter between MNLFormat and MusicXML is to be made which works both ways and at least preserves everything I’ve accounted for in MNLFormat.

You can theoretically write a program in MNLFormat or MusicXML, but this would be so much more unwieldy than just using a program to do the sheet music for you, like Sibelius or MuseScore. It’s out of scope right now, but I would write an extension for MuseScore to automatically export to and load from MNLFormat.

I use MNLFormat because MusicXML is weird and confusing and it would be better to have a clearer proprietary system. But, if a good case for not making MNLFormat and going from MusicXML directly to a token stream makes sense then I’d be happy to abandon it.

Tokenisation model

The compiler tokenises the MN file to make it even more understandable to a computer. Here are the tokens it uses:

TokenDescription
Structural tokens:
STAFFSeparates parallel music streams.
CLEFUsed to define where notes are on a staff, ignored by the compiler
KEYThe key of the piece, defines what intervals are minor or major
MODEAlongside Key, it defines the root note of the piece
TIMEThe time signature which defines how many beats we have in a bar
BARLINESeparates bars, usually implied. Usually separates motifs
MEASUREMeasures are the spaces in between bar lines, Contains one or more motifs, but motifs can also span several measures
Musical tokens
NOTEA played note with a letter, an optional accidental (together with the letter forming a tone), a duration in beats, and a voice which it belongs to
CHORDSeveral notes played at the same time that share a duration and a voice, but have different tones
RESTA lack of playing a note, with a duration and a voice
MOTIFA collection of notes, chords, and rests
Semantic tokens
COMMANDA standard command, as defined in compdef
FUNCA function motif
VARA variable motif
RAWA raw data motif
Meta tokens
SYSTEM_TEXTText that overlays an entire system, often interpreted as function definitions or calls
STAFF_TEXTText that overlays a single staff, often interpreted as variable references.
QUESTA_VOLTAA volta with a conditional in it that defines when the part in its range is played
JUMPAlongside additional details, categorises a “go to” or “al” system text
MARKWhere the jump be jumping to
SLURA standard slur, not a motif-defining phrase
PHRASEA slur as a phrase, defines motifs possibly across bar lines.
ACCENTAn accent marker explicitly defining a new motif

Compilation pipeline

  1. Convert MusicXML -> MNLFormat
  2. Parse MNLFormat -> token stream
  3. Group musical tokens into motifs
  4. Map motives to commands, variables, and raw data
  5. Interpret markings for flow control
  6. Generate executable code

I’d probably write everything in C++ because I’m familiar with it and I know it’ll let me do the weird type system I want. I might write the MNLFormat converter in Rust just to learn that language.

MNLFormat

MNLFormat, or just MNLF, or maybe YAMN (YAML for Musical Notation) if we want to be canonical, is just a nicer way of laying out sheet music as data that’s easier to read than a MusicXML file, but necessarily less extensible.

![[example.yamn]]