Introduzione alla sintesi e manipolazione del suono con JavaScript
La programmazione musicale è l’arte di generare, modificare e controllare il suono usando procedure informatiche. Con JavaScript, grazie alla Web Audio API, possiamo scrivere programmi che diventano veri e propri strumenti virtuali.
OscillatorNode GainNode BiquadFilterNodeosc → filter → gain → output.La Web Audio API è quindi una piattaforma per la musica generativa, la didattica del suono e la creatività web.
AudioContextTutto parte da un AudioContext, la “centralina” del suono.
const audioCtx = new AudioContext();
const osc = audioCtx.createOscillator();
osc.type = 'sine';
osc.frequency.value = 440;
osc.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 1);
const osc2 = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc2.connect(gain);
gain.connect(audioCtx.destination);
gain.gain.value = 0.2;
osc2.frequency.value = 220;
osc2.start();
osc2.stop(audioCtx.currentTime + 1.5);
const osc3 = audioCtx.createOscillator();
const filter = audioCtx.createBiquadFilter();
const gain2 = audioCtx.createGain();
filter.type = 'lowpass';
filter.frequency.value = 800;
gain2.gain.value = 0.3;
osc3.connect(filter);
filter.connect(gain2);
gain2.connect(audioCtx.destination);
osc3.frequency.value = 600;
osc3.start();
osc3.stop(audioCtx.currentTime + 2);
const bufferSize = audioCtx.sampleRate;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random()*2 - 1;
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
noise.connect(audioCtx.destination);
noise.start();
Simuliamo un’eco semplice con un DelayNode
// Oscillatore + delay + gain
const osc4 = audioCtx.createOscillator();
const delay = audioCtx.createDelay();
const gain3 = audioCtx.createGain();
delay.delayTime.value = 0.25; // 250 ms
gain3.gain.value = 0.4;
osc4.connect(delay);
delay.connect(audioCtx.destination);
osc4.connect(audioCtx.destination);
osc4.frequency.value = 440;
osc4.start();
osc4.stop(audioCtx.currentTime + 1);
Possiamo “vedere” le onde sonore in tempo reale con il nodo AnalyserNode
ed il metodo createAnalyser
let analyser, canvas, ctx, source, animationId;
function startAnalyzer() {
if (analyser) cancelAnimationFrame(animationId);
analyser = audioCtx.createAnalyser();
const osc = audioCtx.createOscillator();
osc.type = 'square';
osc.frequency.value = 220;
osc.connect(analyser);
analyser.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 2);
canvas = document.getElementById('analyserCanvas');
ctx = canvas.getContext('2d');
const data = new Uint8Array(analyser.frequencyBinCount);
function draw() {
analyser.getByteTimeDomainData(data);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
const slice = canvas.width / data.length;
for (let i=0; i%lt;data.length; i++) {
const x = i * slice;
const y = (data[i] / 255) * canvas.height;
i === 0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y);
}
ctx.strokeStyle = '#69b7eb';
ctx.stroke();
animationId = requestAnimationFrame(draw);
}
draw();
}
Usiamo più oscillatori con lievi variazioni di frequenza e un filtro passa‑basso per ottenere un timbro caldo.
// Violino semplice: più oscillatori + filtro + inviluppo di volume
const attack = 0.3, release = 0.8;
const baseFreq = 440;
const filterV = audioCtx.createBiquadFilter();
filterV.type = 'lowpass';
filterV.frequency.value = 1200;
const gainV = audioCtx.createGain();
gainV.gain.value = 0;
filterV.connect(gainV);
gainV.connect(audioCtx.destination);
for (let i=0; i<3; i++) {
const oscV = audioCtx.createOscillator();
oscV.type = 'sawtooth';
oscV.frequency.value = baseFreq + i*1.5; // leggere differenze
oscV.connect(filterV);
oscV.start();
oscV.stop(audioCtx.currentTime + attack + release);
}
// inviluppo: attacco e rilascio dolce
const now = audioCtx.currentTime;
gainV.gain.linearRampToValueAtTime(0.4, now + attack);
gainV.gain.linearRampToValueAtTime(0, now + attack + release);
Ecco una panoramica di parametri comuni per riprodurre, per imitazione, alcuni strumenti. Puoi adattarli nei tuoi script per ottenere timbri convincenti.
| Strumento | Forma d’onda | Filtro | Attack | Release | Note / Timbro |
|---|---|---|---|---|---|
| Piano / E‑Piano | sine + sawtooth (mix) | notch ~1.8 kHz | 0.01 s | 1.0 s | Attacco rapido, decadimento medio, leggermente metallico con filtro notch. Delay corto per effetto elettrico. |
| Violino / Strings | sawtooth x2 o x3 detuned ±2–5 cents | lowpass ~1.2 kHz | 0.3 s | 1.0 s | Tono caldo e dolce; phaser o tremolo lento per arricchire il movimento delle armoniche. |
| Chitarra / Pluck | triangle + square | bandpass 700 Hz–2 kHz | 0.02 s | 0.6 s | Attacco deciso e brillante; usa delay breve per creare eco naturale dello strumento. |
| Pad / Synth Pad | sawtooth + sine (leggero detune) | lowpass 800–1500 Hz | 0.8 s | 2.0 s | Tessitura ampia e morbida; notch o phaser per maggiore profondità, ideale per tappeti sonori. |
| Brass / Synth Brass | sawtooth + square (+7 cents) | lowpass 1.8–2.2 kHz | 0.05 s | 0.4 s | Attacco energico, sustain deciso; classico timbro brass anni ‘80. |
| Bass Analogico | sawtooth + triangle | lowpass 400–800 Hz | 0.02 s | 0.3 s | Basso profondo, morbido; riduci il filtro per un suono rotondo tipo Moog. |
| Lead brillante | sawtooth + sine | highpass 400 Hz | 0.01 s | 0.2 s | Suono nitido per soli synth; tremolo leggero o phaser rapido per vivacità. |
| Bell / Campana | sine + sine (+12 cents) | notch 1.8 kHz | 0.1 s | 1.0 s | Suono metallico e cristallino; notch stretto enfatizza la campanatura. |
| Ambient Pad | triangle + sine (+3 cents) | notch 1.0 kHz | 0.8 s | 2.0 s | Effetto atmosferico; usa phaser e delay per profondità e ariosità. |
| Kick (cassa) | sine | none | 0 s | 0.3 s | Frequenza 60→30 Hz in discesa, inviluppo brevissimo e potente. |
| Snare (rullante) | white noise + sine (200 Hz) | highpass ≥ 1 kHz | 0 s | 0.25 s | Rumore secco con punta tonale; aggiungi bandpass centrato sui 2 kHz per realismo. |
| Hi‑Hat (charleston) | white noise | highpass ≥ 5 kHz | 0 s | 0.1 s | Rumore filtrato ad alte frequenze; ottimo con attacco immediato e nessun effetto. |
💡 Tip: puoi combinare più oscillatori e filtri per creare nuovi timbri:
ad esempio miscelare un’onda sine e una sawtooth
con detune minimo e inviluppi differenti per emulare strumenti ibridi come pad‑archi o ep‑brass.
In questo esempio combiniamo due forme d’onda diverse con inviluppi separati e un filtro passa‑basso per ottenere un suono più “ricco”.
// Creiamo un "timbro ibrido" mescolando due oscillatori
function playHybrid() {
const baseFreq = 440; // frequenza della nota (La4)
const attack = 0.2, release = 1.0; // tempi inviluppo
const now = audioCtx.currentTime;
// Nodo di filtro (attenua alte frequenze)
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1800;
// Nodo di volume finale
const gain = audioCtx.createGain();
gain.gain.value = 0;
filter.connect(gain);
gain.connect(audioCtx.destination);
// --- Oscillatore 1: onda sinusoidale, suono morbido
const osc1 = audioCtx.createOscillator();
osc1.type = 'sine';
osc1.frequency.value = baseFreq;
osc1.connect(filter);
osc1.start();
osc1.stop(now + attack + release);
// --- Oscillatore 2: onda a dente di sega, aggiunge brillantezza
const osc2 = audioCtx.createOscillator();
osc2.type = 'sawtooth';
osc2.frequency.value = baseFreq;
osc2.connect(filter);
osc2.start();
osc2.stop(now + attack + release);
// --- Inviluppo di volume (attack + release)
gain.gain.linearRampToValueAtTime(0.5, now + attack); // attacco
gain.gain.linearRampToValueAtTime(0, now + attack + release); // rilascio
}
La Web Audio API usa un clock interno ad alta precisione audioCtx.currentTime
Possiamo usarlo per programmare eventi musicali in base a un BPM (battiti per minuto).
// Funzione per calcolare la durata di una battuta in secondi
function getBeatDuration(bpm) {
return 60 / bpm;
}
// Programma una semplice sequenza ritmica
function playBeatSequence() {
const bpm = 120;
const beat = getBeatDuration(bpm);
const now = audioCtx.currentTime;
for (let i = 0; i < 8; i++) {
const t = now + i * beat;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'square';
osc.frequency.value = (i % 4 === 0) ? 220 : 440; // alterna due suoni
gain.gain.value = 0.2;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(t);
osc.stop(t + 0.1); // ogni nota dura 100ms
}
}
💡 Puoi usare lo stesso principio per costruire un sequencer: programmare più strumenti (kick, snare, hihat) con diverse posizioni nella misura.
Ogni nota musicale può essere calcolata partendo dal La4 = 440 Hz. La formula tipica è:
frequency = 440 * Math.pow(2, (semitoni da A4) / 12)
Possiamo poi collegare i tasti della tastiera fisica a note MIDI per suonare piccoli frammenti.
// Mappa tasti → semitoni (rispetto ad A4)
const keyMap = {
'a': 0, // La4
'w': 1, // La#4
's': 2, // Si4
'd': 3, // Do5
'r': 4, // Do#5
'f': 5, // Re5
't': 6, // Re#5
'g': 7, // Mi5
'h': 9, // Fa#5
'j': 11, // Sol#5
'k': 12 // La5
};
// Funzione per suonare una nota
function playNote(semitone) {
const freq = 440 * Math.pow(2, semitone / 12);
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.frequency.value = freq;
osc.type = 'sine';
gain.gain.value = 0.3;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5); // durata breve
}
// Listener tastiera
window.addEventListener('keydown', e => {
const semitone = keyMap[e.key];
if (semitone !== undefined) {
playNote(semitone);
}
});
✅ Prova a premere A S D F G H J K sulla tastiera del tuo computer: sentirai le note che si susseguono!
Puoi ampliare la mappa aggiungendo più tasti o creando modalità diverse (scale maggiori, minori, pentatoniche, ecc.).
Prova la nostra Drum Machine!
Prova il nostro Synth Basic!
Prova il nostro Synth Pro (con registrazione)!
Prova il nostro Synth Avanced (con registrazione, presets, live modify, e phaser)!
Prova il nostro Mixer 4 piste!