Nota: Questo articolo è la traduzione italiana del post originale in inglese pubblicato su a80.it.

Le Web Audio API sono una delle funzionalità più potenti e sottoutilizzate dei browser moderni. Permettono di creare, manipolare e analizzare l’audio in tempo reale usando JavaScript puro, senza plugin.

Per dimostrare il pieno potenziale di queste API, ho costruito un sintetizzatore modulare virtuale ispirato al Moog: uno strumento completamente funzionante con 3 oscillatori, un filtro ladder risonante, inviluppi ADSR, due LFO ed effetti. Tutto in un singolo file HTML senza dipendenze.

Repository Github: https://github.com/mirchaemanuel/moog-modular/

Moog Modular Synthesizer

La Leggenda: Robert Moog e la Nascita del Sintetizzatore

Prima di tuffarci nel codice, vale la pena capire perché il sintetizzatore Moog è così importante.

Nel 1964, Robert Moog introdusse il primo sintetizzatore a controllo di tensione disponibile commercialmente. A differenza degli strumenti elettronici precedenti, il design di Moog era modulare: componenti separati (oscillatori, filtri, amplificatori, generatori di inviluppo) potevano essere collegati tramite cavi patch in qualsiasi configurazione, permettendo ai musicisti di scolpire suoni completamente nuovi.

Le innovazioni chiave che resero il Moog rivoluzionario:

  • Oscillatori a Controllo di Tensione (VCO): Generano forme d’onda (dente di sega, quadra, triangolare, sinusoidale) a frequenze determinate dalla tensione in ingresso
  • Filtro a Controllo di Tensione (VCF): Il leggendario “filtro ladder Moog”, un passa-basso a 24dB/ottava con risonanza che definisce il suono Moog
  • Amplificatore a Controllo di Tensione (VCA): Controlla il volume in base alla tensione in ingresso
  • Generatori di Inviluppo: Modellano come i parametri cambiano nel tempo usando curve Attack-Decay-Sustain-Release (ADSR)
  • Oscillatori a Bassa Frequenza (LFO): Oscillatori lenti che modulano altri parametri per vibrato, tremolo e sweep di filtro

Questa architettura divenne il modello per praticamente ogni sintetizzatore successivo. Le Web Audio API, sorprendentemente, forniscono equivalenti nativi per tutti questi blocchi costruttivi.

Architettura Web Audio: Nodi e Grafo Audio

Le Web Audio API rispecchiano il concetto di sintetizzatore modulare. Tutto è un nodo connesso in un grafo audio. Il segnale fluisce dai nodi sorgente attraverso i nodi di elaborazione fino a una destinazione (i tuoi altoparlanti).

const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

I browser moderni bloccano l’autoplay audio. L’AudioContext deve essere creato o ripreso dopo un’interazione dell’utente:

function noteOn(note) {
  if (!audioCtx) {
    audioCtx = new AudioContext();
    initAudioGraph();
  }
  // Crea e avvia la voce...
}

Oscillatori: La Sorgente Sonora

Un OscillatorNode genera forme d’onda periodiche, la materia prima della sintesi. Nella mia emulazione Moog, tre VCO lavorano insieme:

const osc = audioCtx.createOscillator();
osc.type = 'sawtooth'; // 'sine', 'square', 'triangle', 'sawtooth'
osc.frequency.value = 440; // A4 = 440 Hz

// Il detuning crea il classico suono "grasso" analogico
osc.detune.value = 7; // cents (centesimi di semitono)

osc.connect(destination);
osc.start();

La magia della sintesi sottrattiva sta nel combinare più oscillatori con leggero detuning e forme d’onda diverse. Nella mia implementazione:

  • VCO 1: Dente di sega, pitch base
  • VCO 2: Onda quadra, +7 cents di detune
  • VCO 3: Triangolare, un’ottava sotto

Questo crea una ricchezza armonica che un singolo oscillatore non può ottenere.

Il Filtro Ladder Moog

Il BiquadFilterNode emula il cuore del suono Moog: un filtro passa-basso risonante:

