Skip to content

Network Control & OSC

What is OSC?

OSC (Open Sound Control) is a protocol for communication between networked audio devices. Compared to MIDI, OSC offers higher precision, more flexible address spaces, and faster transmission.

System Architecture

Workflow:

  1. ESP32 creates a WiFi hotspot (SSID: ESP32-Audio-Module, password: audio123)
  2. Controller connects to the hotspot
  3. Sends OSC control messages to 192.168.4.1:8000
  4. ESP32 updates Faust DSP parameters in real-time

OSC-Controlled ESP32 Audio Program

ino
/**
 * @file audio-faust-audio-w-control.ino
 * @brief OSC-controlled Faust audio processing for ESP32 Audio Kit
 * @version 1.0
 * @date 2025-06-27
 * 
 * Features:
 * - WiFi Access Point mode
 * - OSC server for real-time parameter control
 * - Faust DSP audio processing
 * - Level control via OSC messages
 */

#include "AudioTools.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"
#include "AudioTools/AudioLibs/AudioFaust.h"
#include "low-pass-faust-controlled.h"
#include <WiFi.h>
#include <WiFiUdp.h>
#include "OSCMessage.h"
#include "OSCBundle.h"

// Network Configuration
const char* ssid = "ESP32-Audio-Module";
const char* password = "audio123";
const int oscPort = 8000;

// Audio Components
AudioBoardStream kit(AudioKitEs8388V1); 
FaustStream<mydsp> faust(kit);
StreamCopy copier(faust, kit);

// OSC Components
WiFiUDP udp;
OSCErrorCode error;

// Control Parameters
float levelValue = 1.0;        // Default level (0.0 - 1.0)
float cutoffFreqValue = 1000.0; // Default cutoff frequency (20 - 20000 Hz)
bool parameterChanged = false;

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
  
  Serial.println("Starting ESP32 Audio Module with OSC Control...");
  
  // Initialize WiFi Access Point
  setupWiFiAP();
  
  // Initialize OSC
  setupOSC();
  
  // Setup Faust Audio Processing
  setupAudio();
  
  Serial.println("System ready!");
  Serial.print("Connect to WiFi: ");
  Serial.println(ssid);
  Serial.print("Send OSC messages to: ");
  Serial.print(WiFi.softAPIP());
  Serial.print(":");
  Serial.println(oscPort);
  Serial.println("OSC Addresses:");
  Serial.println("  /level (float 0.0-1.0)");
  Serial.println("  /cutoff_freq (float 20-20000)");
}

void loop() {
  // Handle OSC messages
  handleOSC();
  
  // Update Faust parameters if changed
  if (parameterChanged) {
    updateFaustParameters();
    parameterChanged = false;
  }
  
  // Process audio
  copier.copy();
}

void setupWiFiAP() {
  Serial.println("Setting up WiFi Access Point...");
  
  WiFi.mode(WIFI_AP);
  bool result = WiFi.softAP(ssid, password);
  
  if (result) {
    Serial.println("WiFi AP started successfully");
    Serial.print("AP IP address: ");
    Serial.println(WiFi.softAPIP());
  } else {
    Serial.println("Failed to start WiFi AP");
  }
  
  delay(1000);
}

void setupOSC() {
  Serial.println("Starting OSC server...");
  udp.begin(oscPort);
  Serial.print("OSC server listening on port: ");
  Serial.println(oscPort);
}

void setupAudio() {
  Serial.println("Initializing audio system...");
  
  // Setup Faust
  auto cfg = faust.defaultConfig();
  faust.begin(cfg);
  
  // Setup I2S
  auto cfg_i2s = kit.defaultConfig(RXTX_MODE);
  cfg_i2s.sample_rate = cfg.sample_rate; 
  cfg_i2s.channels = cfg.channels;
  cfg_i2s.bits_per_sample = cfg.bits_per_sample;
  cfg_i2s.input_device = ADC_INPUT_LINE2;
  
  kit.begin(cfg_i2s);
  
  Serial.println("Audio system initialized");
}

