Web Audio API компоненты

Lit — современный фреймворк для создания веб-компонентов, который позволяет писать компактный, декларативный код с реактивным обновлением. Web Audio API предоставляет мощный интерфейс для работы с аудио в браузере, включая генерацию звуков, обработку сигналов и создание интерактивных аудиоэффектов. Совмещение этих технологий открывает возможности для создания динамических аудиокомпонентов.

Создание аудиоконтекста и базовой структуры компонента

Каждый компонент, работающий с Web Audio API, требует создания AudioContext. В Lit это удобно делать в методе connectedCallback, который вызывается при добавлении элемента в DOM:

import { LitElement, html, css } from 'lit';
import { property, state } from 'lit/decorators.js';

class AudioPlayer extends LitElement {
  static styles = css`
    button { margin: 5px; padding: 10px; }
  `;

  @state() playing = false;

  constructor() {
    super();
    this.audioContext = null;
    this.oscillator = null;
  }

  connectedCallback() {
    super.connectedCallback();
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
  }

  render() {
    return html`
      <button @click=${this.toggleAudio}>
        ${this.playing ? 'Стоп' : 'Играть'}
      </button>
    `;
  }

  toggleAudio() {
    if (this.playing) {
      this.stopAudio();
    } else {
      this.startAudio();
    }
  }

  startAudio() {
    this.oscillator = this.audioContext.createOscillator();
    this.oscillator.type = 'sine';
    this.oscillator.frequency.setValueAtTime(440, this.audioContext.currentTime);
    this.oscillator.connect(this.audioContext.destination);
    this.oscillator.start();
    this.playing = true;
  }

  stopAudio() {
    this.oscillator.stop();
    this.oscillator.disconnect();
    this.oscillator = null;
    this.playing = false;
  }
}

customElements.define('audio-player', AudioPlayer);

Ключевые моменты:

  • @state() используется для реактивного свойства состояния компонента.
  • Аудиообъекты создаются и подключаются динамически.
  • Контекст создается один раз при инициализации, что предотвращает многократное создание AudioContext.

Работа с аудиоэффектами и фильтрами

Web Audio API предоставляет узлы обработки сигналов, такие как GainNode, BiquadFilterNode, AnalyserNode. В Lit их легко интегрировать с реактивным состоянием:

startAudio() {
  this.oscillator = this.audioContext.createOscillator();
  this.gainNode = this.audioContext.createGain();
  this.filterNode = this.audioContext.createBiquadFilter();

  this.oscillator.type = 'square';
  this.oscillator.frequency.setValueAtTime(220, this.audioContext.currentTime);

  this.gainNode.gain.setValueAtTime(0.5, this.audioContext.currentTime);
  this.filterNode.type = 'lowpass';
  this.filterNode.frequency.setValueAtTime(1000, this.audioContext.currentTime);

  this.oscillator.connect(this.filterNode);
  this.filterNode.connect(this.gainNode);
  this.gainNode.connect(this.audioContext.destination);

  this.oscillator.start();
  this.playing = true;
}

Выделенные моменты:

  • Цепочка узлов позволяет гибко комбинировать эффекты.
  • GainNode регулирует громкость, BiquadFilterNode — фильтры разных типов.
  • Параметры можно изменять динамически с помощью методов setValueAtTime и анимации сигналов.

Анимация аудиосигнала с AnalyserNode

Для визуализации звукового сигнала используется AnalyserNode, который позволяет получить данные частотного спектра:

startAudio() {
  this.oscillator = this.audioContext.createOscillator();
  this.analyser = this.audioContext.createAnalyser();
  this.oscillator.connect(this.analyser);
  this.analyser.connect(this.audioContext.destination);
  this.oscillator.start();

  this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
  this.animate();
}

animate() {
  this.analyser.getByteFrequencyData(this.dataArray);
  // данные можно использовать для отрисовки графика
  requestAnimationFrame(() => this.animate());
}

Особенности:

  • AnalyserNode предоставляет массив данных dataArray в реальном времени.
  • requestAnimationFrame обеспечивает плавную анимацию спектра.
  • Можно интегрировать с <canvas> внутри компонента Lit для визуальных эффектов.

Реактивное управление параметрами аудио

Lit позволяет автоматически обновлять интерфейс при изменении свойств. Например, частоту или громкость можно привязать к слайдерам:

render() {
  return html`
    <input type="range" min="100" max="1000" .value=${this.frequency} @input=${this.updateFrequency}>
    <input type="range" min="0" max="1" step="0.01" .value=${this.volume} @input=${this.updateVolume}>
    <button @click=${this.toggleAudio}>${this.playing ? 'Стоп' : 'Играть'}</button>
  `;
}

updateFrequency(e) {
  this.frequency = e.target.value;
  if (this.oscillator) {
    this.oscillator.frequency.setValueAtTime(this.frequency, this.audioContext.currentTime);
  }
}

updateVolume(e) {
  this.volume = e.target.value;
  if (this.gainNode) {
    this.gainNode.gain.setValueAtTime(this.volume, this.audioContext.currentTime);
  }
}

Принципы:

  • Свойства слайдеров синхронизируются с узлами аудио в реальном времени.
  • Реактивное обновление позволяет не пересоздавать узлы при каждом изменении.
  • Интерактивность полностью сохраняется внутри компонента без глобальных переменных.

Комбинация нескольких осцилляторов и эффектов

Для сложного звукового дизайна создаются несколько параллельных осцилляторов, которые объединяются через GainNode или StereoPannerNode:

startAudio() {
  this.oscillators = [];
  this.gainNode = this.audioContext.createGain();
  this.gainNode.gain.setValueAtTime(0.5, this.audioContext.currentTime);
  this.gainNode.connect(this.audioContext.destination);

  [220, 440, 880].forEach(freq => {
    const osc = this.audioContext.createOscillator();
    osc.frequency.setValueAtTime(freq, this.audioContext.currentTime);
    osc.connect(this.gainNode);
    osc.start();
    this.oscillators.push(osc);
  });

  this.playing = true;
}

Особенности:

  • Каждый осциллятор можно управлять индивидуально.
  • Параллельное соединение через общий GainNode упрощает регулировку громкости.
  • Схема легко расширяется фильтрами, панорамированием и другими эффектами.

Управление жизненным циклом и производительностью

  • connectedCallback и disconnectedCallback — ключевые методы для инициализации и очистки аудиоресурсов.
  • Необходимо вызывать stop() и disconnect() для всех узлов при удалении компонента, чтобы избежать утечек памяти.
  • Для сложных эффектов рекомендуется использовать отдельные функции генерации цепочек аудиоустройств, чтобы код оставался модульным и читаемым.

Этот подход позволяет создавать полноценные аудиокомпоненты на Lit с поддержкой интерактивности, эффектов и визуализации в реальном времени, полностью управляемые через реактивные свойства.