const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 2000; // Frequenza di taglio
filter.Q.value = 10; // Risonanza (fattore Q)

Il parametro Q (fattore di qualità) crea quel distintivo suono “gorgogliante” quando alzato. A valori estremi, il filtro auto-oscilla, una tecnica usata per bass drop e lead stridenti.

Keyboard Tracking

I veri sintetizzatori Moog hanno il keyboard tracking: la frequenza di taglio del filtro segue la nota suonata così le note più alte suonano più brillanti:

const kbdTrackAmount = state.filter.kbdTrack / 100;
const freqRatio = frequency / 261.63; // C4 come riferimento
const kbdCutoffMod = Math.pow(freqRatio, kbdTrackAmount);
voice.filter.frequency.value = baseCutoff * kbdCutoffMod;

Generatori di Inviluppo: Modellare il Suono nel Tempo

Il GainNode implementa gli inviluppi ADSR, il contorno che modella come un suono evolve:

const vca = audioCtx.createGain();
vca.gain.value = 0;

const now = audioCtx.currentTime;
const attackTime = 0.01;  // 10ms
const decayTime = 0.2;    // 200ms
const sustainLevel = 0.7; // 70%

// Attack: rampa al picco
vca.gain.linearRampToValueAtTime(1.0, now + attackTime);

// Decay: scende al livello di sustain
vca.gain.linearRampToValueAtTime(sustainLevel, now + attackTime + decayTime);

L’inviluppo del filtro funziona identicamente ma modula filter.frequency:

const filterEnvAmount = 0.6; // 60%
const targetCutoff = baseCutoff + (20000 - baseCutoff) * filterEnvAmount;

filter.frequency.linearRampToValueAtTime(targetCutoff, now + attackTime);
filter.frequency.linearRampToValueAtTime(baseCutoff, now + attackTime + decayTime);

Questo crea i classici suoni “pluck” e “sweep” che definiscono la sintesi analogica.

Oscilloscopio che mostra la forma d’onda

Modulazione LFO: Dare Vita al Suono

Gli Oscillatori a Bassa Frequenza creano movimento ed espressione. Non producono suono udibile ma modulano altri parametri:

const lfo = audioCtx.createOscillator();
lfo.frequency.value = 5; // 5 Hz = vibrato sottile

const lfoGain = audioCtx.createGain();
lfoGain.gain.value = 50; // Profondità in cents

lfo.connect(lfoGain);
lfoGain.connect(osc.detune); // Vibrato!
lfo.start();

La tecnica chiave: instradare l’LFO attraverso un GainNode che controlla la profondità di modulazione. Questo permette regolazioni fluide in tempo reale.

Nella mia implementazione, ogni LFO può mirare a:

  • Pitch: Effetto vibrato
  • Filtro: Classico sweep di filtro (suono “wah”)
  • Ampiezza: Effetto tremolo
  • Larghezza d’impulso: Sintesi PWM

Effetti: Delay e Riverbero

Feedback Delay

Un delay con feedback crea echi che decadono nel tempo:

const delayNode = audioCtx.createDelay(2); // Max 2 secondi
delayNode.delayTime.value = 0.35;

const feedback = audioCtx.createGain();
feedback.gain.value = 0.4; // 40% di feedback

// Crea loop di feedback
input.connect(delayNode);
delayNode.connect(feedback);
feedback.connect(delayNode); // Il segnale ritorna!
delayNode.connect(output);

Riverbero a Convoluzione

Il ConvolverNode applica una risposta all’impulso per acustiche realistiche. Invece di caricare file audio, genero l’impulso proceduralmente:

const reverb = audioCtx.createConvolver();
const length = audioCtx.sampleRate * 2; // 2 secondi
const impulse = audioCtx.createBuffer(2, length, audioCtx.sampleRate);

for (let ch = 0; ch < 2; ch++) {
  const data = impulse.getChannelData(ch);
  for (let i = 0; i < length; i++) {
    // Rumore bianco con decadimento quadratico
    data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 2);
  }
}
reverb.buffer = impulse;

