サマリ
ブラウザで特定のURLへアクセスするだけで、toio™Core Cubeのサウンド機能を色々とテスト出来るツールを作成したことをご報告します。
- [Game Music Sample](https://youtu.be/LnFHprQWCl4) ↓ - [](http://www.youtube.com/watch?v=LnFHprQWCl4)ブラウザ(Node非依存)で #toio コアキューブのサウンド機能を使いこなすツールを作りました(v0.8.0)。MIDIファイルからキューブのサウンドへの変換、3つのキューブでの3重奏など色々サポート。
— Tetsunori NAKAYAMA | 中山 哲法 (@tetunori_lego) March 4, 2020
詳しくは下記Qiita記事にて。https://t.co/T914fPMl0v
ツール↓https://t.co/QjoiF5z8oV pic.twitter.com/Tby8pZRXbW
成果物
こちらにアクセスするだけで使えます。ソースコードもOSSでどうぞ。
動画で使用したMIDIファイルについては、このページの一番下にURLを置いておきます。
開発モチベーション
以前、Node.js上で動作するMIDIからCubeサウンドの変換ツールを作ったことがあったのですが(MIDI-to-toio.js-sound)、Node.jsのインストールが前提だと開発者しか楽しめないよなーと思っていました。が、昨今のWebBluetoothサポートOS拡大により、この機能をブラウザアクセスのみで実現できる目途が経ったので、移植を試みました。
せっかくなので、他にもCubeのサウンド機能をテストできたり、皆様の開発効率が上がるような機能も入れてます。
使い方
準備
- Cubeの電源をONしましょう。このツールでは3つのキューブまで同時接続をサポートしています。憧れの3重奏ができますよ。
- このツールを開いてください. Google Chromeを強く推奨します。
-
CONNECT CUBE N
ボタンを押していただき、対象のCubeと順番に接続していってください。 - 各種Cubeの接続が完了すると、キューブのLEDが青、緑、赤それぞれ光りますし、ツール上のいくつかのボタンが有効になるのがわかると思います。これで準備OKです!
単音のテスト
- まずはシンプルに単音を鳴らしてみましょう。以下の画像にあるスライドバーで音程の指定が出来ます。一応変更した際に自動的に鳴るようにしていますが、
PLAY NOTE
ボタンを押して鳴らすことも可能です。 - また、カーソルキーの右左でも音の高低を変えることが出来るようにしてあります。あんまり早く動かすと音が飛んじゃいますよ。
プリインの効果音(SE)を鳴らす
- toio™Core Cubeには、あらかじめいくつかの効果音がプリインストールされています。これもテスト的に鳴らすことが出来ます。
- 以下の画像にあるドロップダウンメニューから適当なSEを選んで見て下さい。こちらも選択時に自動的になるようにしていますが、
PLAY SE
ボタンで鳴らすことももちろん可能です。
MIDIファイルから変換して鳴らす
- このツールメイン機能です。以下の画像にある四角い領域の中にMIDIファイルをドラック&ドロップするか、領域をクリックしてファイルを選択してください。選択すると自動的に解析が走り、
Cube N:
と書かれた隣にあるドロップダウンメニューにトラックが設定されるのが確認できると思います。と同時に、PLAY
ボタンが有効になることも確認して下さい。 - ドロップダウンメニューでトラックを選択後、
PLAY
ボタンを押すとキューブから音が聞こえてくると思います。 - 注意: まだこのツールはVersion 0.8.0なのですが、キューブ間の演奏の同期が上手く行っていません。次のバージョンでは何とか回避したいと思っていますので、今はズレて演奏されてもガマンして下さいませ。
Appendix A. コードのコピー
Appendix B. サンプルサウンド
SWのポイント
ファイルのドラッグ&ドロップの実現
やり方は色々あると思うのですが、今回はdropifyという、file inputの拡張となるプラグインを使いました。
UIパーツがキレイですし、使い勝手も良かったので、採用させていただきました。
以下の通りでいつものfile input同様に扱えるので、導入も容易ですよ~。個人的にはjquery使うところだけは残念ですが・・・
<!-- dropify(overrides file input) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel='stylesheet' href='https://cdn.rawgit.com/JeremyFagis/dropify/master/dist/css/dropify.min.css'/>
<script src='https://cdn.rawgit.com/JeremyFagis/dropify/master/dist/js/dropify.min.js'></script>
// -- Execute dropify
$('.dropify').dropify();
MIDIファイルの解析
前回はかなり実装に手こずったので、今回の移植にあたりMIDIファイル解析のためのライブラリは今一度見直しをかけました。
で、選択したのは、大御所Tone.js/Midiです。
Tone.jsにMIDIについての機能があることは全く知りませんでしたが、バッチリ使いやすい形で解析結果を出力してくれるライブラリであることが判明しました。
以下の通りでこちらも導入は容易です。パースが1行で終了しますよ。
<!-- For parsing MIDI file -->
<script src="https://unpkg.com/@tonejs/midi"></script>
// -- Parser function using tone.js/Midi
const parseMIDIData = ( midi_data ) => { return new Midi( midi_data ); }
以下の画像の様なオブジェクトとしてパース結果が取得出来ます。
※ こちらのデモサイトでも働きを実感できると思いますので、試してみて下さい。
コードのコピーについて
Copyボタン
コピーの処理については、コチラの記事を参考にさせて頂きました。
<button class="mui-btn mui-btn--small" id="copyCode">Copy code</button>
document.getElementById( 'copyCode' ).addEventListener( 'click', async ev => {
copyCode( document.getElementById( 'outputCode' ).innerText );
});
// from https://qiita.com/simiraaaa/items/2e7478d72f365aa48356
function copyCode( string ){
const temp = document.createElement( 'div' );
temp.appendChild( document.createElement('pre') ).textContent = string;
const s = temp.style;
s.position = 'fixed';
s.left = '-100%';
document.body.appendChild( temp );
document.getSelection().selectAllChildren( temp );
const result = document.execCommand('copy');
document.body.removeChild( temp );
return result;
}
Codeのprettify
Google Code Prettifyを使用しています。
CSSの分量がなんだかんだで増えてしまうのがなんとも歯がゆいです。
出来上がりはキレイで満足なんですけど。
<!-- Code prettify -->
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<!-- Copy code region -->
<pre id="outputCode" class="prettyprint lang-js"></pre>
CSS:index.css
/* Pretty printing styles. Used with prettify.js. */
pre.prettyprint {
padding: 0px;
border: 1px solid #E5E5E5;
font-size:12px;
font-family: Menlo, 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Consolas, monospace;
overflow: auto;
white-space: pre-wrap;
tab-size: 4;
height:16em;
width:600px;
background-color:#eee;
margin-bottom: 0em;
}
pre.prettyprint .str { color: #800; }
pre.prettyprint .kwd { color: #00c; }
pre.prettyprint .com { color: #080; }
pre.prettyprint .typ { color: #606; }
pre.prettyprint .lit { color: #066; }
pre.prettyprint .pun { color: #660; }
pre.prettyprint .pln { color: #000; }
pre.prettyprint .tag { color: #008; }
pre.prettyprint .atn { color: #606; }
pre.prettyprint .atv { color: #080; }
pre.prettyprint .dec { color: #606; }
/* Specify class=linenums on a pre to get line numbering */
pre.prettyprint ol.linenums {
margin-top: 0;
margin-bottom: 0;
padding: 0px 0px 0px 4em;
background-color: rgb(235, 240, 255);
line-height: 1em;
} /* IE indents via margin-left */
pre.prettyprint li
{
padding: 2px;
padding-left: 5px;
}
pre.prettyprint li:nth-child(odd)
{
background: rgb(255,255,255);
}
pre.prettyprint li:nth-child(even)
{
background: rgb(250,250,250);
}
pre.prettyprint li.hilight {
background-color: hsla(220, 100%, 80%, 0.25);
}
@media print {
.str { color: #060; }
.kwd { color: #006; font-weight: bold; }
.com { color: #600; font-style: italic; }
.typ { color: #404; font-weight: bold; }
.lit { color: #044; }
.pun { color: #440; }
.pln { color: #000; }
.tag { color: #006; font-weight: bold; }
.atn { color: #404; }
.atv { color: #060; }
}
ol.linenums{
counter-reset:linenumber;
}
ol.linenums li{
list-style-type:none;
counter-increment:linenumber;
}
ol.linenums li:before{
content: counter(linenumber);
float:left;
margin-left:-4em;
text-align:right;
width:3em;
}
document.getElementById( 'outputCode' ).innerHTML = codeText;
document.getElementById( 'outputCode' ).setAttribute( 'class', 'prettyprint lang-js linenums' );
PR.prettyPrint();
連続したsoundコマンドの発行
今回のツールではどんなに長い曲でも、連続してキューブへコマンドを発行することで、演奏する事を可能にしています。
実際には、各コマンドで送付した際のduration
の合計値をsetTimeout
の引数に渡してコールバックしてもらうことで実現しているのですが、そもそもデータの量がコマンド発行毎に異なると、キューブへのデータの到達時間がまばらになり、なめらかに接続した曲に聞こえませんでした。
よって、送付するデータ数は固定になるように、下記の様に必ずデータにパディングを施しました。これによりスムーズな演奏となっています。
// -- functions : Padding for large size music to avoid variation of bluetooth transmission time
const paddingTrack = ( track ) => {
if( track.length > MAX_SOUND_BINARY_NUM ){
const numChunksInTrack = Math.floor( track.length / ( MAX_SOUND_BINARY_NUM ) );
const chunksDataSize = numChunksInTrack * MAX_SOUND_BINARY_NUM;
const fraction = track.length - chunksDataSize;
const paddingDataSize = MAX_SOUND_BINARY_NUM - fraction;
const paddingOperationSize = paddingDataSize / 3;
// console.log( 'paddingOperationSize ' + paddingOperationSize );
for( let i = 0; i < paddingOperationSize; i++ ){
const DURATION = 1; // minimum value( 10msec ).
const NOTE_OFF_NUMBER = 128;
const VELOCITY = 255;
inputNoteData( DURATION, NOTE_OFF_NUMBER, VELOCITY, track );
}
}
}
一方で、まだ課題はあり、上記setTimeout
関数に指定している値が悪いのか、曲が早まったり遅くなったりすることが多発しております。これによりキューブ間の同期がものすごくズレてしまう問題が発生します。
最初は無線環境等の影響かなーなどと他責に思っていましたが、もしかするとBLEのConnection Interval
とかその辺に踏み込んで計算を加味しないといけないのかなと思い始めたところです。
以上、Updateがあれば、追記します。
追記 2020 03/08
Macで動作を確認したところ同期問題はほとんど発生しませんでした。やはり実行環境依存の問題かと思います。
一方で、このページをchromeの最前面ではなく、裏やで別タブで実行させてしまうと同期がズレてしまうこともわかりました。ぜひプレイする際はfore groundで実行し続けてくださいませ。
参考
確認に使ったゲームミュージックのMIDIファイル
こちらのサイトから探してみました。
https://www.vgmusic.com/music/console/nintendo/nes/sm1castl.mid
https://www.vgmusic.com/music/console/nintendo/nes/Smbtheme.mid
https://www.vgmusic.com/music/console/nintendo/nes/smb1uw.mid
https://www.vgmusic.com/music/console/nintendo/nes/dw1_Title.mid
https://www.vgmusic.com/music/console/nintendo/nes/Dw1out.mid
https://www.vgmusic.com/music/console/nintendo/nes/dw1king.mid
https://www.vgmusic.com/music/console/sega/master/ps1.mid
https://www.vgmusic.com/music/console/sega/master/ps1tower.mid
https://www.vgmusic.com/music/console/sega/master/PStar1_Cave.mid
https://www.vgmusic.com/music/console/sega/master/PStar1_Battle.mid
https://www.vgmusic.com/music/console/sega/master/FZone2-Shop.mid
https://www.vgmusic.com/music/console/sega/master/FZone2-Title.mid