WEB-AUDIO-API AUDIOWORKLET WEB-MIDI-API WEBASSEMBLY RUST DSP REAL-TIME BROWSER-API WEB-DEVELOPMENT FRONTEND PERFORMANCE MUSIC-TECH INTERACTIVE-WEB JAVASCRIPT

Membangun Synthesizer Musik di Browser: Memadukan Web MIDI, AudioWorklet, dan WebAssembly untuk Pemrosesan Audio Real-time

⏱️ 8 menit baca
👨‍💻

Membangun Synthesizer Musik di Browser: Memadukan Web MIDI, AudioWorklet, dan WebAssembly untuk Pemrosesan Audio Real-time

1. Pendahuluan

Pernahkah Anda membayangkan membuat alat musik atau efek audio yang kompleks, lengkap dengan interaksi real-time, langsung di dalam browser web? Dulu, ini mungkin terdengar seperti mimpi di siang bolong. Namun, berkat evolusi pesat standar web dan API browser, hari ini kita bisa mewujudkan hal tersebut!

Artikel ini akan membawa Anda dalam perjalanan seru untuk membangun sebuah synthesizer musik sederhana di browser, memanfaatkan tiga pilar teknologi web modern:

  1. Web MIDI API: Untuk menerima input dari perangkat MIDI fisik (keyboard, controller) atau virtual.
  2. AudioWorklet API: Mesin pemroses audio real-time yang berjalan di audio thread terpisah, memastikan latensi rendah dan performa stabil.
  3. WebAssembly (Wasm): Untuk menjalankan algoritma pemrosesan sinyal digital (DSP) yang intensif komputasi dengan performa mendekati native.

Mengapa topik ini penting? Karena ini menunjukkan potensi luar biasa web sebagai platform untuk aplikasi yang sangat interaktif dan berkinerja tinggi. Jika Anda bisa membuat synthesizer di browser, bayangkan apa lagi yang bisa Anda ciptakan: editor video real-time, simulasi fisika kompleks, atau bahkan game dengan grafis berat! Mari kita selami!

2. Web MIDI API: Menghubungkan Browser dengan Dunia Musik Anda

📌 Masalah yang Dipecahkan: Bagaimana cara aplikasi web kita “mendengar” perintah dari keyboard MIDI atau perangkat musik lainnya?

Web MIDI API adalah jembatan yang memungkinkan browser Anda berkomunikasi dengan perangkat MIDI. MIDI (Musical Instrument Digital Interface) adalah protokol standar yang digunakan alat musik elektronik untuk berkomunikasi satu sama lain. Dengan Web MIDI, Anda bisa:

💡 Analogi: Bayangkan Web MIDI sebagai penerjemah universal yang memungkinkan browser Anda memahami “bahasa” dari keyboard musik atau controller Anda.

Contoh Sederhana Web MIDI

Pertama, kita perlu meminta izin dari pengguna untuk mengakses perangkat MIDI.

async function setupMidi() {
  if (!navigator.requestMIDIAccess) {
    console.warn("Web MIDI API tidak didukung di browser ini.");
    return;
  }

  try {
    const midiAccess = await navigator.requestMIDIAccess({ sysex: false });
    console.log("Akses MIDI diberikan.");

    // Iterasi melalui input MIDI yang tersedia
    midiAccess.inputs.forEach((input) => {
      console.log(`Input MIDI: ${input.name} (${input.manufacturer})`);
      input.onmidimessage = handleMidiMessage;
    });

    // Handle saat perangkat MIDI baru ditambahkan/dihapus
    midiAccess.onstatechange = (event) => {
      console.log(`Perangkat MIDI berubah: ${event.port.name} - ${event.port.state}`);
      // Anda mungkin ingin memperbarui daftar input di UI di sini
    };

  } catch (error) {
    console.error(`Gagal mengakses MIDI: ${error}`);
  }
}

function handleMidiMessage(event) {
  const [command, note, velocity] = event.data;
  // Command: 144 (Note On), 128 (Note Off)
  // Note: 0-127 (misal: 60 = C4)
  // Velocity: 0-127 (seberapa keras tombol ditekan)

  if (command === 144 && velocity > 0) {
    console.log(`Note ON: ${note}, Velocity: ${velocity}`);
    // Di sini kita akan memicu suara di synthesizer kita
  } else if (command === 128 || (command === 144 && velocity === 0)) {
    console.log(`Note OFF: ${note}`);
    // Di sini kita akan menghentikan suara di synthesizer kita
  }
}

setupMidi();

Tips: Selalu tangani kasus di mana Web MIDI tidak didukung atau izin ditolak. Pastikan juga untuk memberikan indikator visual kepada pengguna saat meminta izin.

3. AudioWorklet API: Mesin Pemroses Audio Real-time Anda

📌 Masalah yang Dipecahkan: Bagaimana cara memproses audio secara real-time dengan latensi sangat rendah, tanpa mengganggu responsivitas UI utama browser (main thread)?

AudioWorklet adalah bagian dari Web Audio API yang memungkinkan Anda menjalankan kode JavaScript kustom di audio rendering thread. Ini krusial untuk aplikasi audio real-time karena:

💡 Analogi: Anggap AudioWorklet sebagai seorang teknisi audio profesional yang memiliki ruang studio terpisah dan bekerja tanpa henti untuk menghasilkan suara, sementara Anda (main thread) sibuk mengatur lampu panggung atau menerima instruksi dari penonton.

