Реалізація Web Audio API для аудіозастосунків

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Web Audio API для аудіозастосунків
Складна
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Web Audio API для аудіоприкладів

Web Audio API — низькорівневий звуковий рушій у браузері. Аудіо обробляється через граф вузлів: джерело → обробка (gain, filter, reverb, analyser) → destination (динаміки). Працює в окремому потоці, не блокує UI, дає сэмплточне управління відтворенням. Тег <audio> підходить для простого плеєра. Web Audio API — для секвенсорів, синтезаторів, візуалізацій, ефектів у реальному часі.

AudioContext та базовий граф

class AudioEngine {
  private ctx: AudioContext
  private masterGain: GainNode
  private analyser: AnalyserNode
  private compressor: DynamicsCompressorNode

  constructor() {
    this.ctx = new AudioContext({ sampleRate: 44100 })

    // Master chain: input → compressor → gain → analyser → speakers
    this.compressor = this.ctx.createDynamicsCompressor()
    this.compressor.threshold.value = -24
    this.compressor.knee.value = 30
    this.compressor.ratio.value = 4
    this.compressor.attack.value = 0.003
    this.compressor.release.value = 0.25

    this.masterGain = this.ctx.createGain()
    this.masterGain.gain.value = 0.8

    this.analyser = this.ctx.createAnalyser()
    this.analyser.fftSize = 2048

    this.compressor
      .connect(this.masterGain)
      .connect(this.analyser)
      .connect(this.ctx.destination)
  }

  get destination(): AudioNode {
    return this.compressor
  }

  setMasterVolume(value: number) {
    // Плавне зміни гучності без щелчків
    this.masterGain.gain.linearRampToValueAtTime(
      value,
      this.ctx.currentTime + 0.05
    )
  }

  resume() {
    // Браузер вимагає активації AudioContext після користувацького жесту
    return this.ctx.resume()
  }
}

Завантаження та відтворення звуків

class SoundLoader {
  private ctx: AudioContext
  private cache: Map<string, AudioBuffer> = new Map()

  constructor(ctx: AudioContext) {
    this.ctx = ctx
  }

  async load(url: string): Promise<AudioBuffer> {
    if (this.cache.has(url)) return this.cache.get(url)!

    const response = await fetch(url)
    const arrayBuffer = await response.arrayBuffer()
    const audioBuffer = await this.ctx.decodeAudioData(arrayBuffer)

    this.cache.set(url, audioBuffer)
    return audioBuffer
  }

  play(
    buffer: AudioBuffer,
    destination: AudioNode,
    options: {
      when?: number      // Час старту (ctx.currentTime для зараз)
      offset?: number    // Старт з позиції в секундах
      loop?: boolean
      playbackRate?: number
      onEnded?: () => void
    } = {}
  ): AudioBufferSourceNode {
    const source = this.ctx.createBufferSource()
    source.buffer = buffer
    source.loop = options.loop ?? false
    source.playbackRate.value = options.playbackRate ?? 1

    source.connect(destination)
    source.start(options.when ?? this.ctx.currentTime, options.offset ?? 0)

    if (options.onEnded) source.onended = options.onEnded

    return source
  }
}

Візуалізація: осцилоскоп та спектр

function AudioVisualizer({ analyser }: { analyser: AnalyserNode }) {
  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    const canvas = canvasRef.current!
    const ctx = canvas.getContext('2d')!
    const bufferLength = analyser.frequencyBinCount
    const dataArray = new Uint8Array(bufferLength)

    let animFrameId: number

    function draw() {
      animFrameId = requestAnimationFrame(draw)

      // Переключаємось між waveform та frequency
      // analyser.getByteTimeDomainData(dataArray)  // Осцилоскоп
      analyser.getByteFrequencyData(dataArray)       // Спектр

      ctx.fillStyle = '#0f172a'
      ctx.fillRect(0, 0, canvas.width, canvas.height)

      const barWidth = canvas.width / bufferLength * 2.5
      let x = 0

      for (let i = 0; i < bufferLength; i++) {
        const barHeight = (dataArray[i] / 255) * canvas.height

        // Градієнт від синього до фіолетового
        const hue = 220 + (dataArray[i] / 255) * 60
        ctx.fillStyle = `hsl(${hue}, 80%, 60%)`
        ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight)

        x += barWidth + 1
      }
    }

    draw()
    return () => cancelAnimationFrame(animFrameId)
  }, [analyser])

  return (
    <canvas
      ref={canvasRef}
      width={800}
      height={200}
      className="w-full rounded-lg"
    />
  )
}