Visualizzazione in Tempo Reale: L’Oscilloscopio

L’AnalyserNode fornisce dati sulla forma d’onda in tempo reale per la visualizzazione:

const analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;

function draw() {
  requestAnimationFrame(draw);

  const dataArray = new Uint8Array(analyser.frequencyBinCount);
  analyser.getByteTimeDomainData(dataArray);

  ctx.clearRect(0, 0, width, height);
  ctx.strokeStyle = '#00ff00';
  ctx.beginPath();

  for (let i = 0; i < dataArray.length; i++) {
    const y = (dataArray[i] / 128.0 - 1) * height / 2 + height / 2;
    ctx.lineTo(i * (width / dataArray.length), y);
  }
  ctx.stroke();
}
draw();

Gestione Voci Polifoniche

Per suonare accordi, ogni nota crea una catena di segnale completa:

const voices = new Map();

function noteOn(note) {
  const frequency = 440 * Math.pow(2, (note - 69) / 12);

  const voice = {
    oscs: [createOsc(frequency), createOsc(frequency), createOsc(frequency)],
    filter: createFilter(),
    vca: createVCA()
  };

  connectVoice(voice);
  triggerEnvelopes(voice);
  voices.set(note, voice);
}

function noteOff(note) {
  const voice = voices.get(note);
  if (voice) {
    triggerRelease(voice);
    // Rimuovi dopo il completamento del release
    setTimeout(() => voices.delete(note), releaseTime);
  }
}

Aggiornamenti Fluidi dei Parametri

Quando regoli i parametri mentre le note suonano, usa setTargetAtTime per evitare click:

// Male: causa click audio
filter.frequency.value = newCutoff;

// Bene: transizione esponenziale fluida
filter.frequency.setTargetAtTime(newCutoff, audioCtx.currentTime, 0.02);

Il terzo parametro (0.02) è la costante di tempo: quanto velocemente approcciare il valore target.

Portamento (Glide)

Tecnica classica dei sintetizzatori dove il pitch scivola tra le note:

if (glideTime > 0 && lastFrequency) {
  osc.frequency.setValueAtTime(lastFrequency, audioCtx.currentTime);
  osc.frequency.linearRampToValueAtTime(newFrequency,
    audioCtx.currentTime + glideTime / 1000);
} else {
  osc.frequency.value = newFrequency;
}
lastFrequency = newFrequency;

Confronto Reale vs. Virtuale

CaratteristicaMoog OriginaleVersione Web Audio
OscillatoriVCO analogici con driftOscillatorNode digitale (stabile)
FiltroLadder 24dB (4 poli)BiquadFilter 12dB (2 poli)
PatchingCavi fisiciConnessioni JavaScript
InviluppiCurve di tensionelinearRampToValueAtTime
RumoreSorgente rumore analogicaPlayback buffer random
Latenza~0ms10-50ms (dipende dal browser)

L’implementazione Web Audio non può replicare perfettamente il calore e l’imprevedibilità analogica, ma cattura l’architettura e il comportamento in modo sorprendente.

Cosa C’è Dentro

Le Web Audio API forniscono tutto il necessario per costruire strumenti di qualità professionale nel browser. Con ~3.000 righe di JavaScript vanilla, il sintetizzatore Moog Modular include:

  • Tre oscillatori con multiple forme d’onda e detuning
  • Filtro passa-basso risonante con keyboard tracking
  • Doppio inviluppo ADSR per filtro e ampiezza
  • Due LFO con multiple destinazioni di modulazione
  • Effetti delay e riverbero
  • Visualizzazione oscilloscopio in tempo reale
  • Gestione voci polifoniche
  • Sistema di preset e sequenze demo

Il codice sorgente è un singolo file HTML: niente tool di build, niente dipendenze, solo browser e creatività. (Aggiornamento: in seguito ho rifattorizzato il progetto in una struttura modulare per renderlo più facile da mantenere ed espandere.)

Provalo tu stesso su moog-modular.a80.it. Usa la tastiera del computer (tasti A-L) o clicca sui tasti virtuali per suonare.