Docs
Since Linum is a new project, the work on this documentation is in progress. Any and all fixes/contributions will be greatly appreciated.
Table of contents
For users
Representation of notes
In Linum, notes are represented by numbers. For example, the C major scale (C D E F G A B) is directly mapped to the numbers 1, 3, 5, 6, 8, 10, and 12. This can be written as follows:
| 1 3 5 6 8 10 12
Notice the bar character (|) at the start. In Linum, all sections of music must begin with a bar. That way, the structure you use for your creations is completely up to you (empty lines are ignored). Linum doesn’t even take time signatures into account. One bar might take four beats while another takes five. What is a beat? That’s also up to you, although it is often useful to structure bars so that their timing remains consistent.
Linum also supports the creation of chords by putting notes into brackets, for example the C major chord:
| (1 5 8)
Note length
By default, all notes in Linum are quarter notes. To make notes shorter, they can be placed into square brackets. For example, here’s the C major scale in eighth notes:
| [1 3 5 6 8 10 12]
Notice how multiple notes can be transformed into eighth notes at the same time. Square brackets can be repeated to create even shorter notes. In the next example, there are three eighth notes followed by four sixteenth notes:
| [1 3 5 [6 8 10 12]]
Making notes longer can only be done individually – the operators = and * serve this purpose:
=– Multiplies the length of a note by two each time it is repeated.*– Same as a dot in standard notation, it extends the note by half of its length.
Both of them can be put after a note, but = must always precede *. In the example below, the first bar contains a half note, the second bar a dotted half note, the third bar a whole note and the fourth bar a dotted whole note:
| 1=
| 1=*
| 1==
| 1==*
All of the above, of course, also applies to chords. If a length operator is used inside square brackets, it ignores them. In the example below there are two chords with the length of a half note:
| [(1 5 8)= 3 5 6 8 10] (5 8 12)=
Octaves
There are four operators that allow changing the octave in a bar:
>– Moves the current octave up.<– Moves the current octave down.+– Shifts the octave of the note immediately following it up.-– Shifts the octave of the note immediately following it down.
In short, < and > have a “memory”, while + and - only change the note or chord that follows them. This provides a wide range of possible configurations that increase the readability of the code. For example the C major scale across two octaves starting from C4 can be written like this:
| 1 3 5 6 8 10 12 +1 +3 +5 +6 +8 +10 +12
But also like this:
| 1 3 5 6 8 10 12 > 1 3 5 6 8 10 12
On every new bar, all octave moves are reset. This means that the C in the second bar of the following code is C4:
| >>> 1
| 1
The > and < operators (octave moves) have specific placement rules. They can’t be placed inside chords or as the first item in square brackets, and they must precede + and - (octave shifts). These octave shifts also have a rule – they can only be put in front of notes, not chords.
For example, the code below is invalid in three places:
| [ < 1 3 5 6 8 10] +(5 8 > 1)
In contrast, this theoretically equivalent code is correct:
| < [1 3 5 6 8 10] > (5 8 +1)
Synthesizer
Linum has a command line tool with the following arguments:
usage: linum -i input -o output [-v voices] [-n notes] [-t tempo] [-f freq] [-r rate]
It can be installed by downloading a recent release or compiled using the commands below:
$ git clone https://codeberg.org/oxetene/linum
$ make -C linum
The input argument is the name of the Linum notation file, while output is the name of the file the synthesized music will be written to. These arguments are mandatory, the command won’t run without setting them. Linum also features several optional arguments which change the way the sound is generated:
voices– Changes the amount of voices contained in the input file. For example with two voices, odd line numbers belong to the first voice and even line numbers belong to the second voice. Note that only lines that begin with a bar increase the line number count. 1 voice by default, max. 8.notes– Allows the creation of microtonal music by changing the amount of notes in an octave. 12 by default, max. 256.tempo– Self-explanatory, changes the tempo. 120 bpm by default, max. 512.freq– Changes the frequency of the note A4 in a 12-tone system. If thenotesoption was changed, this option changes the frequency of the note two steps below the highest note in the chosen system – for example in a 7-tone system, that note would be 5 in the default octave. 440 Hz by default, max. 1024.rate– Changes the sample rate of the generated sound. 44100 Hz by default, max. 131072.
In general, the only options that usually need to be changed are the input, output, voices and tempo options. The Linum synthesizer only generates raw audio files. To convert them to other formats, it is recommended to use FFmpeg (substitute rate and output with the same options Linum was ran with):
ffmpeg -f s16le -ar rate -ac 1 -i output out.mp3
For direct examples of music written in Linum, head out to the repository!
Syntax and highlighting
Any character that doesn’t belong to the Linum notation can be used as a comment. The only syntax highlighting currently supported is for the micro text editor:
filetype: notum
detect:
filename: "\\.n$"
rules:
- comment: "[^\\d]"
- statement: "\\||\\(|\\)|\\[|\\]"
- symbol: "<|>|\\+|-|=|\\*"
For developers
Language grammar and parsing
The language is implemented in the lexer.h and parse.h files. All the rules explained in the user section are defined by the following formal grammar (N is a number read by the lexer):
<start> ::= "|" <line>;
<line> ::= <octave> <repeat> | "";
<group> ::= "[" <repeat> "]" | <chord>;
<repeat> ::= <group> <line>;
<chord> ::= <format> <length>;
<format> ::= "(" <count> ")" | <note>;
<count> ::= <note> <count> | <note>;
<note> ::= <shift> "N";
<length> ::= "=" <length> | "*" | "";
<octave> ::= <add> | <sub>;
<add> ::= ">" <add> | "";
<sub> ::= "<" <sub> | "";
<shift> ::= <inc> | <dec>;
<inc> ::= "+" <inc> | "";
<dec> ::= "-" <dec> | "";
During development, the website BNFGen was found to be incredibly useful, and the BNF grammar above is compatible with it. Many thanks to Daniil Baturin for creating this tool.
Similarly to other small compilers (let’s say Linum compiles music), free is never called:
Last but not least, chibicc allocates memory using calloc but never calls free. Allocated heap memory is not freed until the process exits. I’m sure that this memory management policy (or lack thereof) looks very odd, but it makes sense for short-lived programs such as compilers. DMD, a compiler for the D programming language, uses the same memory management scheme for the same reason, for example.
DMD does memory allocation in a bit of a sneaky way. Since compilers are short-lived programs, and speed is of the essence, DMD just mallocs away, and never frees.
Generating sound
The synthesizer doesn’t use any dependencies outside of the standard library and it is implemented in sound.c. Currently, the sound is generated using the expression below:
Where a is the attack (currently hardcoded to 0.01), d is the duration of the note and t is the time since the note started. The following condition must also apply:
Planned features
- Tuplets.
- Plugins for custom instruments.
- JavaScript implementation of Linum for use on websites.
- The ability to use multiple instruments when generating audio files.
- Generating PDFs of a more readable version of the Linum notation for use in performances.