Rust Visualizer for SuperCollider for Live Coding - Connection Issues (OSC/UDP) on macOS M1

Hey everyone,

I’m excited to share a project I’ve been working on: a real-time audio visualizer application written in Rust, designed to interact with SuperCollider. My goal is to send various analytical data (e.g., amplitude, frequency content, onset detection, etc.) from SuperCollider to the Rust application, which then processes and renders the visuals.

I’m currently developing this on a macOS M1 machine and using SuperCollider version [mention your SC version here, e.g., 3.13.0 or 3.14.0].

The core of my approach involves using Open Sound Control (OSC) messages transmitted over User Datagram Protocol (UDP) for communication between SuperCollider and the Rust visualizer.

Here’s a simplified overview of how I’m setting things up:


### SuperCollider Side (Sender)

I'm using `NetAddr` to send OSC messages. Here's a basic example of what I'm trying to do:

`Supercollider(
// Define the target address and port for the Rust application
~rustAppAddr = NetAddr("127.0.0.1", 57120); // Always use 127.0.0.1 for localhost testing first

// Example: Sending a simple amplitude value
(
a = {
    var in = SoundIn.ar(0); // Or whatever audio source you're using
    var amp = Amplitude.kr(in);
    ~rustAppAddr.sendMsg("/visualizer/amplitude", amp.asFloat);
    in;
}.play;
)

// Example: Sending data at a regular interval (e.g., for FFT bins)

SynthDef(\analyzeAndSend, { |out=0, bufnum|
    var in = SoundIn.ar(0);
    var chain = FreqAnalysis.kr(in); // Or PV_Chain.kr for FFT
    // ... process chain data ...
    // Send relevant data to Rust. For example, if you're sending an array:
    // ~rustAppAddr.sendMsg("/visualizer/fft", chain[0], chain[1], ..., chain[n]);
    // You'll need to flatten arrays or send individual values depending on your Rust receiver
    Out.ar(out, in);
}).add;

// s.sendMsg("/s_new", \analyzeAndSend, s.nextNodeID, 0, 0); // To start the synth
)
)

Rust Side (Receiver)

I’m using the rosc crate for OSC parsing and a standard UdpSocket for network communication. Here’s a simplified snippet of how I’m attempting to receive:

`Rustuse std::net::UdpSocket;
use rosc::{OscPacket, OscMessage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "127.0.0.1:57120"; // Must match the port SuperCollider is sending to
    let socket = UdpSocket::bind(addr)?;
    println!("Listening on {}", addr);

    let mut buf = [0u8; 1024]; // Buffer for incoming data

    loop {
        let (size, src) = socket.recv_from(&mut buf)?;
        let packet = rosc::decoder::decode(&buf[..size])?;

        match packet {
            OscPacket::Message(msg) => {
                println!("OSC Message: {:?}", msg);
                // Process the message, e.g., based on address
                if msg.addr == "/visualizer/amplitude" {
                    if let Some(arg) = msg.args.get(0) {
                        if let rosc::OscType::Float(amplitude) = arg {
                            println!("Received amplitude: {}", amplitude);
                            // Update your visualizer based on this value
                        }
                    }
                }
                // Add more logic for other message addresses
            },
            OscPacket::Bundle(bundle) => {
                println!("OSC Bundle: {:?}", bundle);
                // Handle bundles if you're sending them
            }
        }
    }
}`

The Problem: Connection Issues

I’m consistently running into connection issues where the Rust application is not receiving the OSC messages from SuperCollider. I’ve tried the following troubleshooting steps:

  1. Firewall/Security: On macOS, I’ve checked “Security & Privacy” (now “Privacy & Security” in newer versions) in System Settings. I’ve ensured that SuperCollider and my Rust application (once compiled) have the necessary network permissions. I’ve also temporarily disabled the macOS firewall to rule it out, with no change.
  2. IP Addresses: Ensured that both SuperCollider’s NetAddr and the Rust UdpSocket::bind are configured with the correct IP address (mostly 127.0.0.1 for local testing, but I’ve also tried my machine’s local network IP).
  3. Ports: Double-checked that the sender port in SuperCollider matches the receiver port in Rust.
  4. Packet Sniffing: I’ve used Wireshark to monitor network traffic. I’m not seeing any UDP packets on port 57120 originating from SuperCollider, which is puzzling. This suggests the issue might be on the SuperCollider side not even sending the packets, rather than Rust failing to receive them.
  5. Basic UDP Test (Rust → Rust): I’ve successfully sent and received UDP packets between two separate Rust applications on the same machine, confirming that my basic Rust UDP setup is working correctly.
  6. Basic OSC Test (SC → SC): Sending OSC messages between two different SuperCollider instances (or within the same) works fine.

My Questions/Seeking Advice:

  • Has anyone encountered similar issues when trying to send OSC from SuperCollider to an external application on macOS M1? Are there any specific considerations for Apple Silicon architecture?
  • Are there any common SuperCollider-specific pitfalls related to NetAddr or UDP sending that I might be overlooking, especially on macOS?
  • Could there be any macOS-specific system or network configuration that could be interfering, even if the firewall seems open? Perhaps related to network sandboxing or permissions.
  • Are there any debugging steps within SuperCollider itself that could help confirm whether the sendMsg call is actually attempting to send data out?
  • Are there any known quirks with rosc or the way Rust handles UDP sockets on macOS that I should be aware of in this context?
  • Is there a better, more robust way to send real-time numerical data from SuperCollider to a Rust application for visualization purposes? (e.g., shared memory, other network protocols?)

Any insights, suggestions, or debugging tips would be immensely appreciated! I’m really keen to get this visualizer up and running.

Thanks in advance for your help!

Best,
sdCarr

Cool project!

I think the problem here is that the server can send messages only using SendTrig or SendReply. Writing sendMsg inside a synth function will not work.

A few weeks ago, there was a question about sending OSC from the server to a non-SC client, and I posted an example. Maybe it will help. Sending OSC to other applications from Server - #2 by jamshark70

Or, send the data from the server to sclang, and then you can use sendMsg.

hjh

Hello @jamshark70 ;

I am sharing more detailed information about the project in relation to SuperCollider, the link to the repository for further documentation. Right now, my main interest is to provide Nannou with good communication with SC, and a set of rules that create visual representations that are as professional and complex as possible. At this point, I am still in the early stages, and as you can see in the screenshots, the visualisations are very simple and boring. If you or anyone else in the community has any ideas, I would be very happy to hear them.

link to github project:

This project is a real-time audio visualizer for SuperCollider, built with Rust and the Nannou creative-coding framework. Its
primary goal is to provide composers, sound artists, and researchers with a reactive and configurable tool to visualize musical
events. Communication between SuperCollider and the visualizer is handled via the OSC protocol.

Key Features:

  • Real-Time Visualization: Renders musical events (notes, drones, clusters) sent from SuperCollider.
  • OSC Communication: Listens for OSC messages, by default on 127.0.0.1:57124.
  • MIDI Support: Optional integration with MIDI devices.
  • Capture System: Allows saving the visual state to JSON and PNG files.
  • Flexible Configuration: Uses a config.toml file to control window settings, OSC parameters, and visual aesthetics.
  • Modular Architecture: The code is structured into modules for easier maintenance and extension.

Directory Structure

Here is a simplified directory tree with descriptions for each important component:

1 .
2 ├── Cargo.toml                # The Rust project manifest, defining dependencies and metadata.
3 ├── README.md                 # Main project documentation.
4 ├── config.toml               # Configuration file for the visualizer's behavior ("rules").
5 ├── scripts/                  # Scripts for various tasks (demos, tools, etc.).
6 │   ├── sc/                   # SuperCollider scripts to interact with the visualizer.
7 │   └── tools/                # Helper tools (launchers, checkers, etc.).
8 ├── src/                      # The project's source code.
9 │   ├── main.rs               # The main application entry point.

10 │ ├── app/ # Application logic (lifecycle, state, view).
11 │ ├── audio/ # Modules related to audio processing and input.
12 │ │ └── osc.rs # Handles OSC message parsing and event creation.
13 │ ├── visual/ # All logic related to the visuals.
14 │ │ ├── mod.rs
15 │ │ ├── audio_visual_mapping.rs # Translates audio data to visual properties.
16 │ │ ├── renderer.rs # The main renderer.
17 │ │ └── shaders/ # Shaders for GPU rendering.
18 │ ├── config.rs # Logic for loading and managing configuration.
19 │ ├── model.rs # Core application data structures.
20 │ ├── osc.rs # The core OSC server setup.
21 │ └── midi.rs # Logic for MIDI device interaction.
22 └── tests/ # Unit and integration tests.


Files for OSC Communication and “Rules”

  1. OSC Communication Files

The OSC communication is a two-way street: SuperCollider sends messages, and the Nannou application receives and interprets
them.

Nannou (Receiver - Rust)

  • src/osc.rs: This file sets up the main OSC server. It’s responsible for binding to the correct IP address and port (as
    defined in config.toml) and listening for incoming data packets. It acts as the entry point for all OSC communication.
  • src/audio/osc.rs: This file contains the logic for parsing the received OSC messages. It defines the expected OSC addresses
    (e.g., /note, /drone, /cluster) and extracts the arguments (like frequency, amplitude, duration) from each message. It then
    translates these raw OSC messages into internal application events that the rest of the program can understand.

SuperCollider (Sender - SCLang)

  • scripts/sc/setup_visualizer.scd: This is the configuration script for the SuperCollider side. It defines the NetAddr (the IP
    address and port of the Nannou application), effectively telling SuperCollider where to send the OSC messages. This is the
    first script a user should run in SuperCollider to establish the connection.
  • scripts/sc/ejemplos_live_coding.scd: This file provides practical examples of how to send OSC messages from SuperCollider to
    the visualizer. It demonstrates how to format the sendMsg calls for different event types like notes and drones, making it a
    great reference for users.
  1. “Rules” and Mapping Files

The “rules” of the visualizer dictate how it behaves and looks. These are defined by the configuration and the mapping logic.

  • config.toml: This is the primary “rules” file. It allows the user to define the visualizer’s behavior without changing any
    code. It contains parameters for:
    • [osc]: The host and port for communication.
    • [window]: The size and title of the application window.
    • [visual]: Visual parameters like quality, background style, and how long events stay on screen (event_fade_time).
    • [performance]: Limits on the number of simultaneous events to manage CPU/GPU load.
  • src/visual/audio_visual_mapping.rs: This file contains the logic that translates the musical events (received via OSC) into
    visual properties. It takes the data (e.g., a note’s frequency and amplitude) and, following the rules from config.toml,
    decides what the corresponding visual element’s color, size, position, and lifespan should be. This file is the creative core
    where the audio-to-visual translation happens.

Questions

  • General Question: I’m working on an audio visualizer in Rust with Nannou and SuperCollider. I’ve structured my project as shown in the directory tree above. Could anyone provide feedback on this structure and suggest any obvious improvements?
  • Specific OSC Question: I’m implementing the OSC communication in my project (see src/osc.rs and src/audio/osc.rs). I’m currently using the nannou_osc crate. Is this a good choice for a real-time application, or are there better alternatives to minimize latency?"
  • Specific Mapping Question: in my visual mapping module (src/visual/audio_visual_mapping.rs), I’m trying to make the mapping from audio parameters to visual properties more flexible. What would be a good strategy to define these mapping rules in my config.toml file so that a user can change them without recompiling the code?"
  • Specific Performance Question: “I’ve noticed that as the number of visual events increases, my application’s performance drops. I’m using shaders for rendering (see src/visual/shaders/). Are there any common optimization techniques in Nannou or wgpu that I should consider to improve performance, such as instancing or batch rendering?”

I hope this detailed report is helpful for all