✏️ Übung: Web Audio API
Praktische Anwendung der Web Audio API
Lernziele
In dieser Übung wirst du:
- Die Web Audio API in der Praxis anwenden
- Audio-Graphen programmatisch erstellen
- Verschiedene Audio-Node-Typen verwenden
- Audio-Effekte implementieren
- Interaktive Audio-Anwendungen entwickeln
Voraussetzungen
- Grundkenntnisse in JavaScript
- Verständnis der Web Audio API Grundlagen
- Web-Browser mit Web Audio API Unterstützung
- Text-Editor
- Beispiel-Audiodateien (optional)
Theoretische Grundlagen
Web Audio API Konzepte
Die Web Audio API basiert auf folgenden zentralen Konzepten:
- AudioContext: Verwaltungsumgebung für Audio-Operationen
- AudioNodes: Bausteine für Audio-Verarbeitung (Quellen, Effekte, Ziele)
- AudioParams: Einstellbare Parameter (z.B. Frequenz, Gain)
- AudioGraph: Verbindungsstruktur zwischen den Nodes
Typische Anwendungsbereiche
- Web-basierte Musikinstrumente
- Audio-Visualisierungen
- Browser-basierte Audio-Editoren
- Interaktive Spiele mit Audio
- Audio-Streaming-Anwendungen
Aufgabenstellung
Aufgabe 1: Einfacher Oszillator
Erstelle einen einfachen Oszillator, der einen Ton abspielt:
- Erstelle einen AudioContext
- Erstelle einen Oszillator-Node
- Setze die Frequenz auf 440 Hz (A4)
- Verbinde den Oszillator mit dem destination-Node
- Starte den Oszillator
- Stoppe den Oszillator nach 2 Sekunden
- Erweitere das Beispiel um eine Frequenzänderung
Aufgabe 2: Audio-Datei Wiedergabe
Lade und spiele eine Audiodatei ab:
- Erstelle einen AudioContext
- Lade eine Audiodatei (z.B. über fetch API)
- Decodiere die Audiodatei zu einem AudioBuffer
- Erstelle einen BufferSource-Node
- Verbinde den BufferSource mit dem destination-Node
- Starte die Wiedergabe
- Implementiere Steuerelemente (Play, Pause, Stop)
Aufgabe 3: Effekt-Hinzufügung
Füge einen Effekt zur Audio-Wiedergabe hinzu:
- Erstelle einen Gain-Node für Lautstärkekontrolle
- Erstelle einen Filter-Node (z.B. Tiefpassfilter)
- Erstelle einen Delay-Node
- Verbinde die Nodes in der richtigen Reihenfolge
- Implementiere Steuerelemente für die Effekt-Parameter
- Teste verschiedene Effekt-Kombinationen
Praktische Anwendung
1. Erstellung eines einfachen Synthesizers
Implementiere einen einfachen Web-Synthesizer:
// HTML
<div id="synth">
<label>Frequenz: <input type="range" id="freqControl" min="50" max="1000" value="440"></label>
<label>Lautstärke: <input type="range" id="volControl" min="0" max="1" step="0.01" value="0.5"></label>
<button id="playBtn">Play</button>
<button id="stopBtn">Stop</button>
</div>
// JavaScript
class SimpleSynth {
constructor() {
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.oscillator = null;
this.gainNode = null;
this.setupControls();
}
setupControls() {
const freqControl = document.getElementById('freqControl');
const volControl = document.getElementById('volControl');
const playBtn = document.getElementById('playBtn');
const stopBtn = document.getElementById('stopBtn');
freqControl.addEventListener('input', (e) => {
if (this.oscillator) {
this.oscillator.frequency.value = parseFloat(e.target.value);
}
});
volControl.addEventListener('input', (e) => {
if (this.gainNode) {
this.gainNode.gain.value = parseFloat(e.target.value);
}
});
playBtn.addEventListener('click', () => this.play());
stopBtn.addEventListener('click', () => this.stop());
}
play() {
if (this.audioCtx.state === 'suspended') {
this.audioCtx.resume();
}
if (this.oscillator) {
this.oscillator.stop();
}
this.oscillator = this.audioCtx.createOscillator();
this.gainNode = this.audioCtx.createGain();
const freqControl = document.getElementById('freqControl');
const volControl = document.getElementById('volControl');
this.oscillator.type = 'sine';
this.oscillator.frequency.value = parseFloat(freqControl.value);
this.gainNode.gain.value = parseFloat(volControl.value);
this.oscillator.connect(this.gainNode);
this.gainNode.connect(this.audioCtx.destination);
this.oscillator.start();
}
stop() {
if (this.oscillator) {
this.oscillator.stop();
this.oscillator = null;
}
}
}
// Initialisierung
document.addEventListener('DOMContentLoaded', () => {
new SimpleSynth();
});
2. Audio-Visualisierung
Erstelle eine einfache Frequenz-Visualisierung:
// Erweitere die SimpleSynth Klasse
initVisualizer() {
this.analyser = this.audioCtx.createAnalyser();
this.analyser.fftSize = 2048;
// Verbinde nach dem Gain-Node
this.gainNode.connect(this.analyser);
this.analyser.connect(this.audioCtx.destination);
// Canvas für Visualisierung
this.canvas = document.createElement('canvas');
this.canvas.width = 600;
this.canvas.height = 200;
document.body.appendChild(this.canvas);
this.canvasCtx = this.canvas.getContext('2d');
this.drawVisualizer();
}
drawVisualizer() {
if (!this.analyser) return;
const bufferLength = this.analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const draw = () => {
requestAnimationFrame(draw);
this.analyser.getByteTimeDomainData(dataArray);
this.canvasCtx.fillStyle = 'rgb(0, 0, 0)';
this.canvasCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.canvasCtx.lineWidth = 2;
this.canvasCtx.strokeStyle = '#00bcd4';
this.canvasCtx.beginPath();
const sliceWidth = this.canvas.width * 1.0 / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * this.canvas.height / 2;
if (i === 0) {
this.canvasCtx.moveTo(x, y);
} else {
this.canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
this.canvasCtx.lineTo(this.canvas.width, this.canvas.height / 2);
this.canvasCtx.stroke();
};
draw();
}
Erweiterte Techniken
1. Effekt-Rack
Erstelle eine modulare Effekt-Verarbeitung:
class EffectChain {
constructor(audioCtx) {
this.audioCtx = audioCtx;
this.nodes = [];
this.input = null;
this.output = null;
}
addEffect(effectType, params) {
let effectNode;
switch(effectType) {
case 'filter':
effectNode = this.audioCtx.createBiquadFilter();
effectNode.type = params.type || 'lowpass';
effectNode.frequency.value = params.frequency || 1000;
break;
case 'delay':
effectNode = this.audioCtx.createDelay(params.maxDelay || 1.0);
effectNode.delayTime.value = params.delayTime || 0.5;
break;
case 'gain':
effectNode = this.audioCtx.createGain();
effectNode.gain.value = params.gain || 1.0;
break;
default:
throw new Error('Unbekannter Effekt-Typ');
}
this.nodes.push(effectNode);
return effectNode;
}
connect(input, output) {
let currentNode = input;
for (const node of this.nodes) {
currentNode.connect(node);
currentNode = node;
}
currentNode.connect(output);
}
}
2. Sample-Genauigkeit
Nutze die sample-genauen Funktionen der Web Audio API:
// Starte Audio zu einem exakten Zeitpunkt
const startTime = audioCtx.currentTime + 0.1; // Starte in 100ms
oscillator.start(startTime);
// Stoppe zu einem exakten Zeitpunkt
const stopTime = startTime + 2.0; // Stoppe nach 2 Sekunden
oscillator.stop(stopTime);
// Automatisiere Parameter zu exakten Zeitpunkten
gainNode.gain.setValueAtTime(0.5, startTime);
gainNode.gain.linearRampToValueAtTime(1.0, startTime + 0.5);
gainNode.gain.exponentialRampToValueAtTime(0.1, startTime + 2.0);
Best Practices
Performance
- Vermeide das Erstellen neuer Nodes in Animationsschleifen
- Wiederverwendung von Nodes wenn möglich
- Effiziente Verarbeitung in ScriptProcessorNodes vermeiden
- Nutze AudioWorklets für komplexe Verarbeitung (wenn verfügbar)
Browser-Kompatibilität
// Sichere Initialisierung des AudioContext
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
// Resumiere Context falls nötig
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
Sicherheit
- AudioContext erst nach Benutzerinteraktion starten
- HTTPS für Audio-Streaming verwenden
- Keine sensiblen Daten über Audio-Streams übertragen
Problembehandlung
Häufige Probleme
- Kein Ton: Prüfe, ob AudioContext nach Benutzerinteraktion gestartet wurde
- Verzögerungen: Prüfe die Latenz-Einstellungen und Buffer-Größen
- Clipping: Stelle sicher, dass die Gesamtlautstärke unter 1.0 liegt
- Memory Leaks: Trenne alle Verbindungen und stoppe Nodes vor dem Entfernen
Debugging
// Debugging-Hilfe
console.log('AudioContext State:', audioCtx.state);
console.log('Sample Rate:', audioCtx.sampleRate);
// Node-Status überprüfen
if (oscillator) {
console.log('Oscillator State:', oscillator.playbackState);
}
// Verbindungen visualisieren
function logConnections(node, prefix = '') {
console.log(`${prefix}${node.constructor.name}`);
// Beachte: Es gibt keine direkte Methode, um Verbindungen zu überprüfen
// Du musst deine eigene Verwaltung implementieren
}
Auswertung und Dokumentation
Zu dokumentierende Ergebnisse
- Welche Audio-Elemente wurden erfolgreich implementiert?
- Welche Herausforderungen traten bei der Implementierung auf?
- Wie unterscheidet sich die Web Audio API von anderen Audio-APIs?
- Welche Performance-Aspekte wurden beobachtet?
- Wie könnte die Implementierung verbessert werden?
Reflexion
Beantworte folgende Fragen in deiner Dokumentation:
- Wie intuitiv war die Web Audio API für dich?
- Welche Konzepte waren besonders schwierig zu verstehen?
- Wie unterscheidet sich die Web Audio API von Desktop-Audio-Software?
- Welche Anwendungsmöglichkeiten siehst du für deine Projekte?
- Wie könntest du die Web Audio API in zukünftigen Web-Anwendungen einsetzen?
Vertiefung
Erweiterungsaufgaben
- Erstelle einen vollständigen Web-Synthesizer mit mehreren Oszillatoren
- Implementiere einen Drum-Machine mit Sample-Playback
- Erstelle eine Audio-Visualisierung mit WebGL
- Entwickle eine Web-Audio-Sequencer-Anwendung
- Erstelle eine Spracherkennung mit Web Speech API und Web Audio API