Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

‚floʊːz

An openFrameworks application that generates stereo audio and synchronises an LED strobe light over WiFi/OSC. A modulation system lets any parameter be driven by built-in LFOs or incoming OSC values.

Features

  • Stereo audio synthesis with independent L/R waveforms and frequency
  • Per-channel volume envelopes (looping, multiple waveform shapes, bezier editing)
  • Modulation matrix: LFOs or external OSC inputs routable to any parameter with curve shaping
  • WS2812 LED strobe control via OSC/UDP (Wemos D1 Mini firmware included)
  • Keyboard-navigable GUI with live parameter bars
  • JSON preset system with automatic preset transitions (threshold or time-based)
  • Leap/blob tracker integration: spatial blob views, occupation / activity / stress accumulators

Requirements

Host (Linux)

  • openFrameworks ≥ 20260309 (gcc6 release build)
  • ofxOsc addon (included with oF)

Strobe hardware

  • Wemos D1 Mini (ESP8266)
  • WS2812 LED strip on pin D7 (GPIO13)
  • Arduino IDE with FastLED and ArduinoOSC (by hideakitai) libraries

Building

cd apps/audioOutputExample
make
make RunRelease

Project structure

src/
  ofApp.h              — class declaration, all structs
  ofApp.cpp            — setup, update, draw, event handlers
  ofApp_audio.cpp      — audio synthesis, wave tables, volume envelopes
  ofApp_mod.cpp        — LFOs, mod matrix, OSC receiver
  ofApp_strobe.cpp     — OSC strobe sender
  ofApp_gui.cpp        — all drawing / GUI
  ofApp_settings.cpp   — JSON settings, preset management
wemos/
  strobe_control/
    strobe_control.ino — ESP8266 firmware
bin/data/
  settings.json        — all configuration and presets

Configuration (bin/data/settings.json)

Network

"strobe":      { "ip": "192.168.178.21", "port": 8000 },
"oscReceiver": { "port": 9000 }
  • strobe — IP and UDP port of the Wemos D1 Mini running the strobe firmware.
  • oscReceiver — UDP port the app listens on for incoming modulation values.

Presets

All sound and strobe parameters live inside presets. The active preset is selected with the keyboard UI. Multiple presets can coexist; saving with Shift+S creates a timestamped copy.


Audio signal path

targetFrequency ──► Phase L ──► waveformL ──► volumeL × volumeEnvL ──► Left out
                └──► Phase R ──► waveformR ──► volumeR × volumeEnvR ──► Right out
                     (freq × freqMultiplierR)
                     (+ phaseOffset lookup offset)

Waveform types (waveformL / waveformR)

Value Shape
0 Sine
1 Square
2 Sawtooth
3 Bezier (drag control points on screen)
4 Triangle
5 Noise

Volume envelopes (volWaveformL / volWaveformR)

Each channel has a looping volume envelope independent of the audio frequency.

Value Shape
0 Static (fixed value)
1 Sine
2 Square
3 Sawtooth
4 Bezier (drag control points on screen)
5 Triangle

volLoopSecL/R — loop length in seconds (0.1 – 60). volEnvMinL/R, volEnvMaxL/R — remap envelope output to a sub-range (e.g. 0.2–0.8 keeps the volume from going silent).


Modulation system

LFOs

Four LFOs (modLfo0modLfo3) run continuously. Each has:

Parameter Key Description
Type modLfo{n}Type 0=static 1=sine 2=square 3=saw 4=triangle
Rate modLfo{n}Rate Hz (0.01 – 20)
Static value modLfo{n}Static Output when type = 0

OSC inputs

External float values arriving on the receiver port are registered via oscInputs in the preset. Raw values are stored without remapping — all shaping is done per-route.

"oscInputs": [
  { "address": "/slider/1" },
  { "address": "/knob/freq", "argIndex": 1, "default": 0.5 }
]
Field Description
address OSC address to listen for
argIndex Which argument of the OSC message to use (default 0)
default Fallback value when no message has been received yet

Mod routes

Routes connect a source (LFO or OSC input) to a target parameter. They live in modRoutes inside the preset and are applied in order. Edit settings.json directly to configure them.

LFO source:

{ "lfo": 0, "target": "volumeL", "depth": 1.0, "multiply": true }

OSC source:

{
  "oscAddress": "/lidar/closest/dist", "target": "volumeL", "depth": 1.0, "multiply": false,
  "remapInMin": 0, "remapInMax": 3000, "remapOutMin": 0, "remapOutMax": 1, "remapCurve": "pow", "remapExponent": 0.5
}
Field Description
lfo LFO index 0–3 (used when oscAddress is absent)
oscAddress OSC address string (takes priority over lfo)
argIndex Which OSC argument to read (default 0)
target Parameter name (any key from the keyboard UI)
multiply true = base × value false = base + (value − 0.5) × 2 × depth
depth Modulation depth for add mode
remapInMin/Max Input range — maps raw source value to normalised [0,1]
remapOutMin/Max Output range — maps normalised value to final output
remapCurve Curve shaping: linear, sin, sqrt, quad, cubic, smoothstep, pow
remapExponent Exponent for pow curve (default 2)

Each route carries its own remap. The raw source value is normalised via remapInMin/Max, shaped by remapCurve, and scaled to remapOutMin/Max. This lets the same input drive multiple targets with independent ranges and curves. Default remap (0→1 linear) is a passthrough.

