きっかけは、noteの方に書いてるのでこちらは技術的な部分のお話です。
制作物は以下で
ロジックとしては、
- クマゼミの音を1/2の音程で再生する
- 「ウェットン」という音声ファイルを1/2の音程で再生する
- ド#からオクターブ上のドまでのsine波の音にトレモロをかける
これらだったので、以前にAudioContext、Tone.jsなどを使ってJavaScriptで音を鳴らしたりマイクにエフェクトをかけてWebRTCしてみたりしていた経験もあったので『簡単に実装できるだろうなぁ』と思っていたのですが、意外とつまづきました。
つまづいたところ 1
音声ファイルをローカルで再生しようとするとCORSで怒られる。
いや、当たり前っちゃー当たり前なんですし、「live-serverでもなんでも使えや!」って話なんですが、これぐらいlive-serverとか使わずにやりたいんです。
ローカルのファイルをブラウザにポイってしたら動くのがHTML+CSS+JSのいいところなはずなのに、パラメーターをつけてブラウザを新規で立ち上げたくないのです。
ということで、音声ファイルをbase64化 + Base64Binary.jsを使ってサーバー経由じゃなくても音声が再生されるようにします。
つまづいたところ 2
Tone.jsのトレモロが意図した動作にならない
最初、C#の音からオクターブ上のCまでの音を段階的にアップさせる方式で実装していました。
const option = {
oscillator:{type:'sine'}
}
const synth = new Tone.Synth(option).toDestination();
synth.volume.value = -20
const voice = [
'C#5',null,'D5',null,'D#5',null,'E5',null,
'F5',null,'F5',null,
'F#5',null,'F#5',null,
'G5',null,'G5',null,
'G#5',null,'G#5',null,
'A5',null,'A5',null,
'A#5',null,'A#5',null,
'B5',null,'B5',null,
'C6',null,'C6','C6',
]
const seq = new Tone.Sequence((time, note) => {
synth.triggerAttackRelease(note, '8n', time)
}, voice, '56n').start(0)
Tone.Transport.start()
で、これだと結局12音階になってるので『ミとファの間の音』が出ないんで段階的に音階を変える(bendingとかギターで言うチョーキング)方法でアプローチしようと思いました。
Tone.jsではこういう感じです。
const osc = new Tone.Oscillator().start()
const signal = new Tone.Signal({
value: "C#5",
units: "frequency"
}).connect(osc.frequency);
signal.rampTo("C6", 2, "+0.5");
osc.stop(2.2)
↑これでC#5からC6まで音階を区切らず滑らかに音が上がってくれます。
(AudioContextのlinearRampToValueAtTimeに該当する部分です。)
あとはこれにトレモロをconnectしたんですが…
私の思っているトレモロとTone.jsのトレモロがステレオで振られる形になっていてなんか違ったんですね。
じゃぁ、代理のライブラリ探すかぁと思って出てきたのがPIZZICATO.JSです。
つまづいたところ 3
こちらのサンプルを再生すれば分かると思いますが、非常にいい感じで音が途切れ途切れになります。
おお、これはいいぞ!と思って色々やっていると。
PIZZCATO.jsはTone.jsのrampToに該当するものがないんです、というかPitchShiftもなさげ…
じゃぁ、別のライブラリ探そうと思って次に挑戦したのがXSoundでした。
つまづいたところ 4
このXSoundめっちゃすごいです。webRTCやらmidiやらWebSocketやらもうなんでもできそうです!
『こりゃぁ、いいぞ!』と思ったところやっぱりTone.jsのrampToに該当するものがないんです。
うーん、PitchShifterがあるから無理矢理実装するかなぁと思ったんですが、そもそもこれらのライブラリってAudioContextをラッピングしてくれるのでAudioContextのlinearRampToValueAtTimeをそのまま自分で使えばいいじゃんって思ったんですね。
なので、最終的にライブラリを使わずAudioContextを生で使って実装することにしました。
つまづいたところ5
iPhoneだと一部の音が鳴らない…
ピコピコ音を出すのに注視し何も考えずにsetIntervalやsetTimeoutで鳴らしていたんですがPCでうまくいくものの、iPhoneだとセミの音しか鳴らず…
どうしようか迷った挙句iPhoneだった場合はuser-agentで無理矢理判定して1回しか鳴らないように変更しました。
(PCの場合はloopする)
がんばったところ
音声が再生されるとオレンジ色のところがアニメーションするようにしたところぐらいでしょうか…
まとめ
これぐらいの実装ならライブラリに頼らず自分で実装しろや!ってところですね。
あと、今回は使用しませんでしたがXSoundマジでイチオシです。