void handleOSC() {
  OSCMessage msg;
  int size = udp.parsePacket();
  
  if (size > 0) {
    while (size--) {
      msg.fill(udp.read());
    }
    
    if (!msg.hasError()) {
      // Handle level control
      if (msg.match("/level")) {
        if (msg.isFloat(0)) {
          float newLevel = msg.getFloat(0);
          if (newLevel >= 0.0 && newLevel <= 1.0) {
            levelValue = newLevel;
            parameterChanged = true;
            Serial.print("Level updated: ");
            Serial.println(levelValue);
          } else {
            Serial.println("Level value out of range (0.0-1.0)");
          }
        } else {
          Serial.println("Level parameter must be float");
        }
      }
      // Handle cutoff frequency control
      else if (msg.match("/cutoff_freq")) {
        if (msg.isFloat(0)) {
          float newCutoff = msg.getFloat(0);
          if (newCutoff >= 20.0 && newCutoff <= 20000.0) {
            cutoffFreqValue = newCutoff;
            parameterChanged = true;
            Serial.print("Cutoff frequency updated: ");
            Serial.print(cutoffFreqValue);
            Serial.println(" Hz");
          } else {
            Serial.println("Cutoff frequency out of range (20-20000 Hz)");
          }
        } else {
          Serial.println("Cutoff frequency parameter must be float");
        }
      }
      else {
        Serial.print("Unknown OSC address: ");
        char addr[50];
        msg.getAddress(addr);
        Serial.println(addr);
      }
    } else {
      error = msg.getError();
      Serial.print("OSC Error: ");
      Serial.println(error);
    }
  }
}

void updateFaustParameters() {
  // Use the correct setLabelValue API to control Faust parameters
  faust.setLabelValue("Level", levelValue);
  faust.setLabelValue("Cutoff Frequency", cutoffFreqValue);
  
  Serial.print("Faust parameters updated - Level: ");
  Serial.print(levelValue);
  Serial.print(", Cutoff Frequency: ");
  Serial.print(cutoffFreqValue);
  Serial.println(" Hz");
}

void printNetworkInfo() {
  Serial.println("\n=== Network Information ===");
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("IP Address: ");
  Serial.println(WiFi.softAPIP());
  Serial.print("OSC Port: ");
  Serial.println(oscPort);
  Serial.println("OSC Commands:");
  Serial.println("  /level <float>  - Set output level (0.0-1.0)");
  Serial.println("============================\n");
}

Key Features

  • WiFi AP Mode: ESP32 acts as hotspot, no router needed
  • OSC Server: Listens on port 8000
  • Controllable Parameters:
    • /level (0.0-1.0) — output volume
    • /cutoff_freq (20-20000 Hz) — filter cutoff frequency

Python OSC Test Client

py
#!/usr/bin/env python3
"""
OSC Test Client for ESP32 Audio Module

This script demonstrates how to send OSC messages to control the audio module.
Install python-osc library first: pip install python-osc

Usage:
python test-osc-client.py
"""

import time
import math
from pythonosc import udp_client

# ESP32 Audio Module configuration
ESP32_IP = "192.168.4.1"  # Default ESP32 AP IP
OSC_PORT = 8000

