From fab8bd8cf9c6a67be8cd9f4aa4ee3646dca47095 Mon Sep 17 00:00:00 2001 From: crertel Date: Wed, 23 Sep 2026 17:07:19 -0500 Subject: [PATCH] supercollider-with-plugins: fix plugin discovery Assisted-by: Codex (GPT-6) --- .../interpreters/supercollider/default.nix | 31 +++++----- .../supercollider/tests/sc3-plugins.scd | 58 +++++++++++++++++++ .../interpreters/supercollider/wrapper.nix | 16 ++++- 3 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 pkgs/development/interpreters/supercollider/tests/sc3-plugins.scd diff --git a/pkgs/development/interpreters/supercollider/default.nix b/pkgs/development/interpreters/supercollider/default.nix index 6b73cdf7f5f3..a61dadb5f49e 100644 --- a/pkgs/development/interpreters/supercollider/default.nix +++ b/pkgs/development/interpreters/supercollider/default.nix @@ -21,7 +21,6 @@ gitUpdater, supercollider-with-plugins, supercolliderPlugins, - writeText, runCommand, }: @@ -87,25 +86,29 @@ stdenv.mkDerivation rec { }; tests = { - # test to make sure sclang runs and included plugins are successfully found + # Check both language classes and server UGens, without an audio device. sclang-sc3-plugins = let supercollider-with-test-plugins = supercollider-with-plugins.override { plugins = with supercolliderPlugins; [ sc3-plugins ]; }; - testsc = writeText "test.sc" '' - var err = 0; - try { - MdaPiano.name.postln; - } { - err = 1; - }; - err.exit; - ''; in - runCommand "sclang-sc3-plugins-test" { } '' - timeout 60s env XDG_CONFIG_HOME="$(mktemp -d)" QT_QPA_PLATFORM=minimal ${supercollider-with-test-plugins}/bin/sclang ${testsc} >$out - ''; + runCommand "sclang-sc3-plugins-test" + { + QT_QPA_PLATFORM = "offscreen"; + } + '' + export XDG_CONFIG_HOME="$TMPDIR/config" + export XDG_DATA_HOME="$TMPDIR/data" + export XDG_CACHE_HOME="$TMPDIR/cache" + mkdir -p "$out" + timeout 60s ${supercollider-with-test-plugins}/bin/sclang \ + ${./tests/sc3-plugins.scd} "$out" 2>&1 | tee "$out/log" + grep -Fq 'SC3 plugins rendered successfully' "$out/log" + if grep -E 'ERROR|FAILURE|duplicate' "$out/log"; then + exit 1 + fi + ''; }; }; diff --git a/pkgs/development/interpreters/supercollider/tests/sc3-plugins.scd b/pkgs/development/interpreters/supercollider/tests/sc3-plugins.scd new file mode 100644 index 000000000000..bcd9152e3cf0 --- /dev/null +++ b/pkgs/development/interpreters/supercollider/tests/sc3-plugins.scd @@ -0,0 +1,58 @@ +var output = thisProcess.argv[0]; +var definition, score; + +try { + // Resolve symbols at runtime: an undefined class literal prevents the entire + // script (including its error handler and exit) from being compiled. + [\MembraneHexagon, \MdaPiano].do { |name| + if(name.asClass.isNil) { + Error("Missing SC3 class: " ++ name).throw; + }; + }; + + definition = SynthDef(\sc3PluginsTest, { + // Give each UGen its own output channel so neither can mask a failure. + Out.ar(0, \MembraneHexagon.asClass.ar(Impulse.ar(0))); + Out.ar(1, \MdaPiano.asClass.ar.sum); + }); + score = Score([ + [0, [\d_recv, definition.asBytes]], + [0, [\s_new, definition.name, 1000, 0, 0]], + [1, [\n_free, 1000]] + ]); + + score.recordNRT( + oscFilePath: output +/+ "score.osc", + outputFilePath: output +/+ "sc3.wav", + headerFormat: "WAV", + sampleFormat: "float", + options: ServerOptions.new.numInputBusChannels_(0).numOutputBusChannels_(2), + action: { |exitCode| + var file, samples; + try { + if(exitCode != 0) { Error("scsynth exited with " ++ exitCode).throw }; + file = SoundFile.openRead(output +/+ "sc3.wav"); + if(file.isNil) { Error("No rendered audio").throw }; + if(file.numChannels != 2) { Error("Expected two audio channels").throw }; + if(file.numFrames < 44100) { Error("Incomplete rendered audio").throw }; + samples = FloatArray.newClear(file.numFrames * file.numChannels); + file.readData(samples); + file.close; + samples.clump(2).flop.do { |channel| + if(channel.any { |sample| sample.isNaN or: { sample.abs == inf } }) { + Error("Invalid SC3 UGen output").throw; + }; + if(channel.abs.maxItem <= 0.00001) { Error("Silent SC3 UGen output").throw }; + }; + "SC3 plugins rendered successfully".postln; + 0.exit; + } { |error| + error.reportError; + 1.exit; + }; + } + ); +} { |error| + error.reportError; + 1.exit; +}; diff --git a/pkgs/development/interpreters/supercollider/wrapper.nix b/pkgs/development/interpreters/supercollider/wrapper.nix index 01f3c592ad9b..4eeff4c1c188 100644 --- a/pkgs/development/interpreters/supercollider/wrapper.nix +++ b/pkgs/development/interpreters/supercollider/wrapper.nix @@ -1,4 +1,5 @@ { + lib, symlinkJoin, makeWrapper, supercollider, @@ -12,10 +13,19 @@ symlinkJoin { nativeBuildInputs = [ makeWrapper ]; postBuild = '' + # --include-path adds to the paths from the user's language configuration, + # including an explicit -l configuration (as used by tidal-cycles-full). + if [ -d "$out/share/SuperCollider/Extensions" ]; then + wrapProgram "$out/bin/sclang" \ + --add-flags "--include-path $out/share/SuperCollider/Extensions" + fi + for exe in $out/bin/*; do - wrapProgram $exe \ - --set SC_PLUGIN_DIR "$out/lib/SuperCollider/plugins" \ - --set SC_DATA_DIR "$out/share/SuperCollider" + # scide launches sclang, and sclang launches scsynth, through PATH. + # The server already loads its core UGens from its compiled-in path. + wrapProgram "$exe" \ + --prefix PATH : "$out/bin" \ + --prefix SC_PLUGIN_PATH : "${lib.makeSearchPath "lib/SuperCollider/plugins" plugins}" done '';