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 – empty lines are ignored. The file structure used is therefore completely up to you. Linum doesn’t even take time signatures into account. One bar might take four beats while another takes five, although it is recommended to keep the timing of bars consistent. Beats are defined by the length of a quarter note.

Linum also supports the creation of chords by putting notes into brackets, for example the C major chord:

| (1 5 8)

When brackets are used, the chord must contain at least two notes.

Note length

By default, all notes in Linum are quarter notes. By placing any number of notes or chords into square brackets, their length is divided by two. For example, here’s the C major scale in eighth notes:

| [1 3 5 6 8 10 12]

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:

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:

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 have specific placement rules. They can’t be placed inside chords or as the first item in square brackets, and they must precede + and -. For example, the code below is invalid in two 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)

Command-line tool

Linum has a command line tool with the following arguments:

linum [-i input] [-o output] [-v voices] [-n notes] [-t tempo] [-f freq] [-r rate] [-p file]

It can be installed by downloading a recent release or compiled on UNIX-like systems using the commands below:

git clone https://codeberg.org/oxetene/linum
make -C linum

On Windows, install the Mingw-w64 compiler and replace the last command with the following:

make -C linum CC=x86_64-w64-mingw32-cc

The input argument in the command line tool is the name of the Linum notation file, while output is the name of the file the synthesized music will be written to. Linum also features several optional arguments which change the way the sound is generated:

In general, the only options that usually need to be changed are the input, output, voices and tempo options. The Linum synthesizer only generates WAV audio files, which are automatically played after they have been generated. WAV files can also be played without generating them first using the -p flag. This option makes Linum act like a basic audio player:

linum -p out.wav

To convert WAV files to other formats, use the following FFmpeg command:

ffmpeg -i out.wav out.mp3

For 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: linum

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>  ::= <shift> <format> <length>;
<format> ::= "(" <note> <note> <count> ")" | "N";
<count>  ::= <note> <count> | "";
<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 only uses the miniaudio library to generate and play WAV files – it is implemented in sound.c. Currently, the sound is generated using the expression below:

let s=dsin(2πft)f in\text{let }s=\frac{d\sin\left(2\pi ft\right)}{f}\text{ in} w(a,d,l,f,t)=smin(ta,1tala)1+sw\left(a,d,l,f,t\right)=\frac{s\min\left(\frac{t}{a},1-\frac{t-a}{l-a}\right)}{1+\left|s\right|} if t<l else 0\text{if }t\lt l\text{ else }0

Where a is the attack (currently hardcoded to 0.01), d is the distortion applied to the sine wave (hardcoded to 100), l is the length of the note, f is the frequency of the note and t is the time since the note started.

Planned features