Fork me on GitHub
#overtone
<
2023-11-10
>
denik02:11:28

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

denik02:11:30

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)

plexus12:11:19

makes sense, you could probably also use for. doseq in only for side effects, its return value is always nil

🙏 1
plexus12:11:18

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.

plexus12:11:09

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.

plexus12:11:23

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

plexus12:11:30

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.

plexus15:11:00

I'm starting to push a bunch of fixes that I've been collecting on a local branch

❤️ 1
plexus15:11:41

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

plexus15:11:53

And one new "feature", saving the freesound token so you don't have to enter it every single time

plexus16:11:33

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

👍 1
denik21:11:33

trying it!

💯 1
denik21:11:07

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?

plexus05:11:36

Can you show the stack trace?

denik00:11:14

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

denik00:11:19

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

plexus11:11:37

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.

denik13:11:18

Yes I doubt that it would. Besides the docstring, is there a tutorial or explanation of synth, defsynth somewhere? I could find anything

plexus13:11:39

I don't think there are proper reference docs, I'm also reverse engineering in the hope of one day documenting them properly...

🙏 1
plexus14:11:07

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

plexus14:11:10

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

denik22:11:23

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