def main():
    print("OSC Test Client for ESP32 Audio Module")
    print(f"Connecting to {ESP32_IP}:{OSC_PORT}")
    print("Make sure you're connected to WiFi network: ESP32-Audio-Module")
    print("Password: audio123\n")
    
    # Create OSC client
    client = udp_client.SimpleUDPClient(ESP32_IP, OSC_PORT)
    
    try:
        # Test 1: Static level control
        print("Test 1: Setting static levels...")
        levels = [0.0, 0.25, 0.5, 0.75, 1.0]
        
        for level in levels:
            print(f"Setting level to {level}")
            client.send_message("/level", level)
            time.sleep(2)
        
        # Test 2: Cutoff frequency control
        print("\nTest 2: Setting cutoff frequencies...")
        frequencies = [100, 500, 1000, 5000, 10000]
        
        for freq in frequencies:
            print(f"Setting cutoff frequency to {freq} Hz")
            client.send_message("/cutoff_freq", freq)
            time.sleep(2)
        
        # Test 3: Smooth level sweep
        print("\nTest 3: Smooth level sweep...")
        print("Performing 10-second level sweep from 0.0 to 1.0")
        
        duration = 10.0  # seconds
        start_time = time.time()
        
        while True:
            elapsed = time.time() - start_time
            if elapsed >= duration:
                break
            
            # Calculate level (0.0 to 1.0 over duration)
            level = elapsed / duration
            
            print(f"Level: {level:.3f}", end='\r')
            client.send_message("/level", level)
            time.sleep(0.1)  # 10Hz update rate
        
        # Test 4: Frequency sweep
        print(f"\n\nTest 4: Cutoff frequency sweep...")
        print("Performing 10-second frequency sweep from 100Hz to 5000Hz")
        
        duration = 10.0  # seconds
        start_time = time.time()
        
        while True:
            elapsed = time.time() - start_time
            if elapsed >= duration:
                break
            
            # Calculate frequency (logarithmic sweep)
            progress = elapsed / duration
            freq = 100 * math.pow(50, progress)  # 100Hz to 5000Hz
            
            print(f"Frequency: {freq:.0f} Hz", end='\r')
            client.send_message("/cutoff_freq", freq)
            time.sleep(0.1)  # 10Hz update rate
        
        # Test 5: Sine wave modulation
        print(f"\n\nTest 5: Sine wave level modulation...")
        print("Performing 10-second sine wave modulation")
        
        duration = 10.0  # seconds
        frequency = 0.5  # Hz
        start_time = time.time()
        
        while True:
            elapsed = time.time() - start_time
            if elapsed >= duration:
                break
            
            # Calculate level using sine wave (0.2 to 0.8 range)
            level = 0.5 + 0.3 * math.sin(2 * math.pi * frequency * elapsed)
            
            print(f"Level: {level:.3f}", end='\r')
            client.send_message("/level", level)
            time.sleep(0.05)  # 20Hz update rate
        
        # Reset to defaults
        print(f"\n\nResetting to defaults...")
        client.send_message("/level", 1.0)
        client.send_message("/cutoff_freq", 1000.0)
        
        print("Tests completed successfully!")
        
    except KeyboardInterrupt:
        print("\n\nTest interrupted by user")
        print("Resetting level to 1.0...")
        client.send_message("/level", 1.0)
        
    except Exception as e:
        print(f"\nError: {e}")
        print("Make sure:")
        print("1. ESP32 Audio Module is powered on")
        print("2. You're connected to WiFi: ESP32-Audio-Module")
        print("3. ESP32 IP address is correct")

def interactive_mode():
    """Interactive mode for manual OSC control"""
    print("\n" + "="*50)
    print("Interactive OSC Control Mode")
    print("="*50)
    print("Commands:")
    print("  level <value>     - Set level (0.0-1.0)")
    print("  cutoff <freq>     - Set cutoff frequency (20-20000 Hz)")
    print("  freq <freq>       - Alias for cutoff")
    print("  reset             - Reset to defaults")
    print("  quit              - Exit")
    print("="*50)
    
    client = udp_client.SimpleUDPClient(ESP32_IP, OSC_PORT)
    
    while True:
        try:
            cmd = input("\nOSC> ").strip().split()
            
            if not cmd:
                continue
                
            if cmd[0].lower() == 'quit':
                break
                
            elif cmd[0].lower() == 'level':
                if len(cmd) != 2:
                    print("Usage: level <value>")
                    continue
                    
                try:
                    level = float(cmd[1])
                    if 0.0 <= level <= 1.0:
                        client.send_message("/level", level)
                        print(f"Sent: /level {level}")
                    else:
                        print("Level must be between 0.0 and 1.0")
                except ValueError:
                    print("Invalid level value")
                    
            elif cmd[0].lower() in ['cutoff', 'freq']:
                if len(cmd) != 2:
                    print("Usage: cutoff <frequency>")
                    continue
                    
                try:
                    freq = float(cmd[1])
                    if 20.0 <= freq <= 20000.0:
                        client.send_message("/cutoff_freq", freq)
                        print(f"Sent: /cutoff_freq {freq}")
                    else:
                        print("Frequency must be between 20 and 20000 Hz")
                except ValueError:
                    print("Invalid frequency value")
                    
            elif cmd[0].lower() == 'reset':
                client.send_message("/level", 1.0)
                client.send_message("/cutoff_freq", 1000.0)
                print("Sent: Reset to defaults (level=1.0, cutoff=1000Hz)")
                    
            else:
                print(f"Unknown command: {cmd[0]}")
                
        except KeyboardInterrupt:
            break
        except EOFError:
            break
    
    print("\nExiting interactive mode...")

if __name__ == "__main__":
    import sys
    
    # Check if python-osc is installed
    try:
        import pythonosc
    except ImportError:
        print("Error: python-osc library not found")
        print("Install it with: pip install python-osc")
        sys.exit(1)
    
    if len(sys.argv) > 1 and sys.argv[1] == "-i":
        interactive_mode()
    else:
        main()
        
        # Ask if user wants interactive mode
        try:
            response = input("\nEnter interactive mode? (y/n): ").strip().lower()
            if response in ['y', 'yes']:
                interactive_mode()
        except (KeyboardInterrupt, EOFError):
            print("\nGoodbye!")