Modulated parameters are highlighted in cyan in the keyboard GUI. The bar shows the effective (post-modulation) position; a small o marks the base value.


Leap / blob tracker integration

The app receives spatial tracking data via OSC on the receiver port. Blob data is automatically exposed in the mod matrix under the following addresses:

Address Description
/leap/stats arg 0/1 Occupation / activity (% of foreground / changed pixels)
/leap/blob/<i>/position args 0/1/2 dist (m), height (m), angle (deg)
/leap/blob/<i>/velocity args 0/1/2/3 vx, vy, vz (m/s), speed (m/s)

Blobs are sorted by dist each frame. Only blobs present in the latest bundle are kept.

Active blob routing

Three named blobs are derived each frame and available as mod route sources:

Key prefix Description
/leap/active/closest/ Blob with smallest dist
/leap/active/x/ Average of blobs on one angular side
/leap/active/y/ Average of blobs on the opposite side

Each exposes dist, height, angle, speed, vx, vy, vz as argIndex 0. Add an oscInputs entry with a matching address to apply remapping or a curve.

Accumulators

oscInputValues key Description
/leap/occupation#0 Accumulates each frame, decays at leapOccupationDecay /s
/leap/activity#0 Raw per-frame value
/leap/stress#0 Sums activity over time, decays at leapStressDecay /s

Decay rates are adjustable in the keyboard GUI under the leap section.


Lidar integration

The app receives lidar cluster data via OSC on the receiver port (same port as Leap). All values are automatically exposed in the mod matrix.

Incoming messages

Address Arguments Description
/lidar/clusters count (int) Number of tracked clusters in this bundle; resets the batch
/lidar/cluster id (int), angle (float, deg), dist (float, mm), pointCount (int) One cluster per message
/lidar/occupation value (float) Occupation metric (0–1)
/lidar/activity value (float) Activity metric (0–1)

Send /lidar/clusters first in each bundle, followed by one /lidar/cluster per cluster.

Derived mod-matrix addresses

Clusters are sorted by distance each frame. The closest cluster is exposed as:

Address Description
/lidar/closest/dist#0 Distance of closest cluster (mm, raw)
/lidar/closest/angle#0 Angle of closest cluster (degrees)
/lidar/closest/points#0 Point count of closest cluster
/lidar/clusters#0 Total cluster count
/lidar/occupation#0 Occupation value
/lidar/activity#0 Activity value

Add oscInputs entries with matching addresses to apply range remapping or curve shaping before the values reach mod routes. Example — map dist from 0–3 m to 0–1:

{ "address": "/lidar/closest/dist", "inMin": 0, "inMax": 3000, "outMin": 0, "outMax": 1 }

Visualisation

A top-down polar view (500 × 500 px) is drawn below the Leap blob views. Each cluster is plotted at dist·sin(angle) / dist·cos(angle) (North = forward). Occupation and activity bars are shown along the bottom edge. Range: 5 m.


Preset transitions

Each preset can define automatic transitions in a "transitions" array. The first matching condition triggers a switch.

"transitions": [
  {
    "condition": "stress",
    "threshold": 15.0,
    "target": "energetic",
    "transitionDuration": 3.0
  },
  {
    "condition": "time",
    "duration": 60.0,
    "target": ["calm", "default", "slow"],
    "transitionDuration": 5.0
  }
]
Field Description
condition "stress" triggers when stress ≥ threshold; "time" triggers after duration seconds
threshold Stress value that fires the transition (stress condition)
duration Seconds before switching (time condition)
target Preset name string, or array of names for a random pick
transitionDuration Seconds to crossfade all parameters from old to new preset (default 0 = instant)

Stress resets to 0 on every preset load. A 1-second cooldown prevents cascading switches. During a crossfade all numeric parameters (volume, frequency, envelope ranges, strobe settings, mod depths, etc.) interpolate smoothly using a smoothstep curve.


Strobe

The strobe frequency is targetFrequency × strobeRatio. Phase is derived from the audio oscillator so the strobe stays locked to the sound.

Parameter Description
strobeRatio Strobe freq relative to audio freq
strobeDutyRatio On-time fraction (0.01 – 1.0)
strobePhaseUs Additional phase offset in microseconds
strobeBrightness FastLED global brightness (0 – 255)

LEDs are always full-white; the host sends fixed (255,255,255,sync=0) over the wire so the Wemos firmware needs no changes.


Keyboard controls

Key Action
↑ / ↓ Move cursor between parameters
← / → Decrease / increase value by one step
Shift + ← / → Decrease / increase by large step
s Save current preset
Shift + S Save as new timestamped preset
e Stop audio
r Start audio

Strobe firmware (strobe_control.ino)

Flash to a Wemos D1 Mini. Edit WIFI_SSID and WIFI_PASS at the top of the file. The device prints its IP address on boot via Serial (115200 baud) — set that IP as strobe.ip in settings.json.

OSC messages received on port 8000:

Address Arguments Description
/strobe/state onUs offUs syncPosUs r g b brightness sync Full state update (sent at 50 Hz by host)
/strobe/status (none) Print current settings to Serial

The firmware uses a hardware timer (100 µs resolution) for strobe timing, so WiFi latency never delays transitions.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages