読者です 読者をやめる 読者になる 読者になる

ハトネコエ Web がくしゅうちょう

プログラミングとかAndroid

SMF ( Standard MIDI File ) Format1 のバイナリを読んでみた

曲を作るためにお世話になっている MIDI
そういえばこれってどういうふうにできているんだろう、ってふと気になりました。

それでこの記事
SMF(Standard MIDI File)フォーマット解説 | 技術的読み物 | FISH&BREAD
を読んでいましたら、「意外と簡単?!」という気がしてきましたので、解読してみることにしました。

DominoMIDI編集ソフト)で、バイオリンの音でドを全音符ぶん鳴らすMIDIファイルを作成し、レッツトライです。

f:id:nekonenene:20170225194904p:plain

上のように作ったMIDIファイルを FavBinEdit で開いてみました。

4D 54 68 64 00 00 00 06 00 01 00 02 03 C0 4D 54 
72 6B 00 00 00 17 00 FF 03 00 00 FF 51 03 06 8A 
1B 00 FF 58 04 04 02 18 08 00 FF 2F 00 4D 54 72 
6B 00 00 00 2B 00 FF 03 00 00 FF 21 01 00 00 B0 
79 00 00 B0 00 00 00 B0 20 00 00 C0 28 00 B0 07 
64 9E 00 90 3C 64 9E 00 80 3C 00 9E 00 FF 2F 00

ヘッダー

まずはヘッダーを見てみましょう。

4D 54 68 64 00 00 00 06 00 01 00 02 03 C0

の部分です。

開始宣言

4D 54 68 64
これを、ASCIIコード表に沿って直すと MThd
たぶん MIDI Type header を略したのかなあ、と思いますが
自信ないですので、情報お持ちの方いらっしゃいましたらください。

ともかく、 4D 54 68 64 の並びが来たら、「ここからヘッダーだよー」っていうサインになります。

ヘッダーの長さ

00 00 00 06
「ヘッダーの長さは 6 Byte だよー」と言っています。

つまり 1行目
4D 54 68 64 00 00 00 06 00 01 00 02 03 C0 4D 54

00 01 00 02 03 C0
までがヘッダーの部分ですね。

(補足)
1Byte = 8bit なので、6Byte = 48bit です。(2進数で表現したときに数字が48個並びます)
16進数1つあたりのデータ量は 4bit(2 ^ 4 = 16 なので)だから、
16進数の場合では12個並ぶところまでが 6Byte です。

MIDIフォーマット

00 01
MIDI format = 1 であることを示しています。
SMF ( Standard MIDI File ) のファイルフォーマットは Format0, Format1, Format2 しかないので、
必然的にここの値は 00 00 ~ 00 02 までしか存在しません。

Wikipedia先生いわく

ヘッダチャンクとトラックチャンク1つのみで構成されるフォーマット0、複数トラックを持つフォーマット1、 マルチシーケンスでシーケンスパターンを指定するフォーマット2の3種類である。このうちフォーマット2は、現在ほとんど使われていない。

とのこと。
( https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%80%E3%83%BC%E3%83%89MIDI%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB )

私の感覚だと Format1 がもっとも一般的かなあと思います。
たいていのDTMソフトでのMIDI出力は Format1 で出力されていたかと思います。

トラック数

00 02
トラック数が 2個であることを示しています。

あとで説明する、 MTrk で始まるところが 2か所あると言い換えることができます。

解像度

03 C0
これを10進数に直すと 960 です。 解像度は960 です。

解像度というのは、1拍を何分割して表現できるか、ということです。
仮に解像度が2であれば、1拍(四分音符)を2分割するのが最大。
八分音符より短い音符を置くことはできませんし、音の長さはすべて八分音符単位。
「ほんのちょっとだけ鳴らす長さを短くしたい」などできません。

解像度が高いほど、「ほんのちょっとだけ鳴らす長さを短くしたい」とか「音の始まりを少しだけずらして和音を生演奏っぽく」などできるわけです。
480 とか 960 の設定値を見ることが多いですかね。

このファイルのヘッダー部分は以上です。

トラック(コンダクタートラック)

トラックはヘッダーに書いてある通り 2個ありますが、
まず 1個目の部分を見ていきましょう。

4D 54 
72 6B 00 00 00 17 00 FF 03 00 00 FF 51 03 06 8A 
1B 00 FF 58 04 04 02 18 08 00 FF 2F 00 

この部分です。