Struktur AudioWorklet

AudioWorklet terdiri dari dua bagian:

  1. AudioWorkletProcessor: Ini adalah kelas JavaScript yang didefinisikan dalam file terpisah (misalnya synth-processor.js) dan dimuat ke dalam AudioWorklet global scope. Di sinilah logika pemrosesan audio real-time Anda berada. Metode process() akan dipanggil berulang kali untuk menghasilkan audio buffer.
  2. AudioWorkletNode: Ini adalah antarmuka di main thread yang memungkinkan Anda berkomunikasi dengan AudioWorkletProcessor dan menghubungkannya ke Web Audio Graph.

Contoh AudioWorklet Sederhana (Sine Wave Generator)

File: synth-processor.js

// synth-processor.js
class SynthProcessor extends AudioWorkletProcessor {
  static get parameterDescriptors() {
    // Kita bisa mendefinisikan parameter yang bisa dikontrol dari main thread
    return [{
      name: 'frequency',
      defaultValue: 440, // A4
      minValue: 20,
      maxValue: 20000,
    }, {
      name: 'gain',
      defaultValue: 0.5,
      minValue: 0,
      maxValue: 1,
    }];
  }

  constructor() {
    super();
    this.phase = 0; // Untuk melacak fase gelombang sinus
  }

  process(inputs, outputs, parameters) {
    const output = outputs[0]; // Ambil output channel pertama
    const frequency = parameters.frequency;
    const gain = parameters.gain;
    const sampleRate = 48000; // Contoh: context.sampleRate di main thread

    for (let channel = 0; channel < output.length; channel++) {
      const outputChannel = output[channel];
      for (let i = 0; i < outputChannel.length; i++) {
        const currentFreq = (frequency.length > 1) ? frequency[i] : frequency[0];
        const currentGain = (gain.length > 1) ? gain[i] : gain[0];

        // Generasi gelombang sinus
        outputChannel[i] = Math.sin(this.phase * 2 * Math.PI) * currentGain;
        this.phase += currentFreq / sampleRate;
        if (this.phase >= 1) {
          this.phase -= 1;
        }
      }
    }
    return true; // Terus proses
  }
}

registerProcessor('synth-processor', SynthProcessor);

File: main.js (di main thread)

// main.js
async function setupAudioWorklet() {
  const audioContext = new AudioContext(); // Pastikan hanya ada satu AudioContext

  await audioContext.audioWorklet.addModule('synth-processor.js');
  console.log("AudioWorklet module dimuat.");

  const synthNode = new AudioWorkletNode(audioContext, 'synth-processor');

  // Hubungkan ke output audio
  synthNode.connect(audioContext.destination);

  // Kontrol parameter dari main thread
  synthNode.parameters.get('frequency').setValueAtTime(440, audioContext.currentTime);
  synthNode.parameters.get('gain').setValueAtTime(0.7, audioContext.currentTime);

  // Contoh mengubah frekuensi setelah beberapa detik
  setTimeout(() => {
    synthNode.parameters.get('frequency').linearRampToValueAtTime(880, audioContext.currentTime + 2);
  }, 3000);

  return { audioContext, synthNode };
}

// Untuk memulai audio context saat ada interaksi pengguna
document.addEventListener('click', async () => {
  if (!window.audioSetupDone) {
    const { audioContext, synthNode } = await setupAudioWorklet();
    window.audioSetupDone = true;
    console.log("Audio context dimulai.");
    // Anda bisa menyimpan synthNode untuk kontrol lebih lanjut
  }
}, { once: true });

⚠️ Penting: Browser modern memerlukan interaksi pengguna (klik, sentuh) untuk memulai AudioContext. Pastikan Anda menanganinya.

4. WebAssembly (Wasm) untuk DSP Berkinerja Tinggi

📌 Masalah yang Dipecahkan: Algoritma DSP (misalnya, filter, reverb, osilator kompleks) seringkali sangat intensif komputasi. JavaScript, meski cepat, kadang tidak cukup untuk performa real-time absolut di semua skenario.

Di sinilah WebAssembly bersinar! Dengan Wasm, Anda bisa menulis logika DSP Anda dalam bahasa seperti Rust, C++, atau Go, lalu mengkompilasinya ke binary Wasm. Ini memberikan performa mendekati native, yang sangat penting untuk pemrosesan audio dengan latensi ultra-rendah.

💡 Analogi: Jika AudioWorklet adalah studio audio profesional, maka WebAssembly adalah chip pemrosesan sinyal khusus yang sangat cepat di dalam studio tersebut, dirancang untuk tugas-tugas komputasi berat.

Mengapa Wasm untuk DSP?

Alur Kerja (Rust ke Wasm)

  1. Tulis Logika DSP di Rust: Buat fungsi-fungsi yang menerima input audio buffer dan menghasilkan output audio buffer.
  2. Kompilasi ke Wasm: Gunakan wasm-pack atau cargo web untuk mengkompilasi kode Rust Anda menjadi modul Wasm.
  3. Muat Wasm di AudioWorklet: Modul Wasm dapat dimuat dan diinstansiasi langsung di dalam AudioWorkletProcessor.

Contoh Struktur Kode Rust (abstrak)

// src/lib.rs
#[no_mangle]
pub extern "C" fn process_audio_wasm(
    input_ptr: *mut f32,
    output_ptr: *mut f32,
    num_samples: usize,
    frequency: f32,
    gain: f32,
    // ... parameter synth lainnya