I was looking into how the stringed synths in overtone are implemented, and found out about "Karplus-Strong" synthesis, available in the pluck Ugen. There are some good explainers on youtube.
You need something to "excite" the delay line, which can just be white/pink/brown noise, but also works really well with e.g. a sample of a snare drum.
Here's a pretty convincing bass guitar
(def snare-fat (freesound 122053))
(definst bass [note 36]
(let [freq (midicps note)
base (+ (* (env-gen (adsr 0.5 0.95 0 0) :action FREE)
(sin-osc freq)
0.7)
(* (pluck
(play-buf 2 (:id snare-fat))
:trig 1
:maxdelaytime 0.25
:delaytime (/ 1 freq)
:decaytime 2.3
:coef 0.3)))]
(free-verb
(+ (lpf base 800)
(rlpf base (* 2 freq) 0.1))
0.3
:room 0.2
:damp 1)))make sure to listen to it somewhere where you have some low frequency response, not just on tinny laptop speakers. Adding a sine wave at the base frequency for some extra oomph.
I'm starting to push a bunch of fixes that I've been collecting on a local branch
A few are long standing annoyances
• Clojure 1.11 introduced abs, which clashes with the abs ugen. Turns out that was easy to fix once I spent half a day understanding some more how the collision detection works 🙂
• synth/inst objects could not be called with more than 21 args. Also a really silly fix, the implementation of IFn was incomplete
• I added an alias lin-env for lin, at some point this got renamed, but I don't think we should break old code like that
And one new "feature", saving the freesound token so you don't have to enter it every single time
Please try things out! I think these are some important bug fixes (together with the thread/sleep fix on newer JVMs) that should be released soon
trying it!
I don’t understand the value of control proxies in overtone unless I’m using them or defsynth wrong. Here’s an example
;; I assumed this would work but it breaks
(defsynth control-proxy-usable-directly [freq 440 num-channels 8]
(let [sig (sin-osc:ar freq)
pan-control (sin-osc:kr 0.2)]
(for [ch (range num-channels)]
(out ch (* sig (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (/ ch num-channels)) 1.5))))))))))
; Execution error (ClassCastException) at den1k.surround/fn$fn (REPL:188).
; class overtone.sc.machinery.ugen.sc_ugen.ControlProxy cannot be cast to class java.lang.Number (overtone.sc.machinery.ugen.sc_ugen.ControlProxy is in unnamed module of loader clojure.lang.DynamicClassLoader @1d150b18; java.lang.Number is in module java.base of loader 'bootstrap')
;; this works but is more verbose
(defsynth control-proxy-not-usable-directly [freq 440 num-channels 8]
(let [sig (sin-osc:ar freq)
pan-control (sin-osc:kr 0.2)
nc (:value num-channels)]
(for [ch (range nc)]
(out ch (* sig (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (/ ch nc)) 1.5))))))))))
Instead of defsynth should I just be using a regular clojure fn with optional params that returns a synth?I'll have to play around with it when I get a moment. The code inside defsynth is basically a mini-language that gets compiled in a special way. For that overtone needs to make some assumptions about what is a ugen and what is a plain function. Generally you want to make sure everything is a signal (ugen, controlproxy), so that changes "flow", i.e. do things "the supercollider way", so that your synths respond to updates. I'm fairly sure your (:value nc) version does not respond to a (ctl num-channels ...) message while it's playing. But I'm not sure if SC has some kind of looping construct to achieve the same thing.
Yes I doubt that it would. Besides the docstring, is there a tutorial or explanation of synth, defsynth somewhere? I could find anything
I don't think there are proper reference docs, I'm also reverse engineering in the hope of one day documenting them properly...
One interesting thing is that SuperCollider performs "Array expansion", so you can pass in a sequence where it expects a single argument, and it automatically creates a multi-channel ugen
(defsynth control-proxy-usable [freq 440 num-channels 8]
(let [sig (sin-osc:ar freq)
pan-control (sin-osc:kr 0.2)
nc (:value num-channels)]
(out (range nc)
(* sig (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (map #(/ % nc) (range nc))) 1.5)))))))))but this is still using the Clojure range function, ideally you'd get that to happen inside the SC server, so maybe using Interval (https://doc.sccode.org/Classes/Interval.html), but it doesn't look like we have particular support for that
; Execution error (ClassCastException) at den1k.surround/fn$fn (REPL:204).
; class overtone.sc.machinery.ugen.sc_ugen.ControlProxy cannot be cast to class java.lang.Number (overtone.sc.machinery.ugen.sc_ugen.ControlProxy is in unnamed module of loader clojure.lang.DynamicClassLoader @7e67239f; java.lang.Number is in module java.base of loader 'bootstrap')
clj꞉den1k.surround꞉>
clojure.lang.Compiler$InvokeExpr/eval (Compiler.java:3719)
clojure.lang.Compiler$DefExpr/eval (Compiler.java:457)
clojure.lang.Compiler/eval (Compiler.java:7199)
clojure.core/eval (core.clj:3215)
clojure.core/eval (core.clj:3211)
nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:87)
clojure.core/apply (core.clj:667)
clojure.core/with-bindings* (core.clj:1990)
nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:87)
clojure.main/repl (main.clj:437)
clojure.main/repl (main.clj:458)
clojure.main/repl (main.clj:368)
nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:84)
nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:56)
nrepl.middleware.interruptible-eval/interruptible-eval (interruptible_eval.clj:152)
nrepl.middleware.session/session-exec (session.clj:218)
nrepl.middleware.session/session-exec (session.clj:217)
java.lang.Thread/run (Thread.java:833)the issue is that the args, in this case freq and num-channels are turned into ControlProxy objects by the defsynth macro. to get the value of these one must call :value on the proxy object. control-proxy-usable-directly does not do this and therefore breaks. control-proxy-not-usable-directly does and works
Can you show the stack trace?
since the overtone.live ns is currently not working on Mac Silicon architecture, is there a way to access the sc object? I’m looking for a more fine grained way to kill running nodes than to always restart the server
Trying to write some code to pan a sound across multiple speakers (going for 64+ 😜) but struggling to get the second synth to work. Any idea how to pan sound across multiple channels where the number of channels is a variable??
(defsynth one-works []
(let [channel 0
num-channels 1
pan-control (sin-osc 440)]
(out 0 (* (sin-osc:ar 440) (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (/ channel num-channels)) 1.5)))))))))
(def x (one-works))
(kill x)
(defsynth mult-breaks []
(let [channel 0
num-channels 1
pan-control (sin-osc 440)]
(doseq [o [0]]
(out o (* (sin-osc:ar 440) (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (/ channel num-channels)) 1.5))))))))))
(def x (mult-breaks)) ;;; BROKEN
=> ; Error: synth expected a ugen. Got: {:outputs ({:rate nil})}makes sense, you could probably also use for. doseq in only for side effects, its return value is always nil
Solved! It didn’t like the doseq
(defsynth mult-breaks []
(let [sig (sin-osc:ar 440)
num-channels 2
pan-control (sin-osc:kr 0.2)]
(map
(fn [ch ]
(out ch (* sig (max 0 (sin (* 0.5 Math/PI (+ pan-control (- (* 2 (/ ch num-channels)) 1.5))))))))
(range num-channels))
))
(def x (mult-breaks)) ;;; BROKEN
(kill x)