Installazione sonora generativa per più moduli ESP32-C3-SUPERMINI. Ogni modulo produce un paesaggio sonoro ambientale che evolve autonomamente e reagisce alla presenza degli altri dispositivi nella rete ESP-NOW, creando un'esperienza collettiva e site-specific.
Ogni modulo genera un drone ambientale basato su sintesi FM a 3 voci (libreria Mozzi). Il suono attraversa ciclicamente cinque stati emotivi (Serene, Mysterious, Restless, Ethereal, Warm) con crossfade lenti (~20s). La presenza di altri dispositivi altera il timbro:
- Personalità unica: ogni dispositivo ha un'impronta sonora deterministica derivata dal proprio MAC address (hash FNV-1a) che influenza detuning, rapporti FM, brillantezza e velocità di modulazione.
- Distanza = intensità: il livello RSSI (dBm) determina il peso dell'influenza. Dispositivi vicini colorano il suono in modo più marcato.
- Isolamento vs. folla: un modulo solo tende verso stati contemplativi (Serene, Ethereal). Più dispositivi nelle vicinanze spingono verso stati più energetici (Restless, Mysterious).
Il pulsante BOOT attiva un accent momentaneo (burst FM + filtro aperto) e invia un broadcast immediato.
| Pin | Funzione nel progetto |
|---|---|
| GPIO0 | I2S DOUT → MAX98357 DIN |
| GPIO1 | I2S BCLK → MAX98357 BCLK |
| GPIO2 | I2S LRCLK → MAX98357 LRCLK |
| GPIO8 | LED integrato (active LOW) |
| GPIO9 | Pulsante BOOT (active LOW) |
| MAX98357 | ESP32-C3 | Note |
|---|---|---|
| VDD | 3V3 | Usare 3.3V: a 5V VIH = 3.5V, oltre il livello alto dell'ESP32 (3.3V) |
| GND | GND | Massa comune |
| DIN | GPIO0 | Dati I2S (3 pin adiacenti in basso a destra sulla scheda) |
| BCLK | GPIO1 | |
| LRCLK | GPIO2 | |
| SD_MODE | (n.c.) | Pull-up interno → amplificatore sempre attivo |
| GAIN | (n.c.) | Default 9 dB |
processControl() @ 64 Hz
├─ Avanza transizione stato emotivo (interpolazione lineare)
├─ Applica influenza dispositivi rilevati (MAC→personalità, RSSI→peso)
├─ Legge 4 LFO → modula profondità FM, cutoff filtro, ampiezza, pitch
├─ Cachea valori integer per audio rate
└─ Imposta frequenze oscillatori e parametri filtro
processSample() @ 16384 Hz
┌─────────────────────────────────────────────────┐
│ mod_0 ──phMod──► carrier_0 (sub, 55-110 Hz) │
│ mod_1 ──phMod──► carrier_1 (mid, 110-440 Hz) ├─► MIX ─► StateVariable LP ─► Volume ─► I2S
│ mod_2 ──phMod──► carrier_2 (high, 220-880 Hz) │
└─────────────────────────────────────────────────┘
- 6×
Oscil<SIN2048, AUDIO_RATE>(3 carrier + 3 modulator) - 4×
Oscil<SIN2048, CONTROL_RATE>(LFO) - 1×
StateVariable<AUDIO_RATE>(filtro LP risonante)
Budget CPU stimato: ~58% utilizzato, ~42% headroom per WiFi ISR.
| # | Nome | Carattere | Frequenze | FM ratios | Filtro |
|---|---|---|---|---|---|
| 0 | SERENE | Consonante, caldo, dolce | C2 G3 E4 | 1:2:3 | 800 Hz, Q dolce |
| 1 | MYSTERIOUS | Irrazionale, oscuro | C#2 A3 A4 | √2, φ, √5 | 600 Hz, Q media |
| 2 | RESTLESS | Dissonante, brillante, agitato | D2 Bb3 F#4 | 1.5, 2.33, 3.5 | 2000 Hz, Q stretta |
| 3 | ETHEREAL | Armonici alti, aereo, lentissimo | C2 E3 B4 | 2:3:5 | 3000 Hz, Q dolce |
| 4 | WARM | Ottave e quinte, basso, pieno | E2 E3 B3 | 1:2:1.5 | 500 Hz, Q bassa |
| 5 | TENSE | Tritoni, respiro trattenuto, irrisolto | G2 C#3 G4 | 1.125, 2.125, 1.875 | 1400 Hz, Q alta |
| 6 | DESOLATE | Subsonico, vuoto, immobile | A1 A2 E3 | 1:2:3 (depth ~0) | 350 Hz, Q nulla |
| 7 | ECSTATIC | Gioioso, brillante, pulsante | C3 G3 G4 | 1:2:4 | 2800 Hz, Q dolce |
| 8 | HYPNOTIC | Trance, ottave che interferiscono | A1 A2 A3 | 3:2:1.5 | 1000 Hz, Q media |
| 9 | SACRED | Devozionale, quinta aperta, glaciale | D2 A2 A3 | 2:3:4 | 700 Hz, Q dolce |
| Parametro | Range | Effetto |
|---|---|---|
| detune[0/1/2] | 0.98×…1.02× (±34 ct) | Detuning indipendente per ciascuna voce |
| fmRatioBias | -0.2…+0.2 | Consonanza/dissonanza FM |
| brightness | 0.0…1.0 | Influenza cutoff filtro |
| modulationRate | 0.5×…2.0× | Velocità LFO |
tob_IA/
├── platformio.ini
├── README.md
├── claude.md
├── documenti/
│ ├── esp32-c3-super-mini.jpg
│ └── MAX98357 I2S Modulo Amplificatore Audio Digitale 3W Classe D.webp
└── src/
├── main.cpp ← Mozzi lifecycle, ESP-NOW, I2S output
├── soundscape.h ← SoundscapeEngine API
├── soundscape.cpp ← Sintesi FM, stati emotivi, personalità
├── serial_cli.h ← SerialCLI API
└── serial_cli.cpp ← Interfaccia a riga di comando via UART
[env:esp32-c3-supermini]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = lolin_c3_mini
framework = arduino
monitor_speed = 115200
lib_deps =
sensorium/Mozzi
build_flags =
-DMOZZI_AUDIO_MODE=MOZZI_OUTPUT_EXTERNAL_CUSTOM
-DMOZZI_AUDIO_RATE=16384
-DMOZZI_CONTROL_RATE=64
-DMOZZI_AUDIO_CHANNELS=1| Scelta | Motivazione |
|---|---|
| pioarduino | ESP-IDF 5.x aggiornato con le librerie che espongono la RSSI nella callback ESP-NOW |
lolin_c3_mini |
Board ID PlatformIO più compatibile con l'ESP32-C3 SuperMini |
| Mozzi EXTERNAL_CUSTOM | ESP32-C3 non ha DAC interno → I2S_DAC non disponibile. I2S gestito manualmente. |
| 16384 Hz | Rate Mozzi ottimale (potenza di 2); MAX98357 lo accetta via auto-detect BCLK |
engine.begin(); // init dopo startMozzi()
engine.processControl(); // da updateControl() @ 64 Hz
int16_t sample = engine.processSample(); // da updateAudio() @ 16384 Hzengine.reportDevice(mac, rssi); // nuovo pacchetto ESP-NOW ricevuto
engine.ageDevices(millis()); // rimuove dispositivi non visti da 10sengine.setTargetState(STATE_MYSTERIOUS); // forza transizione
engine.triggerAccent(); // burst momentaneo (~1.5s)
engine.setAutoMode(true/false); // abilita/disabilita ciclo autonomo
engine.setTransitionTime(20.0f); // durata crossfade (secondi)
engine.setCycleTime(45.0f); // permanenza in uno stato (secondi)engine.setVoiceFreq(0, 80.0f); // voce 0 a 80 Hz
engine.setVoiceFmRatio(1, 1.618f); // voce 1 rapporto aureo
engine.setVoiceFmDepth(2, 0.4f); // voce 2 FM profondo
engine.setFilterCutoff(1200.0f); // filtro a 1200 Hz
engine.setFilterResonance(100.0f); // risonanza moderata (0=max, 255=min)
engine.setMasterVolume(0.6f); // volume 60%engine.getCurrentState(); // stato corrente (0–4)
engine.getTargetState(); // stato verso cui si transita
engine.getTransitionProgress(); // 0.0–1.0
engine.getActiveDeviceCount(); // dispositivi attivi
engine.getLiveParams(); // snapshot parametri correntiConnettendosi a 115200 baud via USB è possibile modificare qualsiasi parametro in tempo reale senza reflashare. I comandi vengono letti in modo non bloccante da updateControl() (64 Hz) e applicati immediatamente al ciclo di sintesi successivo.
| Comando | Argomenti | Descrizione |
|---|---|---|
help / ? |
— | Lista comandi |
status |
— | Snapshot completo di tutti i parametri live |
vol |
<0-100> |
Volume globale % (sopravvive alle transizioni) |
mvol |
<0-100> |
Master volume stato corrente % |
state |
[0-9 | nome] |
Forza / mostra stato target |
auto |
[on | off] |
Abilita/disabilita ciclo autonomo |
transit |
<s> |
Durata crossfade tra stati (secondi) |
cycle |
<s> |
Permanenza per stato (secondi) |
preset |
<0-9 | nome> |
Ripristina parametri di fabbrica di uno stato |
accent |
— | Trigger accent burst |
freq |
<v 0-2> <hz> |
Frequenza carrier voce |
fmratio |
<v 0-2> <r> |
Rapporto FM modulatore/carrier |
fmdepth |
<v 0-2> <0-1> |
Profondità FM |
amp |
<v 0-2> <0-1> |
Ampiezza voce |
filter |
<hz> [ris 0-255] |
Cutoff filtro LP + risonanza (0=max Q, 255=min Q) |
lfo |
<i 0-3> <hz> [depth] |
Rate e profondità LFO |
I nomi degli stati sono accettati case-insensitive (serene, WARM, Tense, …):
0=serene · 1=mysterious · 2=restless · 3=ethereal · 4=warm · 5=tense · 6=desolate · 7=ecstatic · 8=hypnotic · 9=sacred
I target dei 4 LFO: 0 = FM depth · 1 = filter cutoff · 2 = amp balance · 3 = pitch drift.
> vol 40
[CLI] volume globale → 40%
> state mysterious
[CLI] stato target → MYSTERIOUS
> filter 1200 80
[CLI] filtro → cutoff=1200 Hz, ris=80
> lfo 1 0.08 0.6
[CLI] LFO 1 → rate=0.0800 Hz, depth=0.6000
> fmratio 0 1.618
[CLI] voce 0 FM ratio → 1.6180
> status
── Stato ──────────────────────────────────────────
SERENE → MYSTERIOUS (transizione 23%)
auto=on dispositivi=1
vol globale=40% master=65%
── Voci ────────────────────────────────────────────
[0] 65.4 Hz ratio=1.6180 depth=0.3000 amp=0.6000
[1] 220.0 Hz ratio=1.4142 depth=0.4000 amp=0.5000
[2] 440.0 Hz ratio=2.2361 depth=0.3500 amp=0.4000
── Filtro ──────────────────────────────────────────
cutoff=1200 Hz ris=80
── LFO ─────────────────────────────────────────────
[0] FM depth rate=0.0500 Hz depth=0.3000
[1] filter rate=0.0800 Hz depth=0.6000
[2] amp balance rate=0.0300 Hz depth=0.2000
[3] pitch drift rate=0.1200 Hz depth=0.0800
───────────────────────────────────────────────────
Avvio:
[INIT] Sound installation — ESP-NOW + Mozzi
[INIT] ESP-IDF v5.1.4
[INIT] MAC: A4:E5:7C:12:34:56
[I2S] Configured at 16384 Hz, 16-bit mono
[SOUND] Engine ready — state: SERENE
[INIT] Ready — autonomous soundscape active
[INIT] Press BOOT for accent + broadcast
[CLI] Pronta. Digita 'help' per i comandi disponibili.
[CLI] Prompt: <comando> [arg1] [arg2] (invio per eseguire)
Ricezione dispositivo:
[RECV] A4:E5:7C:78:9A:BC RSSI -52 dBm
[SOUND] New device A4:E5:7C:78:9A:BC — freq+3.2 ratio+0.08 bright0.67 modRate1.34
Status periodico:
[STATUS] state=SERENE→MYSTERIOUS (45%) devices=2 auto=on
Accent:
[BTN] Accent + broadcast
[SOUND] Accent triggered
# Compilare e flashare (collegare il modulo via USB)
pio run --target upload
# Aprire il monitor seriale
pio device monitorFlash lo stesso firmware su tutti i moduli. Non è richiesta nessuna configurazione manuale. Ogni modulo genera suono autonomamente all'accensione e reagisce alla presenza degli altri.

