Skip to content

bion/ko

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ko

Declarative scores for Overtone.

Usage Example

;; 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)

Basic Usage

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)])

Actions

begin

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

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)]

finish

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)]

Routing

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.

Synth node ordering

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!)

Other score elements

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)

Similar projects

About

Declarative scores for Overtone

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published