Chapter 15
Audio Processing
Learning Objectives
- Understand audio processing concepts
- Learn to manipulate audio buffers
- Apply effects and transformations
- See how MixMind processes audio
Audio Buffer Manipulation
Audio buffers are just arrays of numbers. We can manipulate them directly:
// Get audio data
const channelData = audioBuffer.getChannelData(0); // Left channel
// Reverse the audio
const reversed = new Float32Array(channelData.length);
for (let i = 0; i < channelData.length; i++) {
reversed[i] = channelData[channelData.length - 1 - i];
}
// Normalize (make loudest part = 1.0)
let maxAmplitude = 0;
for (let i = 0; i < channelData.length; i++) {
maxAmplitude = Math.max(maxAmplitude, Math.abs(channelData[i]));
}
const normalized = new Float32Array(channelData.length);
for (let i = 0; i < channelData.length; i++) {
normalized[i] = channelData[i] / maxAmplitude;
}
Volume and Gain
// Apply gain (volume adjustment)
function applyGain(audioBuffer, gainValue) {
const output = audioContext.createBuffer(
audioBuffer.numberOfChannels,
audioBuffer.length,
audioBuffer.sampleRate
);
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
const input = audioBuffer.getChannelData(channel);
const outputChannel = output.getChannelData(channel);
for (let i = 0; i < input.length; i++) {
outputChannel[i] = input[i] * gainValue;
}
}
return output;
}
// Fade in/out
function applyFade(audioBuffer, fadeDuration) {
const fadeSamples = fadeDuration * audioBuffer.sampleRate;
const output = audioBuffer;
// Fade in
for (let channel = 0; channel < output.numberOfChannels; channel++) {
const data = output.getChannelData(channel);
for (let i = 0; i < fadeSamples; i++) {
data[i] *= i / fadeSamples;
}
}
return output;
}
Mixing Multiple Tracks
// Mix two audio buffers together
function mixBuffers(buffer1, buffer2) {
const length = Math.max(buffer1.length, buffer2.length);
const output = audioContext.createBuffer(
2, // stereo
length,
buffer1.sampleRate
);
for (let channel = 0; channel < 2; channel++) {
const data1 = buffer1.getChannelData(channel);
const data2 = buffer2.getChannelData(channel);
const outputChannel = output.getChannelData(channel);
for (let i = 0; i < length; i++) {
const sample1 = i < data1.length ? data1[i] : 0;
const sample2 = i < data2.length ? data2[i] : 0;
// Mix with clipping protection
outputChannel[i] = Math.max(-1, Math.min(1,
(sample1 + sample2) * 0.5
));
}
}
return output;
}
Crossfade
// Create a crossfade between two tracks
function crossfade(buffer1, buffer2, fadeDuration) {
const fadeSamples = Math.floor(fadeDuration * buffer1.sampleRate);
const totalLength = buffer1.length + buffer2.length - fadeSamples;
const output = audioContext.createBuffer(
2,
totalLength,
buffer1.sampleRate
);
for (let channel = 0; channel < 2; channel++) {
const data1 = buffer1.getChannelData(channel);
const data2 = buffer2.getChannelData(channel);
const outputChannel = output.getChannelData(channel);
let outIndex = 0;
// Copy buffer1 until fade start
const fadeStart = buffer1.length - fadeSamples;
for (let i = 0; i < fadeStart; i++) {
outputChannel[outIndex++] = data1[i];
}
// Crossfade region
for (let i = 0; i < fadeSamples; i++) {
const fadeOut = 1 - (i / fadeSamples);
const fadeIn = i / fadeSamples;
const sample1 = data1[fadeStart + i] * fadeOut;
const sample2 = data2[i] * fadeIn;
outputChannel[outIndex++] = sample1 + sample2;
}
// Copy rest of buffer2
for (let i = fadeSamples; i < buffer2.length; i++) {
outputChannel[outIndex++] = data2[i];
}
}
return output;
}
🎵 MixMind Connection
How MixMind Processes Audio
// MixMind's transition engine
class TransitionEngine {
async createTransition(track1, track2, settings) {
const { crossfadeDuration, type } = settings;
switch(type) {
case 'crossfade':
return this.crossfade(track1.buffer, track2.buffer, crossfadeDuration);
case 'cut':
return this.hardCut(track1.buffer, track2.buffer);
case 'eq_blend':
return this.eqBlend(track1.buffer, track2.buffer, crossfadeDuration);
}
}
// EQ blend - fade bass out, fade bass in
async eqBlend(buffer1, buffer2, duration) {
// Apply low-pass filter fade to track 1
const filtered1 = await this.applyFilterFade(buffer1, 'lowpass', duration, true);
// Apply high-pass filter fade to track 2
const filtered2 = await this.applyFilterFade(buffer2, 'highpass', duration, false);
return this.mixBuffers(filtered1, filtered2);
}
}
// Rendering the final mix
async function renderMix(tracks, transitions) {
let output = tracks[0].buffer;
for (let i = 1; i < tracks.length; i++) {
const transition = transitions[i - 1];
output = await transitionEngine.createTransition(
{ buffer: output },
tracks[i],
transition.settings
);
}
return output;
}
Exercise 15.1: Practice
Goal: Apply what you've learned in this chapter
Try the concepts from this chapter in your browser's console or a simple HTML file. Experiment with the code examples and modify them to see what happens.
📝 Quick Reference:
Review the key concepts from this chapter and make sure you understand each one before moving to the next chapter.