Alda

A Music Programming Language

Built With Clojure

Clojure Remote 2016

Dave Yarwood

@dave_yarwood

This Talk

  • Background on Music Programming Languages
  • Basic introduction to Alda
  • Broad overview of the "moving parts"
  • Alda + Clojure = <3
  • Dave's secret plot to lure contributors

+

=

Music programming?

Sibelius: a graphical score editor

(omg we're famous!)

github.com/alda-lang/alda

DEMO TIME

Alda: Basic Anatomy

Java client

Server

Parser

REPL

Sound Engine

Clojure DSL

The Alda Client

$ alda up
[27713] Starting Alda server...
[27713] Server up ✓

Java/Clojure inter-op

Utility function for calling a Clojure function in the same project:

public static void callClojureFn(String fn, Object... args) {
  Symbol var = (Symbol)Clojure.read(fn);
  IFn require = Clojure.var("clojure.core", "require");
  require.invoke(Symbol.create(var.getNamespace()));
  ISeq argsSeq = ArraySeq.create(args);
  Clojure.var(var.getNamespace(), var.getName()).applyTo(argsSeq);
}
Util.callClojureFn("alda.server/start-server!", args);

Using it to start an Alda server:

The Alda

Sound Engine

Data Representation of a Note

{:offset 1000.0, :instrument "piano-ERbWJ", :volume 1.0,
 :track-volume 0.7874015748031497, :panning 0.5, :midi-note 64,
 :pitch 329.6275569128699, :duration 450.0}
  • Instrument ID
  • Pitch (frequency in Hz)
    • MIDI note (0-127)
  • Duration (ms)
    • Note length (quarter, eighth, etc.)
    • Tempo (beats per minute)
  • Offset (ms)
  • Volume (0.0-1.0)
  • Panning (0.0-1.0)

Data Representation of a Score

{:events
 #{{:offset 1000.0, :instrument "piano-ERbWJ", :volume 1.0,
    :track-volume 0.7874015748031497, :panning 0.5, :midi-note 64,
    :pitch 329.6275569128699, :duration 450.0}
   {:offset 500.0, :instrument "piano-ERbWJ", :volume 1.0,
    :track-volume 0.7874015748031497, :panning 0.5, :midi-note 62,
    :pitch 293.6647679174076, :duration 450.0}
   {:offset 0, :instrument "piano-ERbWJ", :volume 1.0,
    :track-volume 0.7874015748031497, :panning 0.5, :midi-note 60,
    :pitch 261.6255653005986, :duration 450.0}},
 :markers {:start 0},
 :instruments
 {"piano-ERbWJ"
  {:octave 4, :current-offset {:offset 1500.0}, :key-signature {},
   :config {:type :midi, :patch 1}, :duration 1, :volume 1.0,
   :last-offset {:offset 1000.0}, :id "piano-ERbWJ", :quantization 0.9,
   :tempo 120, :panning 0.5, :current-marker :start,
   :stock "midi-acoustic-grand-piano",
   :track-volume 0.7874015748031497}}}

softsynth.com/jsyn

JSyn

javax.sound.midi

alda.lisp

(The Alda Clojure DSL)

alda.lisp

(score
  (part {:names ["piano"]}
    (note (pitch :c) (duration (note-length 8)))
    (note (pitch :e))
    (note (pitch :g))
    (chord
      (note (pitch :d) (duration (note-length 2 {:dots 1})))
      (note (pitch :f))
      (note (pitch :a)))))
alda.parser=> (parse-input "piano: c8 e g d2./f/a")

(alda.lisp/score
  (alda.lisp/part {:names ["piano"]}
    (alda.lisp/note (alda.lisp/pitch :c)
                    (alda.lisp/duration (alda.lisp/note-length 8)))
    (alda.lisp/note (alda.lisp/pitch :e))
    (alda.lisp/note (alda.lisp/pitch :g))
    (alda.lisp/chord
      (alda.lisp/note (alda.lisp/pitch :d)
                      (alda.lisp/duration (alda.lisp/note-length 2 {:dots 1})))
      (alda.lisp/note (alda.lisp/pitch :f))
      (alda.lisp/note (alda.lisp/pitch :a)))))

alda.now

(require '[alda.lisp :refer :all])
(require '[alda.now  :refer (set-up! play!)])

(score*)
(part* "upright-bass")

; This is optional. If left out, Alda will set up the MIDI synth the first
; time you tell it to play something.
(set-up! :midi)

(play!
  (octave 2)
  (note (pitch :c) (duration (note-length 8)))
  (note (pitch :d))
  (note (pitch :e))
  (note (pitch :f))
  (note (pitch :g) (duration (note-length 4))))

Inline Clojure Code

(def REST-RATE 0.15)
(def MS-LOWER 30)
(def MS-UPPER 3000)
(def MAX-OCTAVE 8)

(defn random-note
  "Plays a random note in a random octave,
   for a random number of milliseconds.

   May randomly decide to rest, instead, 
   for a random number of milliseconds."
  []
  (let [ms (ms (rand-nth (range MS-LOWER MS-UPPER)))]
    (if (< (rand) REST-RATE)
      (pause (duration ms))
      (let [o (rand-int (inc MAX-OCTAVE))
            n [(keyword (str (rand-nth "abcdefg")))
               (rand-nth [:sharp :flat :natural])]]
       (octave o)
       (note (apply pitch n) (duration ms))))))

# continued from left

midi-electric-piano-1:
  (panning 25)
  (random-note) * 20

midi-timpani:
  (panning 50)
  (random-note) * 20

midi-celesta:
  (panning 75)
  (random-note) * 20
entropy.alda

What's next?

  • Microtonal music
  • Waveform synthesis
  • Variables
riffA = f8 f g+ a > c c d c <
riffB = b-8 b- > c+ d f f g f <
riffC = > c8 c d+ e g g a g <
riffD = f8 f g+ a > c c d < b > | c c < b- b- a a g g
riffA*4
riffB*2 riffA*2   # or riffB** riffA**
riffC riffB riffD
riffA # expands to "f8 f g+ a > c c d c <"
rockinRiff = [ 
  riffA*4
  riffB*2 riffA*2
  riffC riffB riffD
]
guitar/saxophone: 
    rockinRiff*9999999

What's next?

  • Modules
  • Export to MusicXML, MIDI, LilyPond, etc.
  • Import from MusicXML, MIDI, LilyPond, etc.
  • Export audio to file
  • Plugin system

THANKS

waffle.io/alda-lang/alda

Want to get in on this?

We could use your help!

github.com/

Try Alda

Slide graveyard

 

I over-planned and ended up with way more slides than I had time to present.

 

Stashing the slides I got rid of here.

Music Macro Language (MML)

 1 PRINT "TOORYANSE"
 2 PRINT "ARRANGED BY"
 3 PRINT " (C)2012 MOTOI KENKICHI"
 4 PRINT " THANKS ALL WIKIPEDIANS."
 10 TEMPO 4
 20 A$="E5R1E3R0D3R0E3R0E1R0D1R0-G4R1"
 30 B$="F3R0F1R0F1R0A3R0F1R0E1R0D1R0D1R0E5R0"
 40 C$="C3R0C1R0C1R0E3R0C1R0-B1R0C1R0-B1R0-A1R0-A1-B5R0"
 50 D$="E1R0E1R0E1R0E1R0E1R0E1R0D1R0E1R0E1R0E1R0D1R0-A1R0-A1R0B3R1"
 60 E$="-A1R0-B1R0C1R0D1R0E1R0F1R0E1R0F3R1A3R1B1R0A1R0F3R0E3R0E1R0E4R0"
 100 MUSIC A$+B$+B$
 110 MUSIC C$+C$+B$
 120 MUSIC C$+D$+E$

(Classical MML, est. 1978)

Music Macro Language (MML)

#TITLE My First NES Chip
#COMPOSER Nullsleep
#PROGRAMER 2003 Jeremiah Johnson

@v0 = { 10 9 8 7 6 5 4 3 2 }
@v1 = { 15 15 14 14 13 13 12 12 11 11 10 10 9 9 8 8 7 7 6 6 }
@v2 = { 15 12 10 8 6 3 2 1 0 }
@v3 = { 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 }

ABCDE t150

A l8 o4 @01 @v0
A [c d e f @v1 g4 @v0 a16 b16 >c c d e f @v1 g4 @v0 a16 b16 >c<<]2

C l4 o3 q6
C [c e g8 g8 a16 b16 >c8 c e g8 g8 a16 b16 >c8<<]4

D l4 o1 @0 @v2
D [@v2 b @v3 e @v2 b @v3 e @v2 b @v3 e @v2 b @v3 e8 @v2 b8]4

(Modern MML, est. 2001)

LilyPond

\relative c, {
  \clef "bass"
  \time 3/4
  \tempo "Andante" 4 = 120
  c2 e8 c'
  g'2.
  f4 e d
  c4 c, r
}
my_score.ly
my_score.pdf

Csound

<CsoundSynthesizer>
<CsOptions>
-odac
</CsOptions>
<CsInstruments>
sr = 44100
ksmps = 32
nchnls = 2
0dbfs  = 1

instr 1

iflg = p4
asig oscils .7, 220, 0, iflg
     outs asig, asig

endin
</CsInstruments>
<CsScore>
i 1 0 2 0
i 1 3 2 2
e
</CsScore>
</CsoundSynthesizer>

Using JSyn to schedule events

(import '[com.softsynth.shared.time TimeStamp ScheduledCommand]
        '[com.jsyn.engine SynthesisEngine])

(def engine
  (doto (SynthesisEngine.) .start))

Define events:

(defn event [f]
  (reify ScheduledCommand
    (run [_]
      (f))))

(def hello-event   (event #(println "hello")))
(def clojure-event (event #(println "clojure")))
(def remote-event  (event #(println "remote")))

Schedule events:

(let [start (.getCurrentTime engine)]
  (.scheduleCommand engine (TimeStamp. (+ start 1.0)) hello-event)
  (.scheduleCommand engine (TimeStamp. (+ start 1.5)) clojure-event)
  (.scheduleCommand engine (TimeStamp. (+ start 2.0)) remote-event))

javax.sound.midi

javax.sound.midi

javax.sound.midi

javax.sound.midi

javax.sound.midi

(import '[javax.sound.midi MidiSystem])

(def synth
  (doto (MidiSystem/getSynthesizer) .open))

(def channel
  (aget (.getChannels synth) 0))
(defn play-note [chan n]
  (.noteOn chan n 127)
  (Thread/sleep 300)
  (.noteOff chan n))

; set instrument to slap bass
(.programChange channel 36)

; slap a C major scale
(doseq [n [36 38 40 41 43 45 47 48]]
  (play-note channel n))

Initialize a synth and get the first channel:

Change patches, play notes:

Made with Slides.com