mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 01:41:43 +00:00
175 lines
5 KiB
Markdown
175 lines
5 KiB
Markdown
# Audio-Reactive Reference
|
|
|
|
Patterns for driving visuals from audio — spectrum analysis, beat detection, envelope following.
|
|
|
|
## Audio Input
|
|
|
|
```python
|
|
# Live input from audio interface
|
|
audio_in = root.create(audiodeviceinCHOP, 'audio_in')
|
|
audio_in.par.rate = 44100
|
|
|
|
# OR: from audio file (for testing)
|
|
audio_file = root.create(audiofileinCHOP, 'audio_in')
|
|
audio_file.par.file = '/path/to/track.wav'
|
|
audio_file.par.play = True
|
|
audio_file.par.repeat = 'on' # NOT par.loop
|
|
audio_file.par.playmode = 'locked'
|
|
```
|
|
|
|
---
|
|
|
|
## Audio Band Extraction (Verified TD 2025.32460)
|
|
|
|
Use `audiofilterCHOP` for band separation (NOT `selectCHOP` by channel index):
|
|
|
|
```python
|
|
# Audio input
|
|
af = root.create(audiofileinCHOP, 'audio_in')
|
|
af.par.file = path
|
|
af.par.play = True
|
|
af.par.repeat = 'on'
|
|
af.par.playmode = 'locked'
|
|
|
|
# Low band: lowpass @ 250Hz
|
|
flt_low = root.create(audiofilterCHOP, 'flt_low')
|
|
flt_low.par.filter = 'lowpass'
|
|
flt_low.par.cutofffrequency = 250
|
|
flt_low.par.rolloff = 2
|
|
flt_low.inputConnectors[0].connect(af)
|
|
|
|
# Mid band: highpass@250 → lowpass@4000
|
|
flt_mid_hp = root.create(audiofilterCHOP, 'flt_mid_hp')
|
|
flt_mid_hp.par.filter = 'highpass'
|
|
flt_mid_hp.par.cutofffrequency = 250
|
|
flt_mid_hp.par.rolloff = 2
|
|
flt_mid_hp.inputConnectors[0].connect(af)
|
|
|
|
flt_mid_lp = root.create(audiofilterCHOP, 'flt_mid_lp')
|
|
flt_mid_lp.par.filter = 'lowpass'
|
|
flt_mid_lp.par.cutofffrequency = 4000
|
|
flt_mid_lp.par.rolloff = 2
|
|
flt_mid_lp.inputConnectors[0].connect(flt_mid_hp)
|
|
|
|
# High band: highpass @ 4000Hz
|
|
flt_high = root.create(audiofilterCHOP, 'flt_high')
|
|
flt_high.par.filter = 'highpass'
|
|
flt_high.par.cutofffrequency = 4000
|
|
flt_high.par.rolloff = 2
|
|
flt_high.inputConnectors[0].connect(af)
|
|
|
|
# Per-band: RMS → lag → gain → clamp
|
|
for name, filt in [('low', flt_low), ('mid', flt_mid_lp), ('high', flt_high)]:
|
|
rms = root.create(analyzeCHOP, f'rms_{name}')
|
|
rms.par.function = 'rmspower' # NOT 'rms'
|
|
rms.inputConnectors[0].connect(filt)
|
|
|
|
lag = root.create(lagCHOP, f'lag_{name}')
|
|
lag.par.lag1 = 0.05 # attack (NOT par.lagin)
|
|
lag.par.lag2 = 0.25 # release (NOT par.lagout)
|
|
lag.inputConnectors[0].connect(rms)
|
|
|
|
math = root.create(mathCHOP, f'scale_{name}')
|
|
math.par.gain = 8.0
|
|
math.inputConnectors[0].connect(lag)
|
|
|
|
# mathCHOP has NO par.clamp — use limitCHOP
|
|
lim = root.create(limitCHOP, f'clamp_{name}')
|
|
lim.par.type = 'clamp'
|
|
lim.par.min = 0.0
|
|
lim.par.max = 1.0
|
|
lim.inputConnectors[0].connect(math)
|
|
|
|
null = root.create(nullCHOP, f'out_{name}')
|
|
null.inputConnectors[0].connect(lim)
|
|
null.viewer = True
|
|
```
|
|
|
|
**Key TD 2025 corrections:**
|
|
- `analyzeCHOP.par.function = 'rmspower'` NOT `'rms'`
|
|
- `lagCHOP.par.lag1` / `par.lag2` NOT `par.lagin` / `par.lagout`
|
|
- `mathCHOP` has NO `par.clamp` — use separate `limitCHOP`
|
|
|
|
---
|
|
|
|
## Beat / Onset Detection
|
|
|
|
### Kick Detection (slope → trigger)
|
|
|
|
```python
|
|
slope = root.create(slopeCHOP, 'kick_slope')
|
|
slope.inputConnectors[0].connect(op('out_low'))
|
|
|
|
trig = root.create(triggerCHOP, 'kick_trig')
|
|
trig.par.threshold = 0.12
|
|
trig.par.attack = 0.005 # NOT par.attacktime
|
|
trig.par.decay = 0.15 # NOT par.decaytime
|
|
trig.par.triggeron = 'increase'
|
|
trig.inputConnectors[0].connect(slope)
|
|
|
|
kick_out = root.create(nullCHOP, 'out_kick')
|
|
kick_out.inputConnectors[0].connect(trig)
|
|
```
|
|
|
|
---
|
|
|
|
## Passing Audio to GLSL
|
|
|
|
```python
|
|
glsl.par.vec0name = 'uLow'
|
|
glsl.par.vec0valuex.expr = "op('out_low')['chan1']"
|
|
glsl.par.vec0valuex.mode = ParMode.EXPRESSION
|
|
|
|
glsl.par.vec1name = 'uKick'
|
|
glsl.par.vec1valuex.expr = "op('out_kick')['chan1']"
|
|
glsl.par.vec1valuex.mode = ParMode.EXPRESSION
|
|
```
|
|
|
|
```glsl
|
|
uniform float uLow;
|
|
uniform float uKick;
|
|
float scale = 1.0 + uKick * 0.4 + uLow * 0.2;
|
|
```
|
|
|
|
---
|
|
|
|
## Standard Audio Bus Pattern
|
|
|
|
Recommended structure:
|
|
|
|
```
|
|
audiodeviceinCHOP (audio_in)
|
|
↓
|
|
[null_audio_in]
|
|
├──→ audiofilterCHOP (lowpass@250) → analyzeCHOP → lagCHOP → mathCHOP → limitCHOP → null
|
|
├──→ audiofilterCHOP (bandpass@250-4k) → analyzeCHOP → lagCHOP → mathCHOP → limitCHOP → null
|
|
├──→ audiofilterCHOP (highpass@4k) → analyzeCHOP → lagCHOP → mathCHOP → limitCHOP → null
|
|
│
|
|
└──→ slopeCHOP → triggerCHOP (beat_trigger)
|
|
```
|
|
|
|
Keep this entire bus inside a `baseCOMP` (e.g., `audio_bus`) and reference via paths from visual networks.
|
|
|
|
---
|
|
|
|
## MIDI Input
|
|
|
|
```python
|
|
midi_in = root.create(midiinCHOP, 'midi_in')
|
|
midi_in.par.device = 0 # Check midiinDAT for device index
|
|
# Outputs channels named by MIDI note/CC: 'ch1n60', 'ch1c74', etc.
|
|
|
|
# Map CC to a parameter
|
|
op('bloom1').par.threshold.mode = ParMode.EXPRESSION
|
|
op('bloom1').par.threshold.expr = "op('midi_in')['ch1c74'][0]"
|
|
```
|
|
|
|
---
|
|
|
|
## CRITICAL: DO NOT use Lag CHOP for spectrum smoothing
|
|
|
|
Lag CHOP in timeslice mode expands 256-sample spectrum to 1600-2400 samples, averaging all values to near-zero (~1e-06). The shader receives no usable data. Use `mathCHOP(gain=8)` directly, or smooth in GLSL via temporal lerp with a feedback texture.
|
|
|
|
Verified:
|
|
- Without Lag CHOP: bass bins = 5.0-5.4 (strong, usable)
|
|
- With Lag CHOP: ALL bins = 0.000001 (dead)
|