この記事はNuxt.js #2 Advent Calendar 2018 13日目の記事です。
近年のWebアプリの発展は凄まじく、多くのブラウザベースのアプリが誕生しました。
従来はデスクトップアプリが担当するような高度な処理も、どんどんブラウザ上で実装できる時代になってきました。
友人も、最近はブラウザベースのアプリしか使っていない、とぼやいています。
このビッグウェーブに乗るべく、もともとデスクトップアプリで作る予定だったものをWebアプリで作ることにしました。
その名もMusicCutterです。
つくったもの
ブラウザ上で音楽を切り貼りできるアプリ、MusicCutterを作りました。
紹介動画はこちら
特徴
- 音楽の切り貼りに特化したアプリ
- 曲のテンポを自動解析し、拍単位での編集を可能にする
- ブラウザ上で処理を完結させるため、サーバーに音声ファイルをアップロードする必要がない
- 上記の理由から、オンラインである必要がないため、オフラインで動作可能なPWAに対応する
初めて音楽を切り貼りする人が手を出すのは、Audacityといった波形編集ソフトだと思います。
波形編集ソフトでは、秒単位で編集を行うため、曲を切り貼りするのに耳でタイミングを聞き取るしかありませんでした。このソフトでは、曲のテンポを解析し、拍ベースで編集が行えるため、細かいタイミングを気にせず切り貼りが出来ます。
使った技術
Nuxt.js
みんなご存知Nuxtです。自分が所属している組織で使っていたのがきっかけで出会いました。
なぜVue CLIでなくNuxtを選んだか
Vueでアプリを作る時のデファクトスタンダードはVue CLIだと思います。
NuxtはVue CLIよりも規約で縛る思想にあるので、プロジェクトの秩序が保たれやすいと考えたのが大きな理由です。
Web Audio API
Web上でオーディオを高度に制御するためのAPIです。
音声にフィルターを掛けたり、視覚化したり、再生のタイミングを正確にスケジューリングしたりできます。
Firebase Hosting
Nxutで生成した静的ファイルをホスティングするのに使いました。
Nuxtでやったこと
プラグインの導入
Vue Materialの導入
UIを作るのにVue Materialを採用しました。
nuxt-vue-material
というNuxtプラグインとして導入できるパッケージがあったのでそちらを使いました。
$ yarn add nuxt-vue-material
modules: [
['nuxt-vue-material', {
theme: 'default-dark'
}],
]
コンフィグでテーマが変えられるのが手軽で良いです。
Vue Material
では予めLight
Dark
Light Green
Dark Green
の4種類のテーマがありますが、独自にカスタマイズしたい場合はテーマにnullを指定します。
その後、layoutのスタイルタグに以下を記述します。
<style lang="scss">
@import "~vue-material/dist/theme/engine"; // Import the theme engine
@include md-register-theme("default", (
primary: md-get-palette-color(amber, A700), // The primary color of your application
accent: md-get-palette-color(red, A200) // The accent or secondary color
));
@import "~vue-material/dist/theme/all"; // Apply the theme
</style>
md-get-palette-color
には色を指定します。
指定する名前や数値はこちらで確認できます。
###PWA対応
$ yarn add @nuxtjs/pwa
@nuxtjs/pwa
という公式パッケージを使いました。
nuxt.config.jsに以下を追加します。
modules: [
//...
'@nuxtjs/pwa'
],
manifest: {
name: "アプリ名",
lang: 'ja'
},
workbox: {
offlineAssets:["オフラインで使うファイル","",""]
},
manifest
を追加するだけでPWAとしてブラウザに認識させられるようになりますが、今回はオフライン動作に対応するため、workbox
にオフラインで使うファイルを指定します。
Google Analytics の実装
$ yarn add @nuxtjs/google-analytics
@nuxtjs/google-analytics
パッケージを使用しました。
以下のようにトラッキングIDを指定するだけで、基本的な機能が使えるようになります。
modules: [
//...
['@nuxtjs/google-analytics', {
id: 'UA-12345-6'
}]
]
また、ページ内でユーザーがどのような行動をしたかも収集可能です。
Google Analyticsではイベントトラッキングを用いることで実現できます。
以下のコードでイベントを送信できます。
this.$ga.event('category', 'action', 'label', 123)
各パラメータの意味はこちらから確認できます。
MusicCutterを例に取ると、どれくらいのユーザーが編集をして保存までたどり着いたかを調べるために、Saveボタンを押すとイベントが送信される仕組みになっています。
Vue.Draggableの導入
タイムライン上での要素の並び替えのためにVue.Draggableを導入しました。
$ yarn add vuedraggable
MusicCutterでは以下のような使い方をしています。
<draggable v-model="SoundBlocks">
<div v-for="block in SoundBlocks" :key="block.id">
<SoundBlock :data="block"/>
</div>
</draggable>
Web Audio API
概要
上の画像はWeb Audio APIの概要を示しています。
複数のノードをチェインして最終的にDestination
に接続することで、音を出すことができます。
簡単な使い方
単純に音声を再生する例を以下に示します。
//1. AudioContextの準備
const context = new AudioContext();
//再生するバッファを準備
const prepareBuffer = async path => {
//2. fetch APIで音声ファイルを取得
const res = await fetch(path);
//ArrayBufferを取得
const arr = await res.arrayBuffer();
//3. 音声ファイルをデコード
const buf = await context.decodeAudioData(arr);
return buf;
}
const play = async () => {
const source = context.createBufferSource(); //4. Sourceノードを作成
source.buffer = await prepareBuffer("./audio.mp3"); //5. 再生するバッファを指定
source.connect(context.destination); // SourceノードをDestinationにつなぐ
source.start(0);//6. 再生開始
}
play();
- 音声処理グラフを表す
AudioContext
を作成します。 - fetchAPIで音声ファイルを取得し、バイナリデータ(
ArrayBuffer
)を取得します -
AudioContext.decodeAudioData(ArrayBuffer)
を使用してオーディオデータをデコードします。 -
AudioContext.createBufferSource
を使用してAudioSourceBufferNode
を作成します。 -
AudioSourceBufferNode
にオーディオデータをセットし、AudioContext
のDestinationに繋ぎます。 - 再生します。
フィルタを使う
音量を変更するフィルタを挟んでみましょう。play()
を以下のように変更します。
const play = async () => {
const source = context.createBufferSource(); // Sourceノードを作成
source.buffer = await prepareBuffer("./audio.mp3"); // 再生するバッファを指定
const gainNode = context.createGain();// ゲインノードを作成
gainNode.gain.value = 0.5;// 音量を半分に設定
source.connect(gainNode);// Sourceをゲインノードに接続
gainNode.connect(context.destination);// ゲインノードをDestinationに接続
source.start(0); // 再生開始
}
Web Audio APIについて詳しく知りたければ、こちらの記事がとても参考になります。
曲のテンポを解析する
曲のテンポを解析するアルゴリズムは、Beat Detection Using JavaScript and the Web Audio APIを参考にしました。
また、このアルゴリズムの改良版であるCalculating BPM using Javascript and the Spotify Web APIも参考にしました。
テンポ解析についてはこれだけで記事が1本かけそうなので後で書くかもしれません。
ここでは軽く流れを説明します。
1.フィルターを掛けて音楽を解析しやすくする
ドラムの音がテンポを解析する手がかりになるので、ドラムの音を目立たせて他の音はカットします。
具体的には、ハイパスフィルターとローパスフィルターを掛けて音の中域部分をバッサリ切り落とします。
今回はフィルタを掛けながら再生するのではなく、フィルタを掛けたバッファを取得したいので以下のようにOfflineContext
を使います。
const filter = async (buffer) => {
// レンダリング用のオフラインコンテキストを生成
const offlineContext = new OfflineAudioContext(1, buffer.length, buffer.sampleRate);
// Sourceを作成
const source = offlineContext.createBufferSource();
source.buffer = buffer;
// フィルタを作成する
const lowpass = offlineContext.createBiquadFilter();
lowpass.type = "lowpass";
lowpass.frequency.value = 150;
lowpass.Q.value = 1;
const highpass = offlineContext.createBiquadFilter();
highpass.type = "highpass";
highpass.frequency.value = 100;
highpass.Q.value = 1;
// フィルタをチェインしてコンテキストにつなぐ
source.connect(lowpass);
lowpass.connect(highpass);
highpass.connect(offlineContext.destination);
// 開始
source.start(0);
// レンダリングをする
return offlineContext.startRendering()
}
2.音のピークを取得する
0.5秒(120bpm相当)間隔で音声を区切り、ブロック内で最も音量が大きいサンプルを集計していきます。
3.ピークの間隔からテンポを推測する
ピークの間隔の統計を取り、テンポを推測します。
MusicCutterでは、誤差を考慮していくつかテンポの候補を出すようにしています。
ブラウザ上でMP3エンコード
エンコード用のサーバーを準備すると、アップロード/ダウンロードが発生する上、維持費がかかってしまうので
クライアントで完結させるためにブラウザ上でMP3エンコードするという暴挙にでました。
Using WebAudioRecorder.js to Record MP3, Vorbis and WAV Audio on Your Websiteで紹介されていた
WebAudioRecorder.jsに使用されているMP3エンコーダMp3LameEncoder.jsを使用しました。
使い方は驚くほど簡単で、WebAudioAPI
で使用した音声バッファをエンコーダに投げるだけで、処理をしてくれる手軽さです。
ただ、Mp3LameEncoder.js
はモジュールに対応していないのでスクリプトタグで読み込むか、WebWorker
上ならimportScripts
を使って読み込む必要があります。
以下にWaveファイルをMP3にエンコードする流れを示します。
//バッファを準備
const prepareBuffer = async path => {
//fetch APIで音声ファイルを取得
const res = await fetch(path);
//ArrayBufferを取得
const arr = await res.arrayBuffer();
//音声ファイルをデコード
const buf = await context.decodeAudioData(arr);
return buf;
}
const encode = async () => {
//waveファイルを読み込む
const buf = await prepareBuffer("./audio.wav");
//左右のチャンネルの生データ(Float32Array)を取得
const dataL = buf.getChannelData(0);
const dataR = buf.getChannelData(1);
//エンコーダの初期化
const encoder = new Mp3LameEncoder(buf.sampleRate, 192);
//エンコードする。これは複数回呼び出すことが可能
encoder.encode([dataL,dataR]);
//blobが返される
const blob = encoder.finish();
//blobを保存したりサーバーにアップロードしたりする
}
encode();
MusicCutterではWebWorker
上でエンコードを行い、FileSaver.jsでクライアントにファイルを保存させています。
Firebase Hosting
静的ファイルをホストしてくれるサービスです。
Nuxtで静的ファイルを生成できるのでそれをデプロイします。
デプロイの準備
firebase-tools
をインストールします。
$ yarn global add firebase-tools
次にプロジェクトフォルダで
$ firebase init
を実行します。種類はHosting
を選択します
質問に答えていくと
What do you want to use as your public directory?
と聞かれるのでdist
と入力しておきます。
静的ファイルの生成
$ yarn generate
を実行すると、Nuxtが./dist
フォルダに静的ファイルを生成してくれます。
デプロイ
$ firebase deploy
と入力するとデプロイが行われます。
#おわりに
いかがだったでしょうか?
NuxtとWebAudioAPIを使うだけで、ブラウザ上で動作する音楽編集ソフトを作ることができました。
Nuxtを始め、ブラウザアプリにさらなる可能性を感じていただけたら幸いです。