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:

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 (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:

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:

n=14min(ta,1ta21nda)sin(2πfnt)fn2\sum_{n=1}^{4}\min\left(\frac{t}{a},1-\frac{t-a}{2^{1-n}d-a}\right)\frac{\sin\left(2\pi fnt\right)}{fn^2}

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:

a21nda\le2^{1-n}d

Planned features