I wondered about making a desktop shadertoy clone in Clojure. A quick search turned up an existing project that already does exactly that - overtone/shadertoy. Instead of rewriting that from scratch, this is my log of making it work on my machine (2024-03-15 Debian 13 - trixie, testing). It was a challenge.
The starting point was
git clone https://github.com/overtone/shadertone.git && cd shadertone
and lein repl
.
$ lein repl
--> Loading Overtone...
--> Booting internal SuperCollider server...
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fa08411c479, pid=17628, tid=17743
#
# JRE version: OpenJDK Runtime Environment (17.0.10+7) (build 17.0.10+7-Debian-1)
# Java VM: OpenJDK 64-Bit Server VM (17.0.10+7-Debian-1, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
# Problematic frame:
# C [ld-linux-x86-64.so.2+0x2479]
#
# Core dump will be written. Default location: Core dumps may be processed with "/usr/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h" (or dumping to /home/owen/empty/demo/shadertone/core.17628)
#
# An error report file with more information is saved as:
# /home/owen/empty/demo/shadertone/hs_err_pid17628.log
#
# If you would like to submit a bug report, please visit:
# https://bugs.debian.org/openjdk-17
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
*** ERROR: dlopen '/home/owen/empty/demo/shadertone/target/native/linux/x86_64/liblwjgl64.so' err 'libjawt.so: cannot open shared object file: No such file or directory'
Subprocess failed (exit code: 134)
This isn’t ideal. The JVM is crashing! First objective is to start the Clojure REPL without any errors.
Now I happen to know that libjawt is installed
(dpkg -S libawt.so
), so something is going wrong. I updated
project.clj to use the current version of Clojure (1.11.2). The error
got a lot more descriptive:
--> Loading Overtone...
#error {
:cause Call to clojure.core/ns did not conform to spec.
:data #:clojure.spec.alpha{:problems ({:path [:ns-clauses :refer-clojure :clause], :pred #{:refer-clojure}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-refer-clojure :clojure.core.specs.alpha/ns-refer-clojure], :in [2 0]} {:path [:ns-clauses :require :clause], :pred #{:require}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-require :clojure.core.specs.alpha/ns-require], :in [2 0]} {:path [:ns-clauses :import :classes :class], :pred clojure.core/simple-symbol?, :val [java.awt.Toolkit], :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/import-list], :in [2 1]} {:path [:ns-clauses :import :classes :package-list :classes], :reason Insufficient input, :pred clojure.core/simple-symbol?, :val (), :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/import-list :clojure.core.specs.alpha/package-list :clojure.core.specs.alpha/package-list], :in [2 1]} {:path [:ns-clauses :use :clause], :pred #{:use}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-use :clojure.core.specs.alpha/ns-use], :in [2 0]} {:path [:ns-clauses :refer :clause], :pred #{:refer}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-refer :clojure.core.specs.alpha/ns-refer], :in [2 0]} {:path [:ns-clauses :load :clause], :pred #{:load}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-load :clojure.core.specs.alpha/ns-load], :in [2 0]} {:path [:ns-clauses :gen-class :clause], :pred #{:gen-class}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-gen-class :clojure.core.specs.alpha/ns-gen-class], :in [2 0]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2503 0x32fdec40 clojure.spec.alpha$regex_spec_impl$reify__2503@32fdec40], :value (overtone.libs.app-icon (:use [clojure.java.io] [overtone.helpers.lib :only [branch]] [overtone.helpers.system :only [get-os]]) (:import [java.awt.Toolkit])), :args (overtone.libs.app-icon (:use [clojure.java.io] [overtone.helpers.lib :only [branch]] [overtone.helpers.system :only [get-os]]) (:import [java.awt.Toolkit]))}
:via
[{:type clojure.lang.Compiler$CompilerException
:message Syntax error macroexpanding clojure.core/ns at (overtone/libs/app_icon.clj:1:1).
:data #:clojure.error{:phase :macro-syntax-check, :line 1, :column 1, :source overtone/libs/app_icon.clj, :symbol clojure.core/ns}
:at [clojure.lang.Compiler checkSpecs Compiler.java 6989]}
{:type clojure.lang.ExceptionInfo
:message Call to clojure.core/ns did not conform to spec.
:data #:clojure.spec.alpha{:problems ({:path [:ns-clauses :refer-clojure :clause], :pred #{:refer-clojure}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-refer-clojure :clojure.core.specs.alpha/ns-refer-clojure], :in [2 0]} {:path [:ns-clauses :require :clause], :pred #{:require}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-require :clojure.core.specs.alpha/ns-require], :in [2 0]} {:path [:ns-clauses :import :classes :class], :pred clojure.core/simple-symbol?, :val [java.awt.Toolkit], :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/import-list], :in [2 1]} {:path [:ns-clauses :import :classes :package-list :classes], :reason Insufficient input, :pred clojure.core/simple-symbol?, :val (), :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/ns-import :clojure.core.specs.alpha/import-list :clojure.core.specs.alpha/package-list :clojure.core.specs.alpha/package-list], :in [2 1]} {:path [:ns-clauses :use :clause], :pred #{:use}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-use :clojure.core.specs.alpha/ns-use], :in [2 0]} {:path [:ns-clauses :refer :clause], :pred #{:refer}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-refer :clojure.core.specs.alpha/ns-refer], :in [2 0]} {:path [:ns-clauses :load :clause], :pred #{:load}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-load :clojure.core.specs.alpha/ns-load], :in [2 0]} {:path [:ns-clauses :gen-class :clause], :pred #{:gen-class}, :val :import, :via [:clojure.core.specs.alpha/ns-form :clojure.core.specs.alpha/ns-gen-class :clojure.core.specs.alpha/ns-gen-class], :in [2 0]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2503 0x32fdec40 clojure.spec.alpha$regex_spec_impl$reify__2503@32fdec40], :value (overtone.libs.app-icon (:use [clojure.java.io] [overtone.helpers.lib :only [branch]] [overtone.helpers.system :only [get-os]]) (:import [java.awt.Toolkit])), :args (overtone.libs.app-icon (:use [clojure.java.io] [overtone.helpers.lib :only [branch]] [overtone.helpers.system :only [get-os]]) (:import [java.awt.Toolkit]))}
:at [clojure.spec.alpha$macroexpand_check invokeStatic alpha.clj 712]}]
:trace
[[clojure.spec.alpha$macroexpand_check invokeStatic alpha.clj 712]
[clojure.spec.alpha$macroexpand_check invoke alpha.clj 704]
... stacktrace elided ...
[clojure.lang.Var applyTo Var.java 705]
[clojure.main main main.java 40]]}
nREPL server started on port 39947 on host 127.0.0.1 - nrepl://127.0.0.1:39947
REPL-y 0.5.1, nREPL 1.0.0
Clojure 1.11.2
OpenJDK 64-Bit Server VM 17.0.10+7-Debian-1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
This looks like a problem in overtone
so I also update
that to a recent version (0.13.3177). The complaints start to multiply,
I get 2 of them - lein
doesn’t like the version ranges and
it can’t find the scsynth binary
:
$ lein repl
WARNING!!! version ranges found for:
[overtone "0.13.3177"] -> [casa.squid/jack "0.2.12"] -> [org.jaudiolibs/jnajack "1.4.0"] -> [net.java.dev.jna/jna "[5.0.0,6.0)"]
Consider using [overtone "0.13.3177" :exclusions [net.java.dev.jna/jna]].
--> Loading Overtone...
[overtone.live] [WARNING] Only :external connection type is supported, :connection-type :internal ignored. (/home/owen/.overtone/config.clj)
#error {
:cause Failed to find SuperCollider server executable (scsynth). The file does not exist or is not executable. Places I've looked:
- `:sc-path` in /home/owen/.overtone/config.clj (nil)
- The current PATH (/opt/conda/bin:/home/owen/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/home/owen/bin:/opt/conda/bin/)
- Well-known locations ("/usr/bin/scsynth")
:data {}
:via
[{:type clojure.lang.Compiler$CompilerException
:message Syntax error macroexpanding at (overtone/live.clj:7:1).
:data #:clojure.error{:phase :execution, :line 7, :column 1, :source overtone/live.clj}
:at [clojure.lang.Compiler load Compiler.java 7665]}
{:type clojure.lang.ExceptionInfo
:message Failed to find SuperCollider server executable (scsynth). The file does not exist or is not executable. Places I've looked:
- `:sc-path` in /home/owen/.overtone/config.clj (nil)
- The current PATH (/opt/conda/bin:/home/owen/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/home/owen/bin:/opt/conda/bin/)
- Well-known locations ("/usr/bin/scsynth")
:data {}
:at [overtone.sc.machinery.server.connection$scsynth_path invokeStatic connection.clj 282]}]
:trace
[[overtone.sc.machinery.server.connection$scsynth_path invokeStatic connection.clj 282]
[overtone.sc.machinery.server.connection$scsynth_path invoke connection.clj 276]
... stacktrace elided ...
[clojure.lang.Var applyTo Var.java 705]
[clojure.main main main.java 40]]}
nREPL server started on port 35097 on host 127.0.0.1 - nrepl://127.0.0.1:35097
This can be fixed by installing scsynth
(sudo apt install supercollider
) and adjusting the
project.clj
file as suggested then manually adding a
dependency on jna (I used
[net.java.dev.jna/jna "5.14.0"]
).
Further runtime errors start to appear after the initial complaints are resolved.
$ lein repl
--> Loading Overtone...
[overtone.live] [WARNING] Only :external connection type is supported, :connection-type :internal ignored. (/home/owen/.overtone/config.clj)
[overtone.live] [INFO] Found SuperCollider server: /usr/bin/scsynth (PATH)
--> Booting external SuperCollider server...
[overtone.live] [INFO] Booting SuperCollider server (scsynth) with cmd: /usr/bin/scsynth -u 2238 -b 1024 -z 64 -m 262144 -d 1024 -V 0 -n 1024 -r 64 -l 64 -D 0 -o 8 -a 512 -R 0 -c 4096 -H Overtone -i 8 -w 64
[overtone.live] [INFO] Found Jack-compatible server process:
[overtone.live] [INFO] 2947 owen /usr/bin/pipewire
[overtone.live] [INFO] 2948 owen /usr/bin/pipewire -c filter-chain.conf
[overtone.live] [INFO] 2952 owen /usr/bin/pipewire
[overtone.live] [INFO] 2968 owen /usr/bin/jackdbus auto
--> Connecting to external SuperCollider server: 127.0.0.1:2238
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
[scynth] no message buffer overruns
[scynth] no message buffer overruns
[scynth] no message buffer overruns
[scynth] jackdmp 1.9.21
[scynth] Copyright 2001-2005 Paul Davis and others.
[scynth] Copyright 2004-2016 Grame.
[scynth] Copyright 2016-2022 Filipe Coelho.
[scynth] jackdmp comes with ABSOLUTELY NO WARRANTY
[scynth] This is free software, and you are welcome to redistribute it
[scynth] under certain conditions; see the file COPYING for details
[scynth] JACK server starting in realtime mode with priority 10
[scynth] self-connect-mode is "Don't restrict self connect requests"
[scynth] audio_reservation_init
[scynth] Acquire audio card Audio0
[scynth] creating alsa driver ... hw:0|hw:0|1024|2|48000|0|0|nomon|swmeter|-|32bit
[scynth] ALSA: Cannot open PCM device alsa_pcm for playback. Falling back to capture-only mode
[scynth] JackTemporaryException : now quits...
[scynth] Released audio card Audio0
[scynth] aannot initialize driver
Cannot initialize driver
[scynth] JackServer::Open failed with -1
[scynth] Failed to open server
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
#error {
:cause Error: unable to connect to externally booted server after 50 attempts.
Make sure that you have Server.local.options.maxLogins set to greater than 1 in startup file (startup.scd).
Or if you're on Windows, make sure that the Windows defender isn't blocking the scsynth.exe
:via
[{:type clojure.lang.Compiler$CompilerException
:message Syntax error macroexpanding at (overtone/live.clj:7:1).
:data #:clojure.error{:phase :execution, :line 7, :column 1, :source overtone/live.clj}
:at [clojure.lang.Compiler load Compiler.java 7665]}
{:type java.lang.Exception
:message Error: unable to connect to externally booted server after 50 attempts.
Make sure that you have Server.local.options.maxLogins set to greater than 1 in startup file (startup.scd).
Or if you're on Windows, make sure that the Windows defender isn't blocking the scsynth.exe
:at [overtone.sc.machinery.server.connection$external_connection_runner invokeStatic connection.clj 162]}]
:trace
[[overtone.sc.machinery.server.connection$external_connection_runner invokeStatic connection.clj 162]
[overtone.sc.machinery.server.connection$external_connection_runner invoke connection.clj 135]
... stacktrace elided ...
[clojure.lang.Var applyTo Var.java 705]
[clojure.main main main.java 40]]}
[scynth] Cannot connect to server socket err = No such file or directory
[scynth] Cannot connect to server request channel
nREPL server started on port 45607 on host 127.0.0.1 - nrepl://127.0.0.1:45607
[scynth] jack server is not running or cannot be started
[scynth] JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
[scynth] JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
[scynth] terminate called without an active exception
[scynth] could not initialize audio.
REPL-y 0.5.1, nREPL 1.0.0
Clojure 1.11.2
OpenJDK 64-Bit Server VM 17.0.10+7-Debian-1
Jack is an audio server that is often used by serious music types; it
isn’t running on my machine so overtone doesn’t know where to send
sound. I don’t want to run it. Linux sound is one of those topics that
I’m happy to live in ignorance of right now. The plan is to set Pipewire
to mimic a Jack server enough to play sound. I install the Pipewire Jack
plugin (sudo apt install pipewire-jack
) and then set up ldconf as described
here. Note it is sudo ldconfig
and no
touch
is required for Debian 12+.
I then tested this with mpv -ao=jack nothern-glade.mp3
and it seemed to be working.
$ lein repl
--> Loading Overtone...
[overtone.live] [WARNING] Only :external connection type is supported, :connection-type :internal ignored. (/home/owen/.overtone/config.clj)
[overtone.live] [INFO] Found SuperCollider server: /usr/bin/scsynth (PATH)
--> Booting external SuperCollider server...
[overtone.live] [INFO] Booting SuperCollider server (scsynth) with cmd: /usr/bin/scsynth -u 2744 -b 1024 -z 64 -m 262144 -d 1024 -V 0 -n 1024 -r 64 -l 64 -D 0 -o 8 -a 512 -R 0 -c 4096 -H Overtone -i 8 -w 64
[overtone.live] [INFO] Found Jack-compatible server process:
[overtone.live] [INFO] 2947 owen /usr/bin/pipewire
[overtone.live] [INFO] 2948 owen /usr/bin/pipewire -c filter-chain.conf
[overtone.live] [INFO] 2952 owen /usr/bin/pipewire
[overtone.live] [INFO] 2968 owen /usr/bin/jackdbus auto
--> Connecting to external SuperCollider server: 127.0.0.1:2744
[scynth] JackDriver: client name is 'Overtone-57'
[scynth] SC_AudioDriver: sample rate = 48000.000000, driver's block size = 1024
[scynth] SuperCollider 3 server ready.
--> Connection established
_____ __
/ __ /_ _____ _____/ /_____ ____ ___
/ / / / | / / _ \/ ___/ __/ __ \/ __ \/ _ \
/ /_/ /| |/ / __/ / / /_/ /_/ / / / / __/
\____/ |___/\___/_/ \__/\____/_/ /_/\___/
Collaborative Programmable Music. v0.13.3177
Hello Owen, may algorithmic beauty pour forth from your fingertips today.
#error {
:cause Unable to resolve symbol: buffer-data in this context
:via
[{:type clojure.lang.Compiler$CompilerException
:message Syntax error compiling at (shadertone/tone.clj:127:19).
:data #:clojure.error{:phase :compile-syntax-check, :line 127, :column 19, :source shadertone/tone.clj}
:at [clojure.lang.Compiler analyze Compiler.java 6825]}
{:type java.lang.RuntimeException
:message Unable to resolve symbol: buffer-data in this context
:at [clojure.lang.Util runtimeException Util.java 221]}]
:trace
[[clojure.lang.Util runtimeException Util.java 221]
[clojure.lang.Compiler resolveIn Compiler.java 7431]
... stacktrace elided ...
[clojure.lang.Var applyTo Var.java 705]
[clojure.main main main.java 40]]}
nREPL server started on port 36183 on host 127.0.0.1 - nrepl://127.0.0.1:36183
REPL-y 0.5.1, nREPL 1.0.0
Clojure 1.11.2
OpenJDK 64-Bit Server VM 17.0.10+7-Debian-1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
shadertone.core=>
Nearly there! I looked at the code and there is a function called
buffer-data
thaqt isn’t working. According to the changelog
it has been removed. I guessed that it should be replaced by
create-buffer-data
(this guess proved to be wrong later
on). However, for the moment, that was enough to get the REPL to
load!
$ lein repl
--> Loading Overtone...
[overtone.live] [WARNING] Only :external connection type is supported, :connection-type :internal ignored. (/home/owen/.overtone/config.clj)
[overtone.live] [INFO] Found SuperCollider server: /usr/bin/scsynth (PATH)
--> Booting external SuperCollider server...
[overtone.live] [INFO] Booting SuperCollider server (scsynth) with cmd: /usr/bin/scsynth -u 20502 -b 1024 -z 64 -m 262144 -d 1024 -V 0 -n 1024 -r 64 -l 64 -D 0 -o 8 -a 512 -R 0 -c 4096 -H Overtone -i 8 -w 64
[overtone.live] [INFO] Found Jack-compatible server process:
[overtone.live] [INFO] 2947 owen /usr/bin/pipewire
[overtone.live] [INFO] 2948 owen /usr/bin/pipewire -c filter-chain.conf
[overtone.live] [INFO] 2952 owen /usr/bin/pipewire
[overtone.live] [INFO] 2968 owen /usr/bin/jackdbus auto
--> Connecting to external SuperCollider server: 127.0.0.1:20502
[scynth] JackDriver: client name is 'Overtone-98'
[scynth] SC_AudioDriver: sample rate = 48000.000000, driver's block size = 1024
[scynth] SuperCollider 3 server ready.
--> Connection established
_____ __
/ __ /_ _____ _____/ /_____ ____ ___
/ / / / | / / _ \/ ___/ __/ __ \/ __ \/ _ \
/ /_/ /| |/ / __/ / / /_/ /_/ / / / / __/
\____/ |___/\___/_/ \__/\____/_/ /_/\___/
Collaborative Programmable Music. v0.13.3177
Hello Owen, may this be the start of a beautiful music hacking session...
nREPL server started on port 43117 on host 127.0.0.1 - nrepl://127.0.0.1:43117
REPL-y 0.5.1, nREPL 1.0.0
Clojure 1.11.2
OpenJDK 64-Bit Server VM 17.0.10+7-Debian-1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
shadertone.core=>
Next I loaded the namespace from
examples/00demo_intro_tour.clj
in Emacs. It doesn’t seem to
want to work. Neither does (demo (sin-osc))
at the REPL (I
checked with pavucontrol - no stream created at all, although maybe Jack
output doesn’t get sent to Pulseaudio tools? I assume it would be). This
is a tricky one.
I refer to ChatGPT on how to test SuperCollider. It recommends
scide
(the remarkably rookie friendly IDE bundled with
SuperCollider) then running
s.boot;
{ SinOsc.ar(440, 0, 0.2) }.play;
which produced a boring but functional tone; so the issue is
somewhere in Overtone. I try to connect to the server that Overtone
starts from scide
and get all sorts of errors (it seems not
to be Overtone’s fault though, scide requires <=32 maximum allowable
user connections and scsynth defaults to allowing 64. Weird). Eventually
I settled on this code in sdide
, where the default server
(stored in the s
variable) works and my server (being run
in a terminal with scsynth -u 30000 -l 10
) does not:
// Works
s.boot;
{ SinOsc.ar(440, 0, 0.2) }.play(s);
s.freeAll;
// Does not work.
// Start server with `scsynth -u 30000 -l 10`
~port = 30000;
~server = Server.remote('clojureServer', NetAddr("127.0.0.1", ~port));
~server.boot;
{ SinOsc.ar(440, 0, 0.2) }.play(~server);
~server.freeAll;
Sleuthing eventually included running
ps aux | grep scsynth
and using exactly the same command
line parameters for both servers which did not help at all. Reading the
documentation, I discovered that I needed to run
export SC_JACK_DEFAULT_OUTPUTS="system"
in the terminal
because obviously nobody would default to the system default without
explicitly being told to. An hour of my life I will not get back, but I
learned a bit about SuperCollider so that was nice, I suppose. The
variable was then set in /etc/environment
. After logging
out and back in, the demo ((demo (sin-osc))
) worked and -
remarkably - I don’t think most of that struggle was related to the
overtone
project.
This was the point where I took a long break and listened to some
guitar-strum
s from the demo to reflect on my success so
far. The next problem is the GLSL demo
(t/start "examples/sine_dance.glsl")
doesn’t work properly.
It displays a blank screen. Looking at the shader code, it seems to be
supposed to plot sound data from Overtone. I’ve seen some error messages
about Internal and External overtone servers on the way through, so I’m
expecting it to be related to that.
No error messages are visible so maybe an error message being swallowed somewhere? I load the code and jump down through the most obvious execution path looking for suspicious patterns. I quickly find this function in shadertone’s shader.clj:
(defn start-shader-display
"Start a new shader display with the specified mode. Prefer start or
start-fullscreen for simpler usage."
[mode shader-filename-or-str-atom textures title true-fullscreen?
user-data user-fn display-sync-hz]
(let [is-filename (not (instance? clojure.lang.Atom shader-filename-or-str-atom))
shader-filename (if is-filename
shader-filename-or-str-atom)
;; Fix for issue 15. Normalize the given shader-filename to the
;; path separators that the system will use. If user gives path/to/shader.glsl
;; and windows returns this as path\to\shader.glsl from .getPath, this
;; change should make comparison to path\to\shader.glsl work.
shader-filename (if (and is-filename (not (nil? shader-filename)))
(.getPath (File. ^String shader-filename)))
shader-str-atom (if-not is-filename
shader-filename-or-str-atom
(atom nil))
shader-str (if-not is-filename
@shader-str-atom)]
(when (sane-user-inputs mode shader-filename shader-str textures title true-fullscreen? user-fn)
;; stop the current shader
(stop)
;; start the watchers
(if is-filename
(when-not (nil? shader-filename)
(swap! watcher-future
(fn [x] (start-watcher shader-filename))))
(add-watch shader-str-atom :shader-str-watch watch-shader-str-atom))
;; set a global window-state instead of creating a new one
(reset! the-window-state default-state-values)
;; set user data
(reset! shader-user-data user-data)
;; start the requested shader
(.start (Thread.
(fn [] (run-thread the-window-state
mode
shader-filename
shader-str-atom
textures
title
true-fullscreen?
user-fn
display-sync-hz)))))))
We see a new thread being started. Maybe there are exceptions on this
thread? .start
is a void function, so we aren’t keeping a
reference to the thread in the Clojure code. We also see an
add-watch
- less suspicious, but maybe it is doing
something that throws an error. We also have some state being stored in
other atoms (reset!
) which might be problematic.
I’ve been making Java profilers part of my normal practice, so I
tried connecting visualvm
(setting
_JAVA_AWT_WM_NONREPARENTING=1
to make it work on Wayland;
the Wayland protocol has left the ecosystem with a lot of design issues
to solve). It didn’t reveal anything useful.
However, adding a (try ... (catch Throwable t ...))
to
the run-thread
function revealed something was being
thrown.
#error {
:cause Wrong number of args (1) passed to: overtone.sc.buffer/create-buffer-data
:via
[{:type clojure.lang.ArityException
:message Wrong number of args (1) passed to: overtone.sc.buffer/create-buffer-data
:at [clojure.lang.AFn throwArity AFn.java 429]}]
:trace
[[clojure.lang.AFn throwArity AFn.java 429]
[clojure.lang.AFn invoke AFn.java 32]
[shadertone.tone$tone_fftwave_fn invokeStatic tone.clj 127]
[shadertone.tone$tone_fftwave_fn invoke tone.clj 98]
[shadertone.tone$tone_default_fn invokeStatic tone.clj 205]
[shadertone.tone$tone_default_fn invoke tone.clj 172]
[shadertone.shader$draw invokeStatic shader.clj 585]
[shadertone.shader$draw invoke shader.clj 548]
[shadertone.shader$update_and_draw invokeStatic shader.clj 697]
[shadertone.shader$update_and_draw invoke shader.clj 664]
[shadertone.shader$run_thread invokeStatic shader.clj 729]
[shadertone.shader$run_thread invoke shader.clj 722]
[shadertone.shader$start_shader_display$fn__11628 invoke shader.clj 940]
[clojure.lang.AFn run AFn.java 22]
[java.lang.Thread run Thread.java 840]]}
Aha, well that is unfortunate. It would appear that create-buffer and
create-buffer-data are quite different functions - one probably
extracted from a buffer and the other creates a new buffer. I do some
research to figure out where the buffer abstraction comes from. It seems
to be stemming from scsynth
. That creates a Buffer concept
for clients, and then Overtone creates a Clojure record - also called
Buffer - to represent this.
I replace create-buffer-data
with
buffer-read
. The function documentation notes that
buffer-read
might be quite slow. Slow it may be, but the
improvement to the demo is quite noticeable. Now there is a wiggly line
in the shader display window, presumably wobbling sinusoidally. It
wobbles with even more enthusiasm when the guitar strum sound is played.
The rest of the demo seems to work too.