Slides - Workshop on Functional Art, Music, Modeling and Design

Transcription

Slides - Workshop on Functional Art, Music, Modeling and Design
Exploring Melody Space in a
Live Context Using Declarative
Functional Programming
FARM Workshop at ICFP 2014, Gothenburg
Thomas G. Kristensen, uSwitch Ltd, London
Composer is a simple, responsive and extensible system
utilising logic programming to allow novices to explore
and learn music rules
Background
Background
offline
online
Background
programmers
offline
online
musicians
Background
programmers
offline
online
musicians
Background
programmers
offline
online
Shasheela
musicians
Background
offline
online
programmers
musicians
Shasheela
FHarm
Background
programmers
musicians
offline
Shasheela
FHarm
online
Principled
approach
Background
programmers
musicians
offline
Shasheela
FHarm
online
Principled
approach
Graphical
score grammars
Background
programmers
musicians
offline
Shasheela
FHarm
online
Principled
approach
Graphical
score grammars
•Anders: Composing music by composing rules (Ph.D. thesis)
•Koops, Magalhãe and de Haas: A functional approach to automatic melody harmonisation
•Aaron, Blackwell, Hoadley and Regan: A principled approach to developing new languages for live coding
•Stead, Blackwell and Aarong: Graphic score grammars for end-users
Melody rules
C♯ D♯
• Tonic note
• Mode
• Cadence
F♯
G♯ A♯
C♯ D♯
3
C
1
2
D
E
F
7
4
5
6
G
A
B
8
C
D
E
Melody rules
C♯ D♯
• Tonic note
• Mode
• Cadence
F♯
G♯ A♯
C♯ D♯
3
C
1
2
D
E
F
7
4
5
6
G
A
B
8
C
D
E
Architecture
JVM
Instrument state
loop
OSC listener
Composer loop
OSC server
Overtone loop
SuperCollider
Architecture
JVM
Instrument state
loop
I
OSC listener
OSC server
Composer loop
II
a.
Overtone loop
IV
SuperCollider
III
V
Logic programming
(scaleo next-note scale-rest notes-rest)))
([note [0 . scale-rest] notes]
(fresh [next-note]
(semitone note next-note)
(scaleo next-note scale-rest notes))))
Logic programming
With this rule, it is possible for us to write a logic program to
generate C-major.
(run* [notes]
(scaleo :C3 major-scale notes)
(counto notes 8))
;; => ([:C3 :D3 :E3 :F3 :G3 :A3 :B3 :C4])
As there is no orientation on our computation, it is also possible for
us to go the other way, and re-create the binary pattern of a scale
given a concrete instance of a scale
(run* [tonic-note pattern]
(scaleo tonic-note pattern
[:C3 :D3 :E3 :F3 :G3 :A3 :B3 :C4]))
;; => ([:C3 (1 0 1 0 1 1 0 1 0 1 0 1 1 . _0)])
As a melody in C OMPOSER is just a permutation of a scale, with
the added restriction that the first and the last note must be the first
and last note of the scale, we can start generating melodies that
conform to the scale rule.
(run 3 [m1 m2 m3 m4 m5 m6 m7 m8]
(scaleo next-note
scale-rest
notes-rest)))
As there is no orientation
on our computation,
it is also
possible for
. scale-rest]
notes]
us([note
to go the[0other
way, and re-create
the binary pattern of a scale
[next-note]
given (fresh
a concrete
instance of a scale
(semitone note next-note)
(run* [tonic-note
pattern]
(scaleo
next-note scale-rest notes))))
Logic programming
(scaleo tonic-note pattern
:E3 :F3
:A3write
:B3 a
:C4]))
With this rule, [:C3
it is :D3
possible
for :G3
us to
logic
;; => ([:C3
(1 0 1 0 1 1 0 1 0 1 0 1 1 . _0)])
generate
C-major.
program to
As a melody
in C OMPOSER is just a permutation of a scale, with
(run*
[notes]
the added
restriction
the first and the
last note must be the first
(scaleo
:C3that
major-scale
notes)
and last(counto
note of the
scale,
we can start generating melodies that
notes
8))
conform
to the :D3
scale:E3
rule.:F3 :G3 :A3 :B3 :C4])
;;
=> ([:C3
(run 3 [m1 m2 m3 m4 m5 m6 m7 m8]
As there
is no[n1
orientation
ourn6computation,
it is also possible for
(fresh
n2 n3 n4onn5
n7 n8]
us to go the other
way,
and
re-create the binary pattern of a scale
(scaleo
:C3
major-scale
given a concrete instance
scale
[n1 of
n2a n3
n4 n5 n6 n7 n8])
(permuteo [m1 m2 m3 m4 m5 m6 m7 m8]
(run* [tonic-note pattern]
[n1 n2 n3 n4 n5 n6 n7 n8])
(scaleo
pattern
(==tonic-note
m1 :C3)
(==[:C3
m8 :C4)))
:D3 :E3 :F3 :G3 :A3 :B3 :C4]))
;; => ([:C3
:E31 :F3
;;
([:C3 :D3
(1 0
0 1 :G3
1 0:A3
1 0:B3
1 0:C4]
1 1 . _0)])
;;
[:C3 :E3 :D3 :F3 :G3 :A3 :B3 :C4]
;; a melody
[:C3 :F3
:E3 :G3is:A3
:C4])
As
in C:D3
OMPOSER
just:B3
a permutation
of a scale,
with
the
added restriction that the first and the last note must be the first
1 A few basic macros and functions from core.logic are used in the code
and
last note of the scale, we can start generating melodies that
examples presented. defne defines a goal function using pattern matching;
conform
to all
thesolutions
scale rule.
run* returns
to a system, possibly never halting; run returns
no more than a given number of solutions to a system; fresh introduces
(run
[m1 m2
m3variable.
m4 m5 For
m6a m7
m8]
a new 3
unbound
logic
more
detailed overview of core.logic
the di
was th
expre
melod
To
extens
as the
for th
betwe
progra
often
neede
It
interfa
an iOS
intera
for no
to the
send O
Af
chose
caden
in a l
interfa
with a
of the
chang
key th
The
definition
can
be done
recursively
over itthe
binary
sequence
(scaleo
next-note
scale-rest
notes-rest)))
As there
is no orientation
on our
computation,
is
also
possible
for
1
defining
scale,
as
demonstrated
here
. scale-rest]
notes]
us([note
to gothe
the[0
other
way,
and re-create
the:binary pattern of a scale
(fresh
[next-note]
given
a
concrete
instance of ascale
scale notes]
(defne scaleo [tonic-not
Logic programming
(semitone note next-note)
([note
[1 . scale-rest]
[note . ()]])
(run*
[tonic-note
pattern]
(scaleo
next-note scale-rest notes))))
([note
[1 . scale-rest]
[note . notes-rest]]
(scaleo
tonic-note pattern
[next-note]
[:C3
:E3 :F3
:A3write
:B3 a
:C4]))
With(fresh
this rule,
it is :D3
possible
for :G3
us to
logic program to
(semitone
;; => ([:C3
(1 0 1 0 1note
1 0 next-note)
1 0 1 0 1 1 . _0)])
generate
C-major.
(scaleo next-note scale-rest notes-rest)))
([note
[0 .in scale-rest]
As
a melody
C OMPOSER isnotes]
just a permutation of a scale, with
(run*
[notes]
(fresh
[next-note]
the added
restriction
the first and the
last note must be the first
(scaleo
:C3that
major-scale
notes)
note
next-note)
and last(counto
note (semitone
of the
scale,
we can
start generating melodies that
notes
8))
(scaleo
next-note scale-rest notes))))
conform
to the
scale:E3
rule.
;;
=> ([:C3
:D3
:F3 :G3 :A3 :B3 :C4])
With
rule,
possible
us to write a logic program to
(run this
3 [m1
m2 itm3is m4
m5 m6 for
m7 m8]
As there
is no[n1
orientation
ourn6computation,
it is also possible for
(fresh
n2 n3 n4onn5
n7 n8]
generate
C-major.
us to go the other
way,
and
re-create the binary pattern of a scale
(scaleo
:C3
major-scale
(run*
given [notes]
a concrete instance
scale
[n1 of
n2a n3
n4 n5 n6 n7 n8])
(scaleo(permuteo
:C3 major-scale
[m1 m2 m3notes)
m4 m5 m6 m7 m8]
(run*(counto
[tonic-note
notes pattern]
8))[n1 n2 n3 n4 n5 n6 n7 n8])
(scaleo
;; => ([:C3
:D3
:E3
:F3 :G3pattern
:A3 :B3 :C4])
(==tonic-note
m1 :C3)
(==[:C3
m8 :C4)))
:D3 :E3 :F3 :G3 :A3 :B3 :C4]))
As
is no orientation
our
possible for
;;there
:D3
:E31 :F3
:A3
;;
=> ([:C3
([:C3
(1 0
0on
1 :G3
1 0computation,
1 0:B3
1 0:C4]
1 it1 is. also
_0)])
[:C3other
:E3 way,
:D3 :F3
:A3 :B3
:C4] pattern of a scale
us;;to go the
and :G3
re-create
the binary
;; a amelody
[:C3 :F3
:E3
:C4])
As
ininstance
C:D3
OMPOSER
is:A3
just:B3
a permutation
of a scale, with
given
concrete
of a:G3
scale
the
added restriction that the first and the last note must be the first
1
(run*
pattern]
A few[tonic-note
basic macros and
functions from core.logic are used in the code
and
last
note
of
the
scale,
wea goal
canfunction
start generating
melodies that
(scaleo
tonic-note
pattern
examples
presented.
defne defines
using pattern matching;
conform
to all
thesolutions
scale :D3
rule.
run* returns
to a:E3
system,
never
halting;
run returns
[:C3
:F3possibly
:G3 :A3
:B3
:C4]))
more
than a(1
given
fresh introduces
;;no =>
([:C3
0 1number
0 1 1of0solutions
1 0 1 to
0 a1 system;
1 . _0)])
(run
3
[m1
m2
m3
m4
m5
m6
m7
m8]
a new unbound logic variable. For a more detailed overview of core.logic
the diG
was
ingth
expre
ram
melod
in a
ofToc
extens
char
as the
for th
3.2
betwe
progra
Muc
often
perf
neede
prog
It
cert
interfa
on b
antoiOS
a
intera
by a
forsect
no
to the
up b
send O
them
Af
the
chose
was
caden
inexpr
al
mel
interfa
with T
a
ofexte
the
as t
chang
fortht
key
Logic programming
(ns composer.composer
(:refer-clojure :exclude [==])
(:require [clojure.core.async :refer [go >! <!]]
[clojure.core.logic :refer :all]
[clojure.core.logic.pldb :refer :all]))
(defn scale-from-tones [tone-types]
(take 25
(->> tone-types
(map {:semitone [1]
:tone [0 1]
:minor-third [0 0 1]})
flatten
butlast
(cons 1)
cycle)))
(def major-scale
(scale-from-tones
[:tone :tone :semitone
(def harmonic-minor-scale
(scale-from-tones
[:tone :semitone :tone
(def natural-minor-scale
(scale-from-tones
[:tone :semitone :tone
(def locrian-mode
(scale-from-tones
[:semitone :tone :tone
(def mixolydian-mode
(scale-from-tones
[:tone :tone :semitone
(def scale-modes
[[:major-scale
[:harmonic-minor-scale
[:natural-minor-scale
[:locrian-mode
[:mixolydian-mode
(defne scaleo [base-note scale notes]
([note [1 . scale-rest] [note . ()]])
([note [1 . scale-rest] [note . notes-rest]]
(fresh [next-note]
(semitone note next-note)
(scaleo next-note scale-rest notes-rest)))
([note [0 . scale-rest] notes]
(fresh [next-note]
(semitone note next-note)
(scaleo next-note scale-rest notes))))
(defn key-restriction
[instrument-state s1]
(if-let [key (:key instrument-state)]
(all (== key s1))
succeed))
(defn compositions
[instrument-state & [n]]
(with-db
semitone-facts
(if n
(run n [melody2]
(logic-program instrument-state melody2))
(run* [melody2]
(logic-program instrument-state melody2)))))
(defn- random-composition
[instrument-state]
(rand-nth
(or (seq (compositions instrument-state 1024))
[[]])))
;; Loop
:tone :tone :tone :semitone]))
:tone :semitone :minor-third :semitone]))
:tone :semitone :tone :tone]))
:semitone :tone :tone :tone]))
:tone :tone :semitone :tone]))
major-scale]
harmonic-minor-scale]
natural-minor-scale]
locrian-mode]
mixolydian-mode]])
(db-rel semitone note-1 note-2)
(def keys-from-c
[:C3 :C#3 :D3 :D#3 :E3 :F3 :F#3 :G3 :G#3 :A3 :A#3 :B3
:C4 :C#4 :D4 :D#4 :E4 :F4 :F#4 :G4 :G#4 :A4 :A#4 :B4
:C5])
(def semitone-facts
(reduce
(fn [db [note-1 note-2]]
(db-fact db semitone note-1 note-2))
empty-db
(partition 2 1 keys-from-c)))
(defn scale-restriction
[instrument-state scale-type]
(if (:scale instrument-state)
(all (membero [(:scale instrument-state) scale-type] scale-modes))
succeed))
(defn cadence-restriction
[instrument-state m7 s2 s4 s5]
(case (:cadence instrument-state)
:perfect
(all (== m7 s5))
:plagal
(all (== m7 s4))
:just-nice (all (== m7 s2))
nil
succeed))
(defn- logic-program
[instrument-state melody2]
(fresh [melody
m1 m2 m3 m4 m5 m6 m7 m8
scale
s1 s2 s3 s4 s5 s6 s7 s8
base-note scale-type]
(key-restriction instrument-state s1)
(== melody [m1 m2 m3 m4 m5 m6 m7 m8])
(== scale [s1 s2 s3 s4 s5 s6 s7 s8])
(== m1 s1)
(== m8 s8)
(cadence-restriction instrument-state m7 s2 s4 s5)
(== melody2 [m1 m2 m3 m4 m5 m6 m7 m1])
(scale-restriction instrument-state scale-type)
(scaleo base-note scale-type scale)
(permuteo scale melody)))
(defn- same-melody-params?
[instrument-state-1 instrument-state-2]
(let [non-melody-keys [:speed :gaps]]
(= (apply dissoc instrument-state-1 non-melody-keys)
(apply dissoc instrument-state-2 non-melody-keys))))
(defn composer-loop
"Listens for new instrument states on instrument-state-ch and emits a
random melody to melody-ch. The loop terminates when
instrument-state-ch closes.
Changes to :speed or :gaps does not compose a new melody, but alters
the timing of the existing."
[instrument-state-ch melody-ch]
(go
(loop [prev-instrument-state nil
prev-composition
nil]
(when-let [instrument-state (<! instrument-state-ch)]
(let [gaps (for [i (range 8)] (get (:gaps instrument-state) i 0.5))
speed (:speed instrument-state)
new-melody (if (same-melody-params? prev-instrument-state
instrument-state)
(:melody prev-composition)
(random-composition instrument-state))
new-composition {:gaps gaps
:speed speed
:melody new-melody}]
(>! melody-ch new-composition)
(recur instrument-state
new-composition))))))
The system
The system
Experiments
• Goal: a reactive system
• Experiment 1: What is the size of the
melody space and how long does it take to
enumerate it?
• Experiment 2: What is a reasonable bound
on the search space to achieve
responsiveness?
Experiment 1
Melody space
Execution time (ms)
Melodies/second
No scale
Any tonic note
C
–
pc
–
pc
Major scale
Any tonic note
C
–
pc
–
pc
258
–
–
9, 360
4, 299
2, 177
120
278
431
258
–
–
258
–
–
258
–
–
1, 560
3, 852
404
720
294
2, 448
e of the melody space for different instrument configurations and the time it takes to enumerate that space in C OM
notes available to the engine, which accounts for the bound of 258 . “pc” is short for “perfect cadence”.
Execution time for bounded melody space searches
100000
Experiment 2
No scale
Major scale, any tonic note, any cadence
Major scale, C, any cadence
Major scale, C, perfect cadence
Execution time (ms)
100000
10000
1000
100
10
1
2
8
32
128
512
Search space bound
2048
8192
32768
Conclusion
• Composer demonstrates it is possible to
build a responsive interactive system with
extremely small and succinct core
• The declarative nature of the core
implementation makes it possible to extend
the terminology to other types of music
Future work
• Proper sampling of search space
• Labeled interface
• Non-Western music
• User testing