この1個目のトラックは「コンダクタートラック」と言う場合もあるもので、テンポや拍子の設定などをおこなっています。

開始宣言

4D 54 72 6B
これを、ASCIIコード表に沿って直すと MTrk
MIDI Track の略なんでしょうかね?

ともかく、4D 54 68 64 で「ここからトラックについての記述が始まるよー」というサインになります。

トラックの長さ

00 00 00 17
10進数に直すと 23 です。
「ここから 23 Byte 続くよー」と言ってくれています。

トラックのタイトル

00 FF 03 00

各トラックにはトラック名を付けることができます。
「Drums」などとタイトルを付けておくと、MIDIファイルを開いた人がなんのトラックなのかわかりやすいからです。
ただ、演奏には関係のない情報なので無くても構いません。

最初の「00」は「0Tick進んだところで信号を送るよ」って合図で(Tickについてはあとで説明します)、
「FF 03」は「トラック名を設定するよ」って合図で、その次にトラック名の長さを書きます。
この場合では「00」だから 0 Byte、ですのでそのあとに文字が続いていません。

この場合、トラック名を設定していないのだからバイナリを編集してこの部分を丸ごと削っても問題ありません。
ただし、そうするとトラックの長さも変わりますからそこの編集も忘れないようにしましょう。
(それを忘れると、再生できないMIDIファイルになってしまいます)

つまり、コンダクタートラックの
4D 54 72 6B 00 00 00 17 00 FF 03 00 00 FF 51 03 06 8A
の部分は、
4D 54 72 6B 00 00 00 13 00 FF 51 03 06 8A
と直してしまってかまいません。
こうすることで、無駄な 4Byte を削ることができます。(現代ではまったく意味をなさないレベルのファイル容量削減ですね!)

Tick

Tick は時間の単位です。
解像度が 960 の場合は、960Tick が1拍の長さです。

960Tick(1拍)を表すには、960は16進数で「3C0」だから「03 C0」とでもなるのかな、と思っていると、
そうではありません。「87 40」と表現します。

ポイントは、「960 ÷ 128 = 7 あまり 64」であることです。

Tickの値がどれだけ大きくてもいいように、Tickは何Byteでも表現できるようになっています。
しかし何Byteでも表現できるということは、どこかが終わりだとわからなければいけません。
ですので、「80」より小さい値を終わりと決めています。(つまり「7F」以下)
逆に「80」以上は「Tickを表現する値はこの次も続くんじゃ」と表していることになります。

この意味は2進数で考えるとわかりやすくて、16進数の「7F」は2進数では
01111111
16進数の「80」は2進数では
10000000
なので、勘のいい人は「先頭1ビットがフラグね」とわかるかと思います。

「7F」(10進数で言うところの127)より大きな値である128は以下のように書きます。
81 00
2進数に直すと
10000001 00000000
128 * 1 + 0 = 128
128進数で表現されていると言えるでしょう。

16800Tickならば、
81 83 20 と表します。
(128 ^ 2) * 1 + 128 * 3 + 32 = 16800

テンポの指定

00 FF 51 03 06 8A 1B

最初の「00」は「0Tick進んだところで信号を送るよ」って合図で、
つまり先ほどのトラック名の設定と同じタイミングで信号を送るよ、ってことになりますね。

「FF 51」は「テンポを設定するよ」って合図で、その次の「03」で「そのために 3Byte 使うよ」と伝えています。
そして値は「06 8A 1B」、10進数に直すと 428571 です。

428571、この大きな値は……? と思ったら、どうやらこれ、1拍あたりの秒数を指しているそうです。(単位はマイクロ秒)

BPM=120 のときの1拍の長さは 0.5 秒(500000マイクロ秒)であるから、
BPM=140 のときの1拍の長さは
0.5 * 120 ÷ 140 = 0.428571428… 秒
なるほど、たしかに 428571 マイクロ秒で間違いありませんね。

拍子の指定

00 FF 58 04 04 02 18 08
4分の4拍子であることの指定になります。

最初の「00」は「0Tick進んだところで信号を送るよ」って合図、
次の「FF 58」が「拍子を設定するよ」の合図、
次の「04」より、拍子の設定に 4Byte 使うことがわかります。

その4Byteが「04 02 18 08」の部分です。
「04 02 18 08」について順番に説明していきます。

拍子の指定1つ目

まずは最初の「04」。これは拍子の分子にあたります。
4分の3拍子なら「03」、4分の5拍子なら「05」が入ります。

拍子の指定2つ目

