likes
comments
collection
share

用Web Audio API做个简易电子琴

作者站长头像
站长
· 阅读数 5

前言

Web Audio API 提供了在浏览器中进行音频处理和合成的能力,使得我们可以创建各种类型的音频应用程序。在这篇文档中,我们将使用 Web Audio API 来创建一个简易小电子琴

什么是Web Audio API

Web Audio API提供了一组在web上操作音频的API,使用户可以在音频上下文(AudioContext)中进行音频操作。提供了相比html <audio>标签更丰富、更复杂的音频处理能力,甚至可以通过波形生成器进行音频的创作。<audio>的功能比较类似于<img>,AudioContext则比较类似于canvasContext,二者在不同的场景有不同的用途。

理解Web Audio API

现实生活中的场景

首先我们先看看,现实生活中的我们听到的音频是怎么来的,我们这里以一个边弹边唱的吉他手为例,他的接线大概是这样的

用Web Audio API做个简易电子琴

我们知道声音的本质都是波形。演奏者通过拨吉他弦,可以产生一种类似正弦波的波形,这里吉他的音波之后经过失真效果器处理,音波会作为电信号输入到效果器的电路中,将吉他波形处理成带失真的方波在输入给音响,音响接受到的声波就是电吉他的铮铮乍、铮铮乍的声音。

话筒则是通过振膜采集演唱者的声音的振动,将其转化为电信号输入给音响。手机播放的音频则直接将存储好的音频信号输入给音响。

音响则最后负责将所有声波合到一起,放大后播放出来,这样我们就听到了又有伴奏又有吉他又有歌声的音频了

Web Audio的场景

Web Audio API则是用模拟的方式完成了这些工作(和现实生活中的合成器/电子琴非常像,用数字模拟而非电信号处理)。

Web Audio具有模块化路由的特点。在一个个音频节点(AudioNode)上操作进行基础的音频,它们连接在一起构成音频路由图。我们依然用弹唱的场景举例,如下图

用Web Audio API做个简易电子琴

这里用到的节点分别如下

AudioContext/OfflineAudioContext

音频上下文,所有音频的控制和处理,都在Context的基础上进行,类比canvas的ctx。AudioContext会实时的在硬件上渲染音频,而OfflineAudioContext则是会尽可能快的将音频计算成一个AudioBuffer作为结果

OscillatorNode

OscillatorNode 接口表示一个振荡器(或者说一个信号发生器),它产生一个周期的波形信号,是一个音频源节点。这个模块会生成一个指定频率的波形信号(即一个固定的音调)。同时也提供了setPeriodicWave可以自定义波形,我们可以通过自定义波形直接模拟出失真过后的吉他声音,这里的OscillatorNode作用可以相当于吉他+效果器一起。

GainNode

GainNode 表示音量的变化,是一个音频处理接节点。因为OscillatorNode创造的是规律持续的波形,但吉他的声音并不是持续不断的,所以需要GainNode来控制具体的声音表现。

MediaStreamSourceNode

是一个音频源节点,是MediaStream(比如一个摄像头或者麦克风)的一部分,这里可以用来接受麦克风输入。

AudioBufferSourceNode

也是一个音频源节点,用来播放存在内存中的音频数据(AudioData),这里可以用来播放伴奏。

AudioDestinationNode

音频图形在特定情况下的最终输出地址,对于AudioContext通常是输出到扬声器(类比与音响),而OfflineAudioContext则输出到音频记录节点(类比输入到录音机里去了)

在此之外,就像吉他有Delay(延时)、Compressor(压缩)等众多效果器一样,Web Audio API也有众多的其他node来实现各种各样的作用,具体可以查看文档

做个简易合成器

具体实现

接下来我们就用Web Audio做一个小电子琴,非常简单。首先我们需要先创建好所需的context与node,并且把它们连起来

const audioCtx = new AudioContext();

// 创建节点
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();

// 将波形发生器接到gainNode上,然后将gainNode连接到destination
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);

然后设定波形发生器,让他播放声音,这里就播放一个正弦波

 oscillator.type = 'sine'; // 波形是正弦波
 
 oscillator.start(audioCtx.currentTime); // 开始生成波形
 gainNode.gain.setValueAtTime(0, audioCtx.currentTime); // 把声音关掉,先不要出声

之后我们只需要在每次按下琴键时候,给oscillator指定对应的频率,然后把gainNode打开播放出声音来,就像按下了一个对应音的琴键一样

// C大调每个音对应的频率
const toneFrqList =  [ /* do */ 261.63, /* re */ 293.66, 329.63, 349.23, 392.0, 440.0,
  493.88,
];

export const play = async (n = 0) => {
  gainNode.gain.cancelScheduledValues(audioCtx.currentTime); // 清除掉gainNode之前的操作,防止声音卡顿
  
  // 指定频率
  oscillator.frequency.setValueAtTime(toneFrqList[n], audioCtx.currentTime); 
  
  // 发出声音
  gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01); 
  // 在1秒内渐弱,模拟琴键声音渐小的样子
  gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
};

Demo