Signals & Pixels

Building Arpeggio Pt. 1: Language Design

I’ve tinkered with parsers for a few small side projects like building a language server and parsing blog post metadata, but I wanted to tackle a bigger parsing project. I’ve also been trying to learn some music theory in my spare time, so why not combine the two ideas by building a domain-specific language (DSL) for notating and generating music?

I’m (tentatively) calling it Arpeggio.

The Concept

This is going to take a few blog posts to get through, so let’s just start at the beginning with what our language is going to look like and how we’re going to use it, so we can build our parser around that.

What’s a Domain Specific Language?

If high-level languages like C and Python are a layer of abstraction on top of Assembly to make programs easier to write, DSLs are another step up the ladder of abstraction, built on top of a general-purpose programming language to make one specific type of program easier to write. Think SQL for querying databases or HTML for laying out web pages.

Arpeggio will be built on top of Python to make it easier to programmatically write music.

The Goal

I want to be careful to keep the scope reasonable, so here are the main features I want out of Arpeggio:

  1. Lay out musical notation in an intuitive, simple way. I’ll go into more detail in a future blog post, but the point is that there will be some musical constraints to both limit implementation complexity and make it easier to write simple songs.
  2. Compose songs by layering tracks using different “instruments”. Specifically I’m thinking something like retro video game chiptunes that were generated by dedicated sound chips that could synthesize different sounds with sine, square, sawtooth, and triangle waves.
  3. Use loops and patterns to quickly build up audio tracks. Other programming language standards like conditionals, functions, and classes shouldn’t be needed.

The Design

This is all likely to change once I actually start writing the parser, but I figured it was a good idea to at least come up with an initial plan that I can iterate from.

Syntax

I’m leaning towards a syntax inspired by Lua. Statements will be enclosed with keywords (e.g. track and end) instead of braces. Whitespace will be used for readability, but ignored by the parser. Comments will be started with ~, because that’s kind of fun and I won’t need the operator. Configuration like song tempo or track gain will be accomplished with special block-scoped keywords prefixed with an @ symbol.

Here’s an empty example program with the tentative syntax:

~ This is the song configuration
@bpm 120
@key c
@mode major

~ This is the first track of the song
track
    @instrument sine
    @gain -10

    ~ Music goes here
end

Program Layout

An Arpeggio program will be composed of tracks. Tracks will be composed of notes, chords, and/or loops of notes and chords.

Musical Notation

Each program in Arpeggio will be constrained to one musical key1, defined at the top of the program. The notes and chords in that key will be referenced by interval numbers2, which will make it easy to transpose a song between keys and modes without changing any notes.

Inspired by the Kepatihan notation used in Indonesian music, notes and chords will be arranged into lines, with a single interval representing one 1/16th note that can be extended into longer notes3 using a ..

For example, a C major scale of 1/8th notes could be notated as:

track
    | 1 . 2 . 3 . 4 . 5 . 6 . 7 .
end

Modifiers after notes and chords can shift their octave by 1 (+ and -) or 2 (* and _). Larger shifts are possible by setting a track-level octave modifier.

track
    | 1 _    ~ C2
    | 1 -    ~ C3
    | 1      ~ C4
    | 1 +    ~ C5
    | 1 *    ~ C6
end

With that system, we can notate a simple melody as:

~ Happy Birthday

@bpm 90
@key F_major

track
    @instrument triangle

    | 5 . 5 . 6 . . . 5 . . . 1+. . . 7 . . . . . . .
    | 5 . 5 . 6 . . . 5 . . . 2+. . . 1+. . . . . . .
    | 5 . 5 . 5+. . . 3+. . . 1+. . . 7 . . . 6 . . .
    | 4+. 4+. 3+. . . 1+. . . 2+. . . 1+. . . . . . .
end

Next Steps

With a tentative language design in mind, here’s my loose roadmap:

  1. Build the Python backend for generating music.
  2. Write the parser and finalize the language design.
  3. Put everything together to compile Arpeggio programs into songs.
  4. Maybe a language server?

In Part 2, I outline some basic music theory from a programmer’s perspective, which will be needed to build Arpeggio.


  1. I wrote a “programmer’s guide to music theory” that goes into more depth on the terminology, but the important takeaway is that Arpeggio will use a limited subset of musical notes that generally sound good together. ↩︎

  2. I originally planned on using Roman numerals to distinguish chords from notes, but I found that keeping things vertically aligned was critical to be able to visually keep track of timing, and that wasn’t possible using Roman numerals that contain different numbers of characters. Instead, tracks will use either notes or chords based on a @chords configuration flag. ↩︎

  3. Currently, I don’t have a plan to allow for notes shorter than 1/16th, but you can always double the tempo. ↩︎

#Python #Audio #Arpeggio #Programming-Languages #Music