Usage

bash
# Install dependencies
pip install python-osc

# Run automated tests
python test-osc-client.py

# Interactive mode
python test-osc-client.py -i

Processing GUI Controller

pde
/**
 * Processing OSC Controller for ESP32 Audio Module
 * 
 * This sketch provides a graphical interface with knobs to control
 * the Faust audio parameters via OSC messages.
 * 
 * Required Libraries:
 * - ControlP5 (install via Tools > Manage Tools > Libraries)
 * - oscP5 (install via Tools > Manage Tools > Libraries)
 * 
 * Usage:
 * 1. Connect to WiFi network: ESP32-Audio-Module (password: audio123)
 * 2. Run this sketch
 * 3. Use the knobs to control audio parameters in real-time
 */

import controlP5.*;
import oscP5.*;
import netP5.*;

// OSC Configuration
String ESP32_IP = "192.168.4.1";  // Default ESP32 AP IP
int OSC_PORT = 8000;

// Control objects
ControlP5 cp5;
OscP5 oscP5;
NetAddress esp32Address;

// Parameter values
float levelValue = 1.0;        // 0.0 - 1.0
float cutoffFreqValue = 1000.0; // 20 - 20000 Hz

// UI Configuration
int windowWidth = 800;
int windowHeight = 600;
boolean connected = false;
long lastHeartbeat = 0;

// Colors
color bgColor = color(20, 25, 35);
color panelColor = color(40, 45, 55);
color textColor = color(220, 220, 220);
color accentColor = color(80, 150, 255);

void setup() {
  size(800, 600);
  
  // Initialize OSC
  oscP5 = new OscP5(this, 12000); // Listen on port 12000 for responses
  esp32Address = new NetAddress(ESP32_IP, OSC_PORT);
  
  // Initialize ControlP5
  cp5 = new ControlP5(this);
  
  setupUI();
  
  println("ESP32 Audio Controller Started");
  println("Target: " + ESP32_IP + ":" + OSC_PORT);
  println("Make sure you're connected to WiFi: ESP32-Audio-Module");
}

void setupUI() {
  // Level Control Knob
  cp5.addKnob("level")
     .setPosition(150, 200)
     .setSize(150, 150)
     .setRange(0.0, 1.0)
     .setValue(levelValue)
     .setColorForeground(color(accentColor))
     .setColorBackground(color(panelColor))
     .setColorActive(color(accentColor))
     .setDragDirection(Knob.VERTICAL);
  
  // Cutoff Frequency Control Knob (logarithmic scale)
  cp5.addKnob("cutoff_freq")
     .setPosition(500, 200)
     .setSize(150, 150)
     .setRange(log(20), log(20000))  // Logarithmic range
     .setValue(log(cutoffFreqValue))
     .setColorForeground(color(accentColor))
     .setColorBackground(color(panelColor))
     .setColorActive(color(accentColor))
     .setDragDirection(Knob.VERTICAL);
  
  // Reset Button
  cp5.addBang("reset")
     .setPosition(350, 450)
     .setSize(100, 40)
     .setColorForeground(color(200, 100, 100))
     .setColorBackground(color(panelColor));
  
  // Connection Test Button
  cp5.addBang("ping")
     .setPosition(350, 500)
     .setSize(100, 40)
     .setColorForeground(color(100, 200, 100))
     .setColorBackground(color(panelColor));
}

void draw() {
  background(bgColor);
  
  // Title
  fill(textColor);
  textAlign(CENTER);
  textSize(24);
  text("ESP32 Audio Controller", width/2, 50);
  
  textSize(14);
  text("Faust Low-Pass Filter Control", width/2, 75);
  
  // Connection status
  fill(connected ? color(100, 200, 100) : color(200, 100, 100));
  textAlign(RIGHT);
  textSize(12);
  text("Status: " + (connected ? "Connected" : "Disconnected"), width - 20, 30);
  
  // Parameter labels and values
  fill(textColor);
  textAlign(CENTER);
  textSize(16);
  
  // Level
  text("Level", 225, 190);
  text(nf(levelValue, 1, 3), 225, 380);
  
  // Cutoff Frequency
  text("Cutoff Frequency", 575, 190);
  text(int(cutoffFreqValue) + " Hz", 575, 380);
  
  // Instructions
  textAlign(LEFT);
  textSize(12);
  fill(color(150, 150, 150));
  text("Instructions:", 50, 450);
  text("• Connect to WiFi: ESP32-Audio-Module (password: audio123)", 50, 470);
  text("• Drag knobs to control audio parameters", 50, 485);
  text("• Click 'ping' to test connection", 50, 500);
  text("• Click 'reset' to restore default values", 50, 515);
  
  // Network info
  textAlign(RIGHT);
  text("Target: " + ESP32_IP + ":" + OSC_PORT, width - 50, 450);
  
  // Update connection status
  if (millis() - lastHeartbeat > 5000) {
    connected = false;
  }
}

