How to Build a Web-Based Audio Sampler with Real-Time Pitch and Playback Control
If you've ever wanted to load a sample in the browser and manipulate it like an instrument — think start time, pitch shifting, and looping — the Web Audio API gives you the tools. This guide walks through building a browser-based sampler with dynamic playback control using nothing but vanilla JavaScript. Use Cases for Browser-Based Samplers Interactive music experiences Drum machines or pad samplers Real-time sound design tools Step 1: Load the Sample We’ll fetch a WAV/MP3 and decode it for playback: const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); async function loadSample(url) { const res = await fetch(url); const arrayBuffer = await res.arrayBuffer(); return await audioCtx.decodeAudioData(arrayBuffer); } Step 2: Create a Sampler Function We’ll build a function that creates a playback node each time a note is triggered: function playSample(buffer, options = {}) { const source = audioCtx.createBufferSource(); source.buffer = buffer; const gain = audioCtx.createGain(); gain.gain.value = options.volume || 1.0; source.playbackRate.value = options.pitch || 1.0; if (options.loop) { source.loop = true; source.loopStart = options.loopStart || 0; source.loopEnd = options.loopEnd || buffer.duration; } source.connect(gain).connect(audioCtx.destination); source.start(audioCtx.currentTime, options.offset || 0); return source; } Step 3: Trigger with UI or Keyboard You can hook this into pads, keys, or sequencers: let sampleBuffer; loadSample('/samples/snare.wav').then(buffer => { sampleBuffer = buffer; }); document.getElementById('trigger').addEventListener('click', () => { if (sampleBuffer) { playSample(sampleBuffer, { pitch: 1.2, // speed up slightly offset: 0.05, // trim attack volume: 0.8 }); } }); Optional: Add a UI for Looping Browser samplers can support loop regions: playSample(sampleBuffer, { loop: true, loopStart: 0.2, loopEnd: 0.8, pitch: 0.8 }); Pros and Cons ✅ Pros No dependencies, loads instantly Pitch, trim, and loop all in-browser Modular and extensible (add envelopes, filters, UI) ⚠️ Cons Limited real-time editing without AudioWorklet No time-stretching — pitch alters speed Needs care to prevent overlapping or memory leaks
If you've ever wanted to load a sample in the browser and manipulate it like an instrument — think start time, pitch shifting, and looping — the Web Audio API gives you the tools. This guide walks through building a browser-based sampler with dynamic playback control using nothing but vanilla JavaScript.
Use Cases for Browser-Based Samplers
- Interactive music experiences
- Drum machines or pad samplers
- Real-time sound design tools
Step 1: Load the Sample
We’ll fetch a WAV/MP3 and decode it for playback:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
async function loadSample(url) {
const res = await fetch(url);
const arrayBuffer = await res.arrayBuffer();
return await audioCtx.decodeAudioData(arrayBuffer);
}
Step 2: Create a Sampler Function
We’ll build a function that creates a playback node each time a note is triggered:
function playSample(buffer, options = {}) {
const source = audioCtx.createBufferSource();
source.buffer = buffer;
const gain = audioCtx.createGain();
gain.gain.value = options.volume || 1.0;
source.playbackRate.value = options.pitch || 1.0;
if (options.loop) {
source.loop = true;
source.loopStart = options.loopStart || 0;
source.loopEnd = options.loopEnd || buffer.duration;
}
source.connect(gain).connect(audioCtx.destination);
source.start(audioCtx.currentTime, options.offset || 0);
return source;
}
Step 3: Trigger with UI or Keyboard
You can hook this into pads, keys, or sequencers:
let sampleBuffer;
loadSample('/samples/snare.wav').then(buffer => {
sampleBuffer = buffer;
});
document.getElementById('trigger').addEventListener('click', () => {
if (sampleBuffer) {
playSample(sampleBuffer, {
pitch: 1.2, // speed up slightly
offset: 0.05, // trim attack
volume: 0.8
});
}
});
Optional: Add a UI for Looping
Browser samplers can support loop regions:
playSample(sampleBuffer, {
loop: true,
loopStart: 0.2,
loopEnd: 0.8,
pitch: 0.8
});
Pros and Cons
✅ Pros
- No dependencies, loads instantly
- Pitch, trim, and loop all in-browser
- Modular and extensible (add envelopes, filters, UI)
⚠️ Cons
- Limited real-time editing without AudioWorklet
- No time-stretching — pitch alters speed
- Needs care to prevent overlapping or memory leaks