This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-10
Channels
- # announcements (3)
- # asami (19)
- # babashka (38)
- # beginners (42)
- # cider (19)
- # clojure (17)
- # clojure-europe (34)
- # clojure-hungary (3)
- # clojure-nl (1)
- # clojure-norway (53)
- # clojure-uk (7)
- # clojuredesign-podcast (34)
- # conjure (2)
- # cursive (7)
- # data-science (13)
- # datalevin (3)
- # datomic (19)
- # dev-tooling (1)
- # events (1)
- # honeysql (2)
- # hyperfiddle (31)
- # integrant (16)
- # juxt (39)
- # missionary (14)
- # nrepl (14)
- # off-topic (57)
- # overtone (22)
- # podcasts-discuss (1)
- # practicalli (32)
- # reitit (12)
- # releases (2)
- # ring (13)
- # ring-swagger (2)
- # sql (85)
- # squint (75)
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})}
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)
makes sense, you could probably also use for
. doseq
in only for side effects, its return value is always nil
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.
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
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?; 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
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