はじめに
Webアプリで音声を扱う機会、増えてきましたよね。ポッドキャストの再生画面とか、音楽プレイヤーとか、音声メッセージの表示とか。そういうときに「波形表示があったらかっこいいな」と思ったことありませんか。
個人的には、SoundCloudみたいな波形UIを見るたびに「これどうやって実装してるんだろう」と思ってました。自前でCanvas描画するのは正直しんどい。そこで見つけたのがWavesurfer.jsです。
Wavesurfer.jsは、Web AudioとCanvasを活用したオーディオ波形表示ライブラリ。GitHubスター数は10,000を超えていて、14年以上の歴史がある安定したプロジェクトなんですよね。
説明を読むより、実際にWaveSurfer.jsを動かしてみた方が早いと思います。今回はWeb Audio APIで生成したサイン波やノイズをその場で波形描画するサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
特徴・メリット
軽量で高機能
バンドルサイズが11.9KB(gzip圧縮後)というのは、この手のライブラリとしてはかなり軽い部類です。それでいて機能は充実しているので、コスパ的にはかなり良いと思います。
プラグインが豊富
標準で7つのプラグインが用意されています。
- Regions - 区間選択・ループ再生
- Timeline - 時間軸の表示
- Minimap - 全体像のミニマップ
- Envelope - 音量エンベロープ
- Record - マイク録音
- Spectrogram - スペクトログラム表示
- Hover - ホバー時の情報表示
必要な機能だけ追加できるので、バンドルサイズを抑えつつ拡張できるのが良いですね。
TypeScript対応
型定義が最初から含まれているので、TypeScriptプロジェクトでもスムーズに使えます。30代になって思うのは、型があると安心感が違うということ。補完も効くし、バグも減るし。
カスタマイズ性が高い
波形の色、進捗バーの色、高さ、バーの幅など、見た目は細かく調整できます。Shadow DOMを採用しているので、CSSの競合も起きにくい設計になってます。
インストール方法
NPM経由(推奨)
npm install --save wavesurfer.js
CDN経由
サクッと試したいときはCDNが便利です。
<script src="https://unpkg.com/wavesurfer.js@7"></script>
WaveSurfer.jsのサンプルを動かす
WaveSurfer.jsのサンプルは通常、urlオプションにmp3ファイルなどを渡して波形を表示しますが、この記事のサンプルは外部の音声ファイルを一切fetchしません。代わりにWeb Audio APIのOfflineAudioContextでサイン波やノイズを合成し、そのAudioBufferをWAV形式のBlobに変換したうえでwavesurfer.loadBlob()に渡しています。ブラウザだけで完結するので、サンドボックス環境でも確実に動きます。
パターン1: 合成したサイン波を波形表示する
まずは最小構成として、周波数スライダーを動かすたびにOfflineAudioContextでサイン波を再生成し、WaveSurfer.create()で作った波形にloadBlob()で読み込ませるサンプルです。要点だけを抜き出すと、次のようになります。
import WaveSurfer from 'wavesurfer.js@7.12.11'
const wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: '#4F4A85',
progressColor: '#383351',
height: 100,
})
// AudioBufferをWAVのBlobに変換してから読み込む(外部ファイルへのfetchなし)
const blob = audioBufferToWavBlob(audioBuffer)
await wavesurfer.loadBlob(blob)
wavesurfer.on('ready', () => {
console.log('波形描画完了:', wavesurfer.getDuration(), '秒')
})
実際に動かせるものが下です。スライダーで周波数(110Hz〜1760Hz)を変えると、その場でサイン波が再合成されて波形が描き直されます。低い周波数にすると波形の山がゆったりと大きく、高い周波数にすると細かく密になるのが分かるはずです。
renderSineWave()のdurationを伸ばしたり、oscillator.typeを'sawtooth'や'square'に変えたりすると、波形の形が大きく変わります。波形描画自体はloadBlob()が受け取ったAudioBufferのデコード結果を使っているだけなので、音声ファイルの実体がどこから来ているかはWaveSurfer.js側には関係ありません。
パターン2: 再生・ズーム操作を試す
次はwavesurfer.playPause()とwavesurfer.zoom()を使った操作系のサンプルです。ノイズ波形をloadBlob()で読み込み、再生ボタンでplayPause()を呼び出し、スライダーでzoom(minPxPerSec)を呼んで1秒あたりのピクセル数を変えています。再生位置はtimeupdateイベントで取得しています。
// 再生 / 一時停止を切り替える
playButton.addEventListener('click', () => wavesurfer.playPause())
// ズーム(1秒あたりのピクセル数)を変更する
zoomSlider.addEventListener('input', (e) => {
wavesurfer.zoom(Number(e.target.value))
})
// 再生位置をリアルタイムに表示する
wavesurfer.on('timeupdate', (currentTime) => {
timeLabel.textContent = `${currentTime.toFixed(2)} 秒 / ${wavesurfer.getDuration().toFixed(2)} 秒`
})
実際に動かせるものが下です。再生ボタンでノイズ波形を再生し、ズームスライダーを右に動かすと波形が横に引き伸ばされて細部まで見えるようになります。逆に左に動かすと全体が縮小されます。
zoom()に渡す値を大きくするほど波形の1秒あたりの表示幅(px/秒)が広がり、再生カーソル(cursorColorで指定した白い縦線)の動きも視覚的に追いやすくなります。実際の音声ファイルを扱うときも、このplayPause() / zoom() / timeupdateの組み合わせがそのまま使えます。
基本的な使い方
最小構成
まずはシンプルに波形を表示してみます。
import WaveSurfer from 'wavesurfer.js'
const wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: '#4F4A85',
progressColor: '#383351',
url: '/audio.mp3',
})
// 波形クリックで再生/停止
wavesurfer.on('interaction', () => {
wavesurfer.playPause()
})
HTMLはこれだけ。
<div id="waveform"></div>
これだけで波形が表示されて、クリックで再生できるようになります。正直、初めて動かしたときは「え、これだけ?」ってなりました。
再生コントロールの追加
実際のアプリでは再生ボタンとか必要ですよね。
import WaveSurfer from 'wavesurfer.js'
const wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: '#4F4A85',
progressColor: '#383351',
url: '/audio.mp3',
height: 80,
barWidth: 2,
barGap: 1,
barRadius: 2,
})
// 再生/停止ボタン
document.querySelector('#playButton').addEventListener('click', () => {
wavesurfer.playPause()
})
// 再生位置の表示
wavesurfer.on('timeupdate', (currentTime) => {
document.querySelector('#currentTime').textContent =
formatTime(currentTime)
})
// 再生終了時
wavesurfer.on('finish', () => {
console.log('再生終了')
})
function formatTime(seconds) {
const min = Math.floor(seconds / 60)
const sec = Math.floor(seconds % 60)
return `${min}:${sec.toString().padStart(2, '0')}`
}
Regionsプラグインで区間ループ
ポッドキャストの編集とか、特定区間をリピートしたいときに便利です。
import WaveSurfer from 'wavesurfer.js'
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js'
const wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: '#4F4A85',
progressColor: '#383351',
url: '/audio.mp3',
})
const regions = wavesurfer.registerPlugin(RegionsPlugin.create())
// 波形読み込み完了後にリージョン追加
wavesurfer.on('ready', () => {
regions.addRegion({
start: 10,
end: 20,
color: 'rgba(79, 74, 133, 0.3)',
drag: true,
resize: true,
})
})
// リージョン内ループ再生
regions.on('region-clicked', (region, e) => {
e.stopPropagation()
region.play()
})
実践的なユースケース
ポッドキャストプレイヤー
Timelineプラグインと組み合わせると、本格的なポッドキャストプレイヤーが作れます。時間軸があると、どのあたりを再生しているかが一目でわかるので、ユーザー体験がかなり上がります。
音声メッセージUI
LINEとかSlackみたいな音声メッセージのUI。波形があると「ちゃんと録音できてる」感が出るので、ユーザーの安心感につながります。Recordプラグインを使えば、録音機能も実装できます。
音楽制作ツール
Spectrogramプラグインでスペクトログラム表示、Regionsプラグインで区間編集。ブラウザ上で動く簡易的な音声エディタも作れなくはないです。
語学学習アプリ
Regionsプラグインでセンテンスごとに区切って、リピート再生できるようにする。これ、意外と需要あると思うんですよね。
まとめ
Wavesurfer.jsは、Web上で音声波形を扱うなら一択と言っていいライブラリです。
- 軽量(11.9KB gzip)
- 豊富なプラグイン
- TypeScript対応
- 14年以上の実績
音声を扱うWebアプリを作るなら、まず検討してみてください。自前でCanvas描画するより、確実に時短になります。QOLも上がる。
個人的には、プラグインの設計が良くできているなと思いました。必要な機能だけ追加できるので、バンドルサイズを気にしながら機能追加できるのは、モダンなフロントエンド開発において重要なポイントですね。
公式サイト: https://wavesurfer.xyz/ GitHub: https://github.com/wavesurfer-js/wavesurfer.js
