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:
- ESP32 creates a WiFi hotspot (SSID:
ESP32-Audio-Module, password:audio123) - Controller connects to the hotspot
- Sends OSC control messages to
192.168.4.1:8000 - 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 -iProcessing 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.