Declarative scores for Overtone.
;; bring in the essentials, currently a messy affair
(use [ko.gesture]
[ko.scheduling :only (play-score)]
[ko.score :only (defscore)]
[overtone.core])
;; define a synthdef
(ko-defsynth test-synth
[freq 1 amp 1]
(out 0 (* (sin-osc freq) amp)))
;; define a score
(defscore test-score
(beats-per-bar 4)
(beats-per-minute 108)
1 [(begin :ssg :my-gesture {:instr test-synth
:freq 220
:amp -16})]
2 [(begin :ssg :other-gesture {:instr test-synth
:freq 440
:amp -12})]
(silent-measure)
1 [(curve :my-gesture {:freq [220 :exp]
:amp [-6 :exp]})
(alter :other-gesture {:freq 440})]
2 [(finish :my-gesture :other-gesture)])
;; play it
(play-score test-score)
Use ko-defsynth
to define synthdefs instead of Overtone's defsynth
(ko-defsynth
was chosen so users can use
both Overtone and Ko in
the same ns). Then use defscore
to define a score. A basic score
consists of pairs of numbers and vectors. Numbers indicate beats in a
measure e.g. 1.5 is the first offbeat of the measure. Measures are
inferred by numbers lower than their predecessors. Vectors contain
begin
, adjust
, finish
and curve
actions to be executed at the time
that corresponds with their corresponding number.
The following plays two gestures, one starting on beat one and the other starting on the offbeat of beat two. Both end on beat one of the following measure.
(defscore
1 [(begin :ssg :my-gesture my-gesture-spec)]
2.5 [(begin :ssg :next-gesture next-gesture-spec)]
1 [(finish :my-gesture :next-gesture)])
begin
currently supports two gesture types :ssg
(Single-Synth
Gesture) and :msg
(Multi-Synth Gesture).
:ssg
gestures take the form
(begin :ssg :gesture-name spec)
where spec
must be a map containing an :instr
key specifying
a ko-synthdef
along with all other params to the synth. Spec
can itself be a map, a var referring to a map, or form that when
evaluated returns a map.
:msg
are similar but provide a means of controlling multiple
synthesizers that share arguments. When a vector is passed in for
the value of a synth arg, :msg
gestures create an individual synth
for each entry in the vector. E.g.
(ko-defsynth simple-sine [bus 0 freq 440 amp 0.1]
(out:ar bus (* amp (sin-osc:ar freq))))
(defscore test-score
(begin :msg :my-gest {:instr simple-sine :bus 0 :amp 0.2 :freq [220 440 880]}))
:my-gest
here will create three synths with identical arguments
except for freq
, which would be 220, 440, and 880, respectively.
adjust
and curve
actions control gestures as they are playing, but do so
differently.
adjust
is used to alter parameters of a running synth at
a specific time while it is playing. The following will change
the amp
param of :my-gesture
to -12 decibels on beat three of the
corresponding measure:
3 (adjust :my-gesture {:amp -12})
curve
is used to specify control envelope breakpoints for smooth
changes over the course of a gesture by calculating the time
difference between a gesture's begin
and successive curve
actions.
Unlike alter
, curve
generates a new synthdef under the hood and does
not send additional OSC messages to scsynth while the score is playing.
The following begins a gesture on beat two that crescendos along an
exponential curve (specified by :exp
) to -6 decibels on beat one of
the following measure before decrescendoing to -32 decibels along a curve
value of 4 (see SuperCollider's env docs
for more info on envelope curvature) and ending on beat three:
2 [(begin :ssg :my-gesture {:instr test-synth :amp -24 :freq 220})]
1 [(curve :my-gesture {:amp [-6 :exp]})]
3 [(curve :my-gesture {:amp [-32 4]})
(finish :my-gesture)]
The typical Overtone equivalent of this would be to define a synthdef with freq
bound
to
(envelope (map db->amp [-24 -6 -32])
[time-between-meas-1-beat-2-and-meas-2-beat-1 time-between-meas-2-beat-1-and-meas-2-beat-3]
[:exp 4])
curve
actions can also be used with :msg
gesture-style argument
expansion. Take care to ensure the number of arguments specified in
the curve
action's argument vectors matches that of the begin
action for the :msg
:
2 [(begin :msg :my-gesture {:instr test-synth
:amp -24
:freq [220 440]})]
1 [(curve :my-gesture {:freq [[660 880] :exp]})]
(finish :my-gesture)]
Ends a running gesture. In the case of :ssg
gestures, simply sends a
\n_free
or 'node free' message to the server for the corresponding
synth. Ending two gestures:
1 [(finish :some-gesture-name :another-gesture-name)]
To route sound from one synth to any number of synths, pass a string
ending in -bus
for params mapped to busses. The following
demonstrates routing the output of a synth called :gen-saw-wave
to
a synth called :shared-low-pass
, which in turn routes to hardware
output 0:
1 [(begin :ssg :gen-saw-wave {:instr saw-wave
:freq 220
:amp -12
:out-bus "shared-low-pass-in-bus"})
(begin :ssg :shared-low-pass {:instr low-pass
:in-bus "shared-low-pass-in-bus"
:out-bus 0})]
Under the hood, Ko looks for string arguments ending in -bus
and
substitutes them for audio busses. For this reason, do not end a
string argument to a synth in -bus
unless it's for a bus.
If no target is specified for a begin
action, it will be added to
the head of the "Overtone Default" group. This isn't ideal for even
simple routing schemas that rely on sources and filters to be in a
specific order. To explicitly order synth nodes, first use
register-group
to specifiy a graph of groups, then reference the
desired target group by name in the begin
action.
Create a group named "source"
at the head of the "Overtone
Default", a group called "filter"
directly after it, then a
group middling
in between the two:
;; single-arity adds to head of "Overtone Default"
(register-group "source")
;; two-arity adds after the second arg
(register-group "filter" "source")
;; three-arity specifies add-action
(register-group "middling" "filter" :before)
(overtone/pprint-node-tree)
;; =>
;; --snip--
;; {:type :group,
;; :name "Overtone Default"
;; :id 76,
;; :children
;; ({:type :group, :id 110, :name "source", :children nil}
;; {:type :group, :id 112, :name "middling", :children nil}
;; {:type :group, :id 111, :name "filter", :children nil})}
;; --snip--
Specify begin
actions for a source and filter, each targeting
the head of their appropriate group:
1 [(begin :ssg :g-one source-spec "source")
(begin :ssg :filt filt-spec "filter")]
Additionally specify add-actions:
1 [(begin :ssg :g-one source-spec [:tail "source"])
(begin :ssg :filt filt-spec [:head "filter"])]
Remove all registered groups:
(reset-groups!)
Aside from specifying gestures, the defscore macro provides for setting the time signature:
(beats-per-bar 4)
(beats-per-minute 80)
and for notating measures in which no actions occur:
(silent-measure)