次の「02」。これは分母です。しかし、"4分の" なのに 2 っておかしいですよね?
これは 2 ^ (-2) (2のマイナス2乗)が4分の1であることがポイントです。
このマイナス2の2が、ここでの値なのです。

ですから、8分の6拍子でしたら、
ひとつめの部分は6拍子なので「06」、
ふたつめの部分は “8分の” なので、2のマイナス3乗が8分の1ですから「03」となります。

拍子の指定3つ目

そしてその次、「18 08」の部分。ここはなんのためにあるのか私よくわかっていません。

まず「18」の部分、10進数で言うと24であるここは、
1拍あたりのMIDIクロック数を示しています。*1

MIDIクロック」という言葉がわからなかったので調べたところ、
ここ( http://www.dipss.com/dipss/dtmkouza/whatsdtm/3-3.html )によると、

2台のシーケンサーMIDI接続されていて同時に演奏する場合、どちらもインターナルクロックで動いてしまうとバラバラの演奏になってしまいます。そこで、1台をインターナルクロックに、もう1台をエクスターナルクロックに設定してやります。すると、エクスターナルクロックに設定した方の機器はもう1方の機器が出すテンポに合わせて演奏するようになります。これが同期演奏です。

とあります。
つまるところ、複数のMIDIバイス間でのテンポ感を共有するものっぽいんですけど、よくわかってないです。
少なくとも、単純にMIDI再生するだけにおいてはどんな値でも良いっぽくて、ここを倍の「40」とかに変えても、なんの変わりもなく再生されます。

拍子の指定4つ目

最後の「08」、ここは
「4分音符1つと同じ長さになる32分音符の数」を指しているそうです。

「8以外になにか設定する値ある?!!」と思います。かなり不思議です。

トラックの終わり(End of Track)

00 FF 2F 00

最初の「00」は先ほどまでと同じく、0Tick経過後に、あとに続く信号を流すことを意味しています。
次に続く「FF 2F 00」が、「このトラックはここで終了します(End of Track)」という信号です。

この部分がなくても再生できるMIDIプレイヤーもありますが、
Windows Media Player などで再生できなくなりますので、しっかりと付けてあげましょう。

以上がコンダクタートラック部分の説明です。


長くなりましたので、残りの演奏トラック部分の説明は、また時間のできたときにおこないます。

【※追記 2017/03/06】
続き書きました!

atom-text-editor::shadow は廃止されました(ATOM v1.13.0)

f:id:nekonenene:20170120192346p:plain

テキストエディタ Atom を使っていて「おや?」と思うことが出た。
初めて見たぞこの表示……。

Atom Deprecation Cop というもので、つまり Atom の設定で変な箇所あったら怒ってくれる人です。
昔から搭載されてるらしいですが私は初めて怒られました。

f:id:nekonenene:20170120192816p:plain

こんなエラーでした。
「なるほどわからん」と思いつつもよく見たら、 style.less で deprecated selector が使われてるよー、と言われていることがわかります。

それで
atom-text-editor .highlight.ideographic-space .region:after, atom-text-editor::shadow .highlight.ideographic-space .region:after
から
atom-text-editor .highlight.ideographic-space .region:after, atom-text-editor.editor .highlight.ideographic-space .region:after
にそのうち自動で直しちゃうよー。みたいな。

問題のある箇所だけ教えてくれればいいのにこれじゃ間違い探しです。
正解は atom-text-editor::shadow => atom-text-editor.editor のとこだけです。わかりづらい!


せっかくなので、これが出た原因である全角スペース表示プラグインの作者さんに報告しようとしたら、
すでに報告済みでしたし、READMEもよく見たら修正されていました。
https://github.com/raccy/show-ideographic-space/issues/9

めでたし

起動すぐに『Windows Media Playerは動作を停止しました』となる問題の解決

Windows Media Player が再生を初めて数秒で
Windows Media Playerは動作を停止しました』と出て終了する問題に、この数週間悩まされてきました。

別の再生プレイヤー使おうか……
いや、でもWindows Media Playerは動作が軽いし広告出ないからいいんだよなぁ……。

iTunes は再生すると必ずライブラリに登録されるから自作曲を再生させたくないし、
Groove Player(Windows10からのプレイヤー)はファイルをダブルクリックしてもたまに再生始めないことがあるし……。

「プログラムと機能」の「Windowsの機能の有効化または無効化」で一度無効にして再起動して有効にすれば直るよー、ってのを試しても解決しなかったので、
「もうダメじゃー」とあきらめていたのですが、今日調べなおしたら、Yahoo知恵袋で1つの答えを見つけて、それを試したら直りました。

Windows10 だからこの解決法は無関係かなーと思っていたので、予想外に直って感動しました。

それは、 https://support.microsoft.com/ja-jp/kb/2970908 にあるMicrosoft 簡易修正ツールソリューション」の中の、

の三項目を実行させるというものです。

なお、私はライブラリを開くとすぐに『Windows Media Playerは動作を停止しました』が出ていましたので、
2番目の『Windows Media Player のライブラリの問題を検索・修正する』の実行が効果を発揮したんだと思います。

このページ全然知りませんでしたが、他のことでもお世話になることがあるかもですね。
快適なWindows音楽ライフに戻れました! よかったよかったー

GitHub Pages に、ビルドしたフォルダだけを上げたいときの Circle.yml

GitHub に git push するだけでウェブサイトを作成・更新できる GitHub Pages 。便利です。
ちょっとめんどくさいのが、 gh-pages ブランチか master ブランチのトップディレクトリに index.html がないといけないところ。
( master ブランチの場合は docs と名付けたフォルダをトップディレクトリにすることも可能)

gulp とか webpack とかで最終的な出力を build フォルダに向けてしている……なんて場合は、この仕様がけっこう厄介です。
デプロイ先のフォルダ名を docs とするのは一つの解決方法ですが、最終出力先のフォルダ名としてどうなの? って気持ちになります。

そこで、GitHub ユーザーにとっての CIツールとしておなじみ、 CircleCI の出番です!
CircleCIgh-pages ブランチへの git push をお願いしちゃいましょう!

0. 前置きの雑談

さっきから出力とかビルドとかデプロイとか言葉が安定しませんが、全部同じ意味で使ってると思ってください。
Sass が CSS ファイルになったり webpack で JS が出力されるのとかをコンパイルって呼んで、minify 系は圧縮と呼んで、それら一連のことがおこなわれるのをビルドとかデプロイとか呼んでる感じです。

正確にはそういう一連のコンパイルの流れを「ビルド」と呼んで、
人がさわれる状態になる(今回CircleCIが担当する、GitHub Pages への公開・更新)のを「デプロイ」と呼び、
サービス開始を「リリース」と言うらしいです。

私はビルドのことデプロイって言ったり、デプロイのことリリースって言ったりするので、
「あ〜この人あたまがわるいんだ〜」と笑いながら読んでください。

ディレクトフォルダも同じ意味くらいの気持ちで使ってますけど気にしないでね!

1. circle.yml の設定

答えから見せたほうがわかりやすいと思うので、まず出来上がりを見せます。
細かい注意点はそのあとに書いていきます。

src ディレクトリの中にある .scss ファイルや .pug ファイルを、タスクランナー gulp によってビルドし、
build ディレクトリに出力している形です。

1-1. CircleCI を働かさない branch の指定

general:
  branches:
    ignore:
      - gh-pages

まずはこの部分で、 gh-pages ブランチへの push があっても CircleCI が走らないよう設定されています。
今回は gh-pages ブランチに直接 push する予定はないので、この設定(もしくは以下に述べる [ci skip] の設定のいずれか)は本当はいりません。

f:id:nekonenene:20161102022958p:plain

上の写真のように、CI の実行がスキップされたログは残ります。
すこし邪魔ですが、ログがないと CircleCI の挙動がおかしいのか SKIPPED されたかわかりませんから必要ですね。

1-2. npm パッケージのインストール

dependencies:
  pre:
    - rm -rf node_modules
  override:
    - npm install

まず node_modules フォルダを削除しています。
CircleCI はキャッシュを持つのですが、 node_modules ディレクトリ内のキャッシュが悪さして変なところで CI が止まるということはよくあって、
しばらく原因解明に悩む時間がたびたび発生するので潔く削除しています。
(CircleCI には「Rebuild without cache」というボタンもあるのですが、そもそもキャッシュが原因の可能性を見落としがち)

1-3. ビルド

deployment:
  deploy:
    branch: master
    commands:
      - npm run build

branch: master と指定することで、 master ブランチへの push による CircleCI タスクのときのみ、この deploy 部分は働きます。
他のブランチへの push でも gh-pages に反映させたいという場合は、この記述は無くすといいでしょう。

その後に、 npm run build でビルドします。
以下のように package.json に書かれているために、npm run build とコマンドすると gulp build がおこなわれます。

# package.json より一部抜粋
  "scripts": {
    "build": "gulp build"
  }

gulp build の中身は gulpfile.js に記述しています。
大ざっぱに言うと、src ディレクトリにあるファイルをビルドして build ディレクトリに出力する処理が書かれています。

1-4. build ディレクトリの中身をトップディレクトリに

    commands:
      - npm run build
      - rm -rf src
      - cp -Rf build/. ./

ビルドし終わったら src フォルダは用済みなので消しています。
特に深い意味はありません。なんとなく消しています。

大事なのは cp -Rf build/. ./ の方です。
build フォルダの中身をトップディレクトリにコピーしてきます。

R オプションでフォルダの下を潜ってファイルを探します。
f オプションで、コピー先に同名のファイルがたとえあっても容赦なく上書きします。

個人的にハマったのは、build でも build/ でもなく、build/. が正解なことです。そっかー。

.gitignore によって build ディレクトリを含めてないから考えていませんでしたが、
ここ cp コマンドでなく mv コマンドでもよさそうですね。

1-5. git push で gh-pages へデプロイ

  - git config --global user.name  "CircleCI"
  - git config --global user.email "circleci@gh-pages.com"
  - git add .
  - git commit -m "Publish [ci skip]"
  - git checkout -B gh-pages
  - git push -u origin gh-pages --force

さて、ビルドが終わりました。
あとはデプロイ! git push までの流れを見ましょう。

1-5-1. git config

まずは git config で username と email の設定を入れます。
値はなんでもいいですが、この設定をしておかないとコミット git commit ができません。

1-5-2. git commit

コミットメッセージが「Publish [ci skip]」となっていますね。
この [ci skip] の部分が特別で、コミットメッセージにこの文字が含まれているコミットに対して、CircleCI は処理をおこないません。

通常は 1-1. で書いた特定のブランチを無視する方法だけでいいと思いますが、
おもしろい仕組みだなと思いましたので書いておきました。

1-5-3. add branch & checkout

続いて git checkout -B gh-pages です。

新しいブランチを作りつつチェックアウトする b オプションは知っていたのですが、
すでに同名のブランチがある場合はエラーを発生させてしまうオプションなので「どうしよう」と悩んでいたのですが、
この B オプションならば、基本は b オプションと同様ながら「すでに同名のブランチがある場合はブランチの作成をせずチェックアウトする」ようです。助かりました。

1-5-4. git push

そして最後に gh-pages ブランチをリモートリポジトリに git push します。
pull を要求されてはエラーになりますので、force オプションを付けます

2. git push だ! の前に……

CircleCI が git push できるようにするためには下準備が必要です。

CircleCI のプロジェクトページ(対象リポジトリ)に飛び、PROJECT SETTINGS を開き、
PERMISSIONS の「Checkout SSH keys」をクリックします。

f:id:nekonenene:20161102031951p:plain

すでに deploy key はあるかと思いますが、これには git pull の方の権限しかありませんので、user key を作成します。

Add user key のところにある「Authorize with GitHub」ボタンをクリックして user key を発行してください。
それが終わりますと下準備は完了です。

3. Failed にならないために

test:
  override:
    - npm run test

CircleCI には test の部分があります。
ここを省略することは可能ですが、書かないと、必ず結果が Failed になってしまいます。

精神的に Failed ラベルが並ぶのは嫌ですし、
すべてが Failed となってしまうのでは、ビルドに失敗しての Failed などと見分けがつきません。

とはいえ、作りたてのプロジェクトなど、
テストコードがない場合もあるかと思います。

その場合は、test の部分は cd ./echo 'にんじんたべたい' などと、
当たり障りのないコマンドを書いておくといいかと思います。

4. 余談 : yarn

公開した circle.yml では npm install としていますが、実際は

dependencies:
  pre:
    - rm -rf node_modules
    - npm install -g yarn
  override:
    - yarn install

test:
  override:
    - yarn test

deployment:
  deploy:
    branch: master
    commands:
      - yarn build

のように、npm から yarn に乗り換えつつあります。
rm -rf node_modules のあとに yarn cache clear を書いたほうが安全かも)

今のところは特に問題が起きていません。
……が、yarn をインストールさせることなく npm だけで終わらせたほうが CircleCI のタスクにかける時間が 1分近く少なくて、複雑な気持ちになっています。
yarn.lock による安定さが優れてるのではって勝手に期待してるんだけど、どうでしょう?