void controlEvent(ControlEvent event) {
  if (event.getController().getName().equals("level")) {
    levelValue = event.getController().getValue();
    sendOSC("/level", levelValue);
  }
  else if (event.getController().getName().equals("cutoff_freq")) {
    // Convert from logarithmic scale
    float logValue = event.getController().getValue();
    cutoffFreqValue = exp(logValue);
    sendOSC("/cutoff_freq", cutoffFreqValue);
  }
  else if (event.getController().getName().equals("reset")) {
    resetParameters();
  }
  else if (event.getController().getName().equals("ping")) {
    testConnection();
  }
}

void sendOSC(String address, float value) {
  OscMessage msg = new OscMessage(address);
  msg.add(value);
  oscP5.send(msg, esp32Address);
  
  println("Sent OSC: " + address + " " + value);
  lastHeartbeat = millis();
  connected = true;
}

void resetParameters() {
  levelValue = 1.0;
  cutoffFreqValue = 1000.0;
  
  // Update UI
  cp5.getController("level").setValue(levelValue);
  cp5.getController("cutoff_freq").setValue(log(cutoffFreqValue));
  
  // Send OSC messages
  sendOSC("/level", levelValue);
  sendOSC("/cutoff_freq", cutoffFreqValue);
  
  println("Parameters reset to defaults");
}

void testConnection() {
  println("Testing connection...");
  sendOSC("/level", levelValue);
  sendOSC("/cutoff_freq", cutoffFreqValue);
}

// Handle incoming OSC messages (if any)
void oscEvent(OscMessage msg) {
  println("Received OSC: " + msg.addrPattern() + " " + msg.get(0).floatValue());
  connected = true;
  lastHeartbeat = millis();
}

// Keyboard shortcuts
void keyPressed() {
  switch(key) {
    case 'r':
    case 'R':
      resetParameters();
      break;
    case 'p':
    case 'P':
      testConnection();
      break;
    case '1':
      levelValue = 0.25;
      cp5.getController("level").setValue(levelValue);
      sendOSC("/level", levelValue);
      break;
    case '2':
      levelValue = 0.5;
      cp5.getController("level").setValue(levelValue);
      sendOSC("/level", levelValue);
      break;
    case '3':
      levelValue = 0.75;
      cp5.getController("level").setValue(levelValue);
      sendOSC("/level", levelValue);
      break;
    case '4':
      levelValue = 1.0;
      cp5.getController("level").setValue(levelValue);
      sendOSC("/level", levelValue);
      break;
  }
}

// Mouse wheel for fine control
void mouseWheel(MouseEvent event) {
  // Check if mouse is over a knob for fine adjustment
  float e = event.getCount();
  
  // Level knob area
  if (mouseX > 150 && mouseX < 300 && mouseY > 200 && mouseY < 350) {
    levelValue = constrain(levelValue - e * 0.01, 0.0, 1.0);
    cp5.getController("level").setValue(levelValue);
    sendOSC("/level", levelValue);
  }
  
  // Cutoff frequency knob area
  if (mouseX > 500 && mouseX < 650 && mouseY > 200 && mouseY < 350) {
    float factor = 1.0 + (e * 0.05);
    cutoffFreqValue = constrain(cutoffFreqValue / factor, 20, 20000);
    cp5.getController("cutoff_freq").setValue(log(cutoffFreqValue));
    sendOSC("/cutoff_freq", cutoffFreqValue);
  }
}

Features

  • Two knobs: Level and Cutoff Frequency
  • Connection status indicator
  • Keyboard shortcuts: R=reset, P=ping, 1-4=preset levels
  • Mouse wheel for fine control

Required Libraries

  • ControlP5: GUI controls library
  • oscP5: OSC communication library

Install via Processing IDE: Tools > Manage Tools > Libraries.