KuinでSMF(MIDIファイル)を解析しよう１

# KuinでSMF(MIDIファイル)を解析しよう
MIDIを使った演奏のおもちゃを作ろうと思ったのですが、使いたいマイコンでSMFを扱うライブラリが無かったのでゼロからコーディングする事となりました。
いきなりマイコンでは敷居が高いので、Kuinという言語を使って解析方法を学習します。

## SMFについて
Standard MIDI Fileの略で、電子楽器等を演奏するためのデータであるMIDI通信をファイルとして保存したものです。
普段は単純にMIDI、あるいはMIDIファイルと呼ばれていることが多い不憫なファイル形式です。
拡張子も「.smf」がありますが「.mid」で使われるほうが圧倒的に多いのでますます目にする機会がないですね…

今回はSMFやMIDIの構造については詳しく解説しませんので、私が参考にしたサイト等で確認してください。
- [SMF(Standard MIDI File)フォーマット解説 | 技術的読み物 | FISH&amp;BREAD](http://maruyama.breadfish.jp/tech/smf/)
- [SMF (Standard MIDI Files) の構造](https://sites.google.com/site/yyagisite/material/smfspec)

## 使用する環境
Kuin Programming Language (Version 2018.9.17)
zipを解凍しただけで使えるという、環境構築の手間がないステキな言語ですよ。
[プログラミング言語「Kuin」](http://kuina.ch/kuin)

## 解析の前準備
Kuinのfileライブラリはバイナリデータをbit8型の配列として取得します。
このままでは扱いにくいので、数値に変換するメソッドを先に作っておきます。

### 1. 固定長数値表現
難しそうな名前を使いましたが、2byteか4byteで表される単純な数値です。
`file@Reader.read`で取得したデータをbit32型の変数にビットシフトしながら論理和します。

```Kuin:main.kn
func castInt(data: []bit8): int
	var value: bit32
	
	for i(0, ^data - 1)
		do value :: value.shl(8).or(data[i] $ bit32)
	end for
		
	ret value $ int
end func
```


### 2. 可変長数値表現
これは8bitのうち、7bitを数値データとして扱い、最上位ビットを次のバイトデータも数値表現の続きかどうかを判断するフラグにしています。
例えば10進数の`127`を2進数で表すと`0111 1111`となりますね。
この場合、最上位ビットは `0`となっているため、次に続くバイトデータは別の物として判断できます。

では、読み込んだデータが`1000 0001`となっていた場合はどうでしょう？　10進数の`129`でしょうか？
この場合、最上位ビットが`1`となっているため、次に続くバイトデータを読み込んで見る必要があります。
さらに1byte読み込むと`1000 0001, 0000 1010`となりました。
ここで、それぞれの7bitの部分を抜き出して結合すると`1000 1010`となり、10進数の`138`になります。

このように、最上位ビットを確認しながらデータを結合するメソッドを作成します。

```Kuin:main.kn
func parseVariableInt(): int
	var value: bit32
	
	while loop(true)
		var byte: bit8 :: me._reader.read(1)[0]
		do value :: value.shl(7).or((byte $ bit32).and(0x7Fb32))
		if(byte < 0x80b8)
			break loop
		end if
	end while
	
	ret value $ int
end func
```

`(byte $ bit32).and(0x7Fb32)`の部分で読み込んだデータの最上位ビットをマスクし、
さらに`value.shl(7).or()`の部分で7bitずつ左にシフトしながら論理和して数値を再現しています。

## SMFのヘッダチャンクを解析する
SMFはバイナリデータの先頭に次のような14byteのヘッダチャンクを持っています。
`4D 54 68 64 00 00 00 06 00 01 00 02 01 E0`

ヘッダーに記録されている情報は以下の通り。
　1. チャンクタイプ (4byte)
　2. データ長 (4byte)
　3. フォーマット形式 (2byte)
　4. トラック数 (2byte)
　5. 時間単位 (2byte)

### 1. チャンクタイプの解析
`4D 54 68 64` の部分です。文字にすると「MThd」となります。
これがなければ何も始まりません。きっとSMFの拡張子の皮をかぶった別物です。

```Kuin:main.kn
const MTHD: int :: 0x4D546864

var chunk: int :: me.castInt(me._reader.read(4))
if(chunk <> MTHD)
	throw ERR_INVALID_HEADER
end 
```
先程作った、bit8の配列をintに変換するメソッドを利用し、比較しています。

### 2. データ長の解析
`00 00 00 06` の部分です。
これは、ヘッダチャンクがこの後何バイト続いているかを示しているのですが、現状では固定値で`6`となっています。
フォーマット形式、トラック数、時間単位がそれぞれ2byteで、これ以外にデータはありません。
4byteも準備してあるのは将来の拡張性でしょうか？

```Kuin:main.kn
var headerSize: int :: me.castInt(me._reader.read(4))
if(headerSize <> 6)
	throw ERR_INVALID_HEADER
end if
```

### 3. フォーマット形式の解析
`00 01` の部分です。
読み込んでいるSMFがどのフォーマットで作成されているかを示しています。
SMFのフォーマットは現在「0,1,2」の３タイプが存在しています。
各フォーマットの詳細は解説サイトで確認してください。

```Kuin:main.kn
var formatType: int :: me.castInt(me._reader.read(2))
if(formatType < 0 | formatType > 2)
	throw ERR_FORMAT_TYPE
end if
```

### 4. トラック数の解析
`00 02` の部分です。
ヘッダチャンクの後に続くトラックチャンクが何個あるかを示しています。
上の例では、ヘッダチャンクの後ろにトラックチャンクが2個並んでいることが分かります。

```Kuin:main.kn
var trackSize: int :: me.castInt(me._reader.read(2))
```

### 5. 時間単位の解析
`01 E0` の部分です。
4分音符あたりの分解能、または秒あたりのフレーム数を示しています。
若干ややこしいので、詳しくは解説サイトで確認してください。

```Kuin:main.kn
var timeDivision: int :: me.castInt(me._reader.read(2))
```

## ヘッダチャンク解析のまとめ
これでヘッダチャンクの解析ができるようになりました。
ここまでのプログラムをまとめます。

```Kuin:main.kn
func main()
	var parser: @SMFParser :: #@SMFParser
	do parser.parse("test.mid")
end func


+class SMFParser()
	const MTHD: int :: 0x4D546864
	
	const ERR_INVALID_HEADER: int :: 0x00003100
	const ERR_FORMAT_TYPE: int :: 0x00003101
	const ERR_INVALID_TRACK_HEADER: int :: 0x00003102
	
	var _reader: file@Reader
	
	
	+func parse(filename: []char)
		do me._reader :: file@makeReader(filename)
		do me.parseHeader()
		do me._reader.fin()
	end func
	
	
	func parseHeader()
		; chunk
		var chunk: int :: me.castInt(me._reader.read(4))
		if(chunk <> MTHD)
			throw ERR_INVALID_HEADER
		end if
		
		; header size
		var headerSize: int :: me.castInt(me._reader.read(4))
		if(headerSize <> 6)
			throw ERR_INVALID_HEADER
		end if
		
		; format
		var formatType: int :: me.castInt(me._reader.read(2))
		if(formatType < 0 | formatType > 2)
			throw ERR_FORMAT_TYPE
		end if
		
		; track size
		var trackSize: int :: me.castInt(me._reader.read(2))
		
		; time division
		var timeDivision: int :: me.castInt(me._reader.read(2))
		
		; debug print
		do cui@print("format type   : \{formatType}\n")
		do cui@print("track size    : \{trackSize}\n")
		do cui@print("time division : \{timeDivision}\n")
	end func
	
	
	func parseVariableInt(): int
		var value: bit32
		
		while loop(true)
			var byte: bit8 :: me._reader.read(1)[0]
			do value :: value.shl(7).or((byte $ bit32).and(0x7Fb32))
			if(byte < 0x80b8)
				break loop
			end if
		end while
		
		ret value $ int
	end func
	
	
	func castInt(data: []bit8): int
		var value: bit32
		
		for i(0, ^data - 1)
			do value :: value.shl(8).or(data[i] $ bit32)
		end for
		
		ret value $ int
	end func
end class
```


「main.kn」と同じフォルダに「test.mid」というSMFを置いてCUIモードで実行すると、ヘッダー部分が出力されると思います。
私が持っているファイルでは以下のようになりました。

```
format type   : 1
track size    : 2
time division : 480
```

ここまで解析できたら、次はトラックチャンクを解析していく訳ですが、長くなりそうなので記事を分割します。
[KuinでSMF(MIDIファイル)を解析しよう２](https://qiita.com/satonayu/items/d82413a7a24d1344465d)