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 | sine + sawtooth (mix) | lowpass ~2 kHz | 0.01 s | 1.0 s | Attacco rapido, decadimento medio, suono rotondo. |
| Violino | sawtooth x3 detuned ±2 Hz | lowpass ~1.2 kHz | 0.3 s | 0.8 s | Tono caldo; inviluppo dolce e sostenuto. |
| Chitarra | triangle o square | bandpass 700 Hz–2 kHz | 0.02 s | 0.6 s | Attacco deciso, corpo centrale, decadimento veloce. |
| Pad / Synth | sawtooth + sine | lowpass 800 Hz | 1.0 s | 2.5 s | Tessitura morbida, ideale per tappeti sonori. |
| Kick (cassa) | sine | none | 0 s | 0.3 s | Frequenza 60→30 Hz in discesa, inviluppo brevissimo. |
| Snare (rullante) | white noise + sine (200 Hz) | highpass ≥ 1 kHz | 0 s | 0.25 s | Rumore secco con punta tonale. |
| Hi‑Hat (charleston) | white noise | highpass ≥ 5 kHz | 0 s | 0.1 s | Rumore filtrato ad alte frequenze. |
💡 Tip: puoi combinare più oscillatori e filtri per creare nuovi timbri:
ad esempio miscelare un’onda sine e una sawtooth
con inviluppi differenti per emulare strumenti ibridi.
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 Javascript!
Prova il nostro Synth Javascript!