Step-секвенсор

interface SequencerStep {
  active: boolean
  velocity: number  // 0–1
}

class StepSequencer {
  private ctx: AudioContext
  private steps: SequencerStep[][]  // [track][step]
  private currentStep = 0
  private bpm: number
  private nextNoteTime = 0
  private timerID: number | null = null
  private sounds: AudioBuffer[]

  constructor(ctx: AudioContext, sounds: AudioBuffer[], bpm = 120) {
    this.ctx = ctx
    this.sounds = sounds
    this.bpm = bpm
    this.steps = sounds.map(() => Array(16).fill({ active: false, velocity: 0.8 }))
  }

  private scheduleNote(trackIndex: number, time: number, velocity: number) {
    const source = this.ctx.createBufferSource()
    source.buffer = this.sounds[trackIndex]

    const gainNode = this.ctx.createGain()
    gainNode.gain.value = velocity

    source.connect(gainNode)
    gainNode.connect(this.ctx.destination)
    source.start(time)
  }

  private scheduler() {
    const secondsPerBeat = 60.0 / this.bpm
    const secondsPerStep = secondsPerBeat / 4  // 16th notes

    while (this.nextNoteTime < this.ctx.currentTime + 0.1) {
      // Планюємо ноти на 100ms вперед
      this.steps.forEach((track, trackIndex) => {
        const step = track[this.currentStep]
        if (step.active) {
          this.scheduleNote(trackIndex, this.nextNoteTime, step.velocity)
        }
      })

      this.currentStep = (this.currentStep + 1) % 16
      this.nextNoteTime += secondsPerStep
    }

    this.timerID = window.setTimeout(() => this.scheduler(), 25)
  }

  start() {
    this.nextNoteTime = this.ctx.currentTime
    this.scheduler()
  }

  stop() {
    if (this.timerID) clearTimeout(this.timerID)
  }

  setStep(track: number, step: number, active: boolean, velocity = 0.8) {
    this.steps[track][step] = { active, velocity }
  }
}

Синтез: осцилятор + огибаюча ADSR

function playNote(ctx: AudioContext, frequency: number, destination: AudioNode) {
  const osc = ctx.createOscillator()
  const gainNode = ctx.createGain()

  osc.type = 'sawtooth'
  osc.frequency.value = frequency

  // Low-pass filter для теплого звуку
  const filter = ctx.createBiquadFilter()
  filter.type = 'lowpass'
  filter.frequency.value = 2000
  filter.Q.value = 2

  osc.connect(filter)
  filter.connect(gainNode)
  gainNode.connect(destination)

  const now = ctx.currentTime

  // ADSR огибаюча
  gainNode.gain.setValueAtTime(0, now)
  gainNode.gain.linearRampToValueAtTime(0.7, now + 0.01)  // Attack: 10ms
  gainNode.gain.linearRampToValueAtTime(0.4, now + 0.1)   // Decay: 90ms → Sustain 0.4
  gainNode.gain.linearRampToValueAtTime(0, now + 0.5)      // Release: 400ms

  osc.start(now)
  osc.stop(now + 0.55)  // Після закінчення release
}

Що ми робимо

Аналізуємо завдання: плеєр з візуалізацією, секвенсор ударних, синтезатор, аналіз мікрофонного входу. Проектуємо граф аудіовузлів, реалізуємо UI управління (play/stop/tempo/volume), додаємо візуалізацію через AnalyserNode. Вирішуємо питання autoplay policy — AudioContext вимагає жесту користувача для старту.

Строк: аудіоплеєр з візуалізатором — 2–3 дні. Секвенсор або синтезатор — 6–10 днів.