Packages

A simple OSC transport layer for communicating with SuperCollider from Elixir. Provides low-level OSC messaging and node/group management for live coding and audio synthesis. Requires SuperCollider to be installed on your system.

Current section

Files

Jump to
waveform lib mix tasks waveform.install.ex
Raw

lib/mix/tasks/waveform.install.ex

defmodule Mix.Tasks.Waveform.Install do
@moduledoc """
Automatically installs SuperCollider and SuperDirt for your platform.
This task will:
1. Detect your operating system and package manager
2. Install SuperCollider (if not already installed)
3. Install the SuperDirt Quark
4. Verify the installation
## Usage
mix waveform.install
## Options
--skip-sc Skip SuperCollider installation (only install SuperDirt)
--skip-superdirt Skip SuperDirt installation (only install SuperCollider)
--force Reinstall even if already installed
## Supported Platforms
- **macOS**: Uses Homebrew
- **Linux**: Supports apt (Debian/Ubuntu), pacman (Arch), dnf (Fedora), zypper (openSUSE)
- **Windows**: Provides download instructions (manual installation required)
"""
@shortdoc "Install SuperCollider and SuperDirt"
use Mix.Task
require Logger
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
switches: [skip_sc: :boolean, skip_superdirt: :boolean, force: :boolean],
aliases: []
)
Mix.shell().info("""
╔═══════════════════════════════════════════════════════════╗
║ Waveform Installation Wizard ║
╚═══════════════════════════════════════════════════════════╝
This will install:
#{if opts[:skip_sc], do: "⊘", else: "✓"} SuperCollider (audio synthesis platform)
#{if opts[:skip_superdirt], do: "⊘", else: "✓"} SuperDirt (TidalCycles-compatible sampler)
""")
# Step 1: Install SuperCollider
sc_result =
if opts[:skip_sc] do
Mix.shell().info("Skipping SuperCollider installation...\n")
:ok
else
install_supercollider(opts[:force])
end
# Step 2: Install SuperDirt
sd_result =
if opts[:skip_superdirt] || sc_result == :error do
if sc_result == :error do
Mix.shell().error("Skipping SuperDirt installation due to SuperCollider error\n")
else
Mix.shell().info("Skipping SuperDirt installation...\n")
end
:ok
else
install_superdirt(opts[:force])
end
# Final summary
print_summary(sc_result, sd_result, opts)
end
# --- SuperCollider Installation ---
defp install_supercollider(force) do
Mix.shell().info("━━━ Installing SuperCollider ━━━\n")
if !force && supercollider_installed?() do
Mix.shell().info([:green, "✓ SuperCollider is already installed", :reset])
Mix.shell().info(" Use --force to reinstall\n")
:ok
else
platform = detect_platform()
install_sc_for_platform(platform)
end
end
defp supercollider_installed? do
sclang_path = get_sclang_path()
cond do
File.exists?(sclang_path) -> true
System.find_executable("sclang") -> true
true -> false
end
end
defp install_sc_for_platform({:macos, :homebrew}) do
Mix.shell().info("Platform: macOS (using Homebrew)\n")
case run_command("brew", ["install", "supercollider"]) do
{_, 0} ->
Mix.shell().info([:green, "\n✓ SuperCollider installed successfully", :reset])
:ok
{output, code} ->
Mix.shell().error("\n✗ Installation failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
end
defp install_sc_for_platform({:linux, :apt}) do
Mix.shell().info("Platform: Linux (using apt)\n")
Mix.shell().info("Running: sudo apt-get update && sudo apt-get install -y supercollider")
case run_command("sudo", ["apt-get", "update"]) do
{_, 0} ->
case run_command("sudo", ["apt-get", "install", "-y", "supercollider"]) do
{_, 0} ->
Mix.shell().info([:green, "\n✓ SuperCollider installed successfully", :reset])
:ok
{output, code} ->
Mix.shell().error("\n✗ Installation failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
{output, code} ->
Mix.shell().error("\n✗ apt-get update failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
end
defp install_sc_for_platform({:linux, :pacman}) do
Mix.shell().info("Platform: Linux (using pacman)\n")
Mix.shell().info("Running: sudo pacman -Sy --noconfirm supercollider")
case run_command("sudo", ["pacman", "-Sy", "--noconfirm", "supercollider"]) do
{_, 0} ->
Mix.shell().info([:green, "\n✓ SuperCollider installed successfully", :reset])
:ok
{output, code} ->
Mix.shell().error("\n✗ Installation failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
end
defp install_sc_for_platform({:linux, :dnf}) do
Mix.shell().info("Platform: Linux (using dnf)\n")
Mix.shell().info("Running: sudo dnf install -y supercollider")
case run_command("sudo", ["dnf", "install", "-y", "supercollider"]) do
{_, 0} ->
Mix.shell().info([:green, "\n✓ SuperCollider installed successfully", :reset])
:ok
{output, code} ->
Mix.shell().error("\n✗ Installation failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
end
defp install_sc_for_platform({:linux, :zypper}) do
Mix.shell().info("Platform: Linux (using zypper)\n")
Mix.shell().info("Running: sudo zypper install -y supercollider")
case run_command("sudo", ["zypper", "install", "-y", "supercollider"]) do
{_, 0} ->
Mix.shell().info([:green, "\n✓ SuperCollider installed successfully", :reset])
:ok
{output, code} ->
Mix.shell().error("\n✗ Installation failed (exit code: #{code})")
Mix.shell().info("Output: #{output}")
:error
end
end
defp install_sc_for_platform({:windows, _}) do
Mix.shell().info("Platform: Windows\n")
Mix.shell().info("""
⚠ Automatic installation is not available for Windows.
Please install SuperCollider manually:
1. Download from: https://supercollider.github.io/download
2. Run the installer
3. Run: mix waveform.install --skip-sc
""")
:manual
end
defp install_sc_for_platform({:macos, :no_homebrew}) do
Mix.shell().error("""
✗ Homebrew not found on macOS.
Please install Homebrew first:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Then run: mix waveform.install
Alternatively, download SuperCollider manually:
https://supercollider.github.io/download
""")
:error
end
defp install_sc_for_platform(:unknown) do
Mix.shell().error("""
✗ Could not detect your platform or package manager.
Please install SuperCollider manually:
https://supercollider.github.io/download
Then run: mix waveform.install --skip-sc
""")
:error
end
# --- SuperDirt Installation ---
defp install_superdirt(force) do
Mix.shell().info("\n━━━ Installing SuperDirt ━━━\n")
if !force && superdirt_installed?() do
Mix.shell().info([:green, "✓ SuperDirt is already installed", :reset])
Mix.shell().info(" Use --force to reinstall\n")
:ok
else
sclang_path = get_sclang_path()
path = if File.exists?(sclang_path), do: sclang_path, else: System.find_executable("sclang")
if is_nil(path) || !File.exists?(path) do
Mix.shell().error("""
✗ Could not find sclang executable.
SuperCollider must be installed first.
Run: mix waveform.install (without --skip-sc)
""")
:error
else
install_superdirt_via_sclang(path)
end
end
end
defp superdirt_installed? do
sclang_path = get_sclang_path()
path = if File.exists?(sclang_path), do: sclang_path, else: System.find_executable("sclang")
if is_nil(path) || !File.exists?(path) do
false
else
command =
"SuperDirt.class.notNil.if({ \"SUPERDIRT_INSTALLED\".postln }, { \"SUPERDIRT_NOT_INSTALLED\".postln }); 0.exit;"
case run_sclang_code(path, command) do
{output, _} ->
String.contains?(output, "SUPERDIRT_INSTALLED")
end
end
rescue
_ -> false
end
# Helper to run SuperCollider code via a temp file
defp run_sclang_code(sclang_path, code) do
# Create a temporary file
temp_file =
Path.join(System.tmp_dir!(), "waveform_install_#{:erlang.unique_integer([:positive])}.scd")
try do
# Write code to temp file
File.write!(temp_file, code)
# Execute sclang with the temp file
# Note: SuperDirt installation can take a while, so we use a long timeout
System.cmd(sclang_path, [temp_file], stderr_to_stdout: true)
after
# Clean up temp file
File.rm(temp_file)
end
rescue
error ->
{"Error executing sclang code: #{inspect(error)}", 1}
end
defp install_superdirt_via_sclang(sclang_path) do
Mix.shell().info("Installing SuperDirt Quark via sclang...")
Mix.shell().info("This may take a few minutes as it downloads samples...\n")
install_command = """
Quarks.install("SuperDirt");
"SUPERDIRT_QUARK_INSTALLED".postln;
0.exit;
"""
case run_sclang_code(sclang_path, install_command) do
{output, _} -> handle_superdirt_quark_install(output, sclang_path)
end
rescue
error ->
Mix.shell().error("\n✗ Exception during SuperDirt installation: #{inspect(error)}")
:error
end
defp handle_superdirt_quark_install(output, sclang_path) do
if String.contains?(output, "SUPERDIRT_QUARK_INSTALLED") ||
String.contains?(output, "already installed") do
Mix.shell().info([:green, "✓ SuperDirt Quark installed", :reset])
recompile_and_verify(sclang_path)
else
Mix.shell().error("\n✗ SuperDirt Quark installation failed")
Mix.shell().info("Output: #{output}")
:error
end
end
defp recompile_and_verify(sclang_path) do
Mix.shell().info("\nRecompiling SuperCollider class library...")
recompile_command = """
thisProcess.recompile;
"RECOMPILE_COMPLETE".postln;
2.wait;
0.exit;
"""
case run_sclang_code(sclang_path, recompile_command) do
{recompile_output, _} -> verify_recompile_and_installation(recompile_output)
end
end
defp verify_recompile_and_installation(recompile_output) do
if String.contains?(recompile_output, "RECOMPILE_COMPLETE") ||
String.contains?(recompile_output, "compile done") do
Mix.shell().info([:green, "✓ Class library recompiled", :reset])
if superdirt_installed?() do
Mix.shell().info([:green, "\n✓ SuperDirt installed successfully!", :reset])
:ok
else
Mix.shell().error("\n✗ SuperDirt installation could not be verified")
Mix.shell().info("Try running: mix waveform.doctor")
:error
end
else
Mix.shell().error("\n✗ Class library recompilation failed")
Mix.shell().info("Output: #{recompile_output}")
:error
end
end
# --- Platform Detection ---
defp detect_platform do
case :os.type() do
{:unix, :darwin} ->
if System.find_executable("brew") do
{:macos, :homebrew}
else
{:macos, :no_homebrew}
end
{:unix, _} ->
detect_linux_package_manager()
{:win32, _} ->
{:windows, nil}
end
end
defp detect_linux_package_manager do
cond do
System.find_executable("apt-get") -> {:linux, :apt}
System.find_executable("pacman") -> {:linux, :pacman}
System.find_executable("dnf") -> {:linux, :dnf}
System.find_executable("zypper") -> {:linux, :zypper}
true -> :unknown
end
end
# --- Helpers ---
defp get_sclang_path do
System.get_env("SCLANG_PATH") || default_sclang_path()
end
defp default_sclang_path do
case :os.type() do
{:unix, :darwin} ->
"/Applications/SuperCollider.app/Contents/MacOS/sclang"
{:unix, _} ->
Enum.find(["/usr/bin/sclang", "/usr/local/bin/sclang"], &File.exists?/1) ||
"/usr/bin/sclang"
{:win32, _} ->
Enum.find(
[
"C:\\Program Files\\SuperCollider\\sclang.exe",
"C:\\Program Files (x86)\\SuperCollider\\sclang.exe"
],
&File.exists?/1
) || "C:\\Program Files\\SuperCollider\\sclang.exe"
end
end
defp run_command(cmd, args) do
Mix.shell().info("$ #{cmd} #{Enum.join(args, " ")}")
System.cmd(cmd, args, stderr_to_stdout: true, into: IO.stream(:stdio, :line))
rescue
error ->
{"Command failed: #{inspect(error)}", 1}
end
defp print_summary(sc_result, sd_result, opts) do
print_summary_header()
print_sc_result(sc_result, opts)
print_sd_result(sd_result, opts)
print_summary_footer(sc_result, sd_result)
end
defp print_summary_header do
Mix.shell().info("\n")
Mix.shell().info(String.duplicate("═", 60))
Mix.shell().info("Installation Summary")
Mix.shell().info(String.duplicate("═", 60))
end
defp print_sc_result(result, opts) do
unless opts[:skip_sc] do
case result do
:ok ->
Mix.shell().info([:green, "✓ SuperCollider: Installed", :reset])
:manual ->
Mix.shell().info([:yellow, "⊘ SuperCollider: Manual installation required", :reset])
:error ->
Mix.shell().error("✗ SuperCollider: Installation failed")
end
end
end
defp print_sd_result(result, opts) do
unless opts[:skip_superdirt] do
case result do
:ok -> Mix.shell().info([:green, "✓ SuperDirt: Installed", :reset])
:error -> Mix.shell().error("✗ SuperDirt: Installation failed")
end
end
end
defp print_summary_footer(sc_result, sd_result) do
Mix.shell().info(String.duplicate("═", 60))
if sc_result == :ok && sd_result == :ok do
Mix.shell().info([:green, "\n🎉 Installation complete!", :reset])
Mix.shell().info("""
Next steps:
1. Verify installation: mix waveform.doctor
2. Try a demo: mix run demos/01_basic_patterns.exs
3. Start coding! See README.md for examples
""")
else
Mix.shell().info("\n⚠ Some installations failed. Please review the errors above.")
Mix.shell().info("Run 'mix waveform.doctor' to check your installation.\n")
end
end
end