Authors:
- Nikolas Borrel (nikolasborrel@gmail.com)
- Andreas Hjortgaard Danielsen (andreashd@gmail.com)
Notus is a domain-specific language for expressing musical structures in the high-level, declarative style of functional programming. These musical structures consist of primitive notions such as notes and rests, operations to transform musical objects such as transpose and tempo-scaling, and operations to combine musical objects to form more complex ones, such as concurrent and sequential composition. From these simple roots, much richer musical ideas can easily be developed.
Notus is inspired by the Euterpea project implemented in Haskell.
In Notus, the representation of musical structures is done with the Music data type, and the semantics (or interpretation) is done by transforming Music to the Performance data type, from where it is possible to export to MIDI, CSound or other low-level representation (only MIDI export is currently available).
Let's start by looking at the pitch type, which consists of a Pitch
and the Octave
in which the pitch is defined:
public enum PitchClass {
case c, d, e, f, g, a, b,
cf, df, ef, ff, gf, af, bf,
cs, ds, es, fs, gs, aas, bs,
cff, dff, eff, fff, gff, aff, bff,
css, dss, ess, fss, gss, ass, bss
}
public typealias Octave = UInt8
public typealias Pitch = (PitchClass, Octave)
The PitchClass
data type declares all 12 semitone by 35 pitch class names. For a reference, the notion of "the concert key A" is denoted (.a, 4)
in the above design.
The representation of musical structures is done with the Music
type:
public enum Music {
case prim(Primitive)
indirect case stack(Music, Music)
indirect case seq(Music, Music)
indirect case modify(Control, Music)
}
public enum Primitive {
case note(Dur, Pitch)
case noteAttr(Dur, Pitch, [NoteAttribute])
case rest(Dur)
case none
}
public enum Control {
case tempo(Bpm)
case volume(Vol)
case transpose(RelativePitch)
case staff(Staff)
case player(PlayerName)
case interpret([PhraseAttribute])
}
public typealias PlayerName = String
A note
is defined by a duration and a pitch, where a rest
only has a duration. The duration is defining whole notes (.wn
), half notes (.hn
) and so on. To spare your fragile fingers, some syntactical sugar has been springled upon the Notus language like so: O(.qn, (.a, 4))
for a quarter note with pitch A in octave 4, and R(.wn)
for a whole note rest.
With these building blocks, we can build more complex musical structures as follows:
m1 ++ m2
is the sequencial composition ofm1
andm2
, i.e.m1
andm2
are played in sequence.m1 |=| m2
is the parallel composition ofm1
andm2
, i.e.m1
andm2
are played simultaneously.tempo(Bpm) => music
sets the tempoBpm
ofmusic
.volume(Vol) => music
sets the volumeVol
ofmusic
.transpose(RelativePitch) => music
transposesmusic
by an intervalRelativePitch
measured in semitones.staff(Staff) => music
placesmusic
in staffStaff
.interpret([PhraseAttribute]) => music
sets various attributes related to the subjective interpretation ofmusic
, such as dynamics and articulation.
A simple example using these constructors is shown below:
let dMajor: Notes = notes((.d, 4), (.fs, 4), (.a, 4))
let gMinor: Notes = notes((.d, 4), (.g, 4), (.bf, 4))
let dMajorMelody1: Music =
O(.en, (.d, 5)) ++ O(.en, (.d, 5)) ++ O(.en, (.d, 5))
++ O(.en, (.e, 5))
++ O(.en, (.fs, 5)) ++ O(.en, (.fs, 5)) ++ O(.qn, (.fs, 5))
let dMajorMelody2: Music =
O(.en, (.e, 5)) ++ O(.en, (.d, 5)) ++ O(.en, (.e, 5))
++ O(.en, (.fs, 5))
++ R(.qn) ++ O(.qn, (.d, 5))
let melody = dMajorMelody1 ++ dMajorMelody2
let harmony = dMajor.stack(.wn) ++ gMinor.stack(.hn) ++ dMajor.stack(.hn)
let music = melody |=| harmony
let upm = try UserPatchMap([(0, .ch0, .acousticGrandPiano)])
let piece = .staff(0) => .tempo(60) => music
The Notus framework is grouped into 2 parts:
- Notus contains building blocks for describing musical structures and can be used on Apple and Linux platforms.
- NotusIO contains functionality for converting from Notus to MIDI and vice versa, currently available only on the Apple platforms.
NotusDemoHost is an iOS demo project showing some Notus music examples.
The following files are a good starting point to get used to the strange, recursive world of Notus:
Notus
MusicNotation.swift: The Notus domain-specific language
Performance/
MusicToPerformance.swift: conversion from Music to Performance
UserPatchMap.swift: defines mapping between Staff, Channel and Instrument
Performance.swift: defines constructs for describing music as `Performance`, the most low-level representation available in Notus.This representation can then be transformed from `Performance` to Midi or other formats.
Players/
DefaultPlayer.swift: simple player handling (some) interpretations
FancyPlayer.swift: more advanced player handling dynamics, articulations and tempos
MusicExamples/
NotusIO/
Midi/
MusicToMidi.swift : Convert from Music to Midi
MidiToMusic.swift : Convert from Midi to Music
Playback/
MIDIPlayer : Playback Midi
The Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
Once you have your Swift package set up, adding Notus as a dependency si done by adding the package URL to your Package.swift.
Swift 5
dependencies: [
.package(url: "https://github.com/cremo-music/Notus.git", from: "1.0.0")
]
There are a number of examples showing how Notus can be used. There are two ways to run the NotusDemoHost project:
Open the workspace for NotusDemoHost called NotusDemoHost.xcworkspace
inside the NotusDemoHost
folder and simply run the project on an iOS device.
To run on iOS with proper MIDI sounds, you need a soundfont. We recommend the Airfont 340, which you can download form here. This file should be placed in the Resources
folder as usual.
Open a Terminal and go to the NotusDemoHost
folder. To run:
swift run
On MacOS this will generate a number a MIDI example files in the Documents folder on your Mac. On Linux, it will print out an example in Notus language.
- Importing triplets from MIDI files results in empty duration when the triplet contains identical pitches.
- FancyPlayer: legato interpretation should bind notes together but not overlap.
This is an open-source project, please contact us if you are interested in contributing!
This project is work in progress!
See LICENSE