paiza.ioでelixir その178

# 概要
paiza.ioでelixirやってみた。
midiファイルを解析してみた。

# 参考にしたページ

https://github.com/chooblarin/midi_parser

# サンプルコード

```

:inets.start
:ssl.start


defmodule MidiParser do
	@moduledoc """
	Documentation for MidiParser.
	"""
	@doc """
	Parsing MIDI file.
	"""
	@chunk_type_header "MThd" # <<77, 84, 104, 100>>
	@chunk_type_track  "MTrk" # <<77, 84, 114, 107>>
	def parse(data) do
		{header, chunks} = extract_header(data)
		tracks = extract_tracks(chunks)
			|> Enum.map(fn x -> 
				parse_track(x) 
			end)
		{header, tracks}
	end
	def extract_header(
		<<
			@chunk_type_header,
			6 :: size(32),
			format :: size(16),
			num_of_tracks :: size(16),
			time_unit :: size(16),
			chunks :: binary
		>>) do
		header = %{
			format: format,
			num_of_tracks: num_of_tracks,
			time_unit: time_unit
		}
		{header, chunks}
	end
	def extract_tracks(data) do
		_extract_tracks(data, [])
	end
	defp _extract_tracks(<<>>, result) do
		Enum.reverse(result)
	end
	defp _extract_tracks(
		<<
			@chunk_type_track,
			length :: size(32),
			body :: binary - size(length),
			chunks :: binary
		>>, tracks) do
		_extract_tracks(chunks, [body | tracks])
	end
	def parse_track(body) do 
	    _parse_track(body, 0x00, []) 
	end
	defp _parse_track(<<>>, _, result) do 
	    Enum.reverse(result) 
	end
	defp _parse_track(body, prev, acc) do
		{delta_time, rest} = extract_variable_length(body)
		{event, chunks} = parse_event(rest, prev)
		case event do
			{:midi, status, _, _} ->
				_parse_track(chunks, status, [{delta_time, event} | acc])
			_ ->
				_parse_track(chunks, prev, [{delta_time, event} | acc])
		end
	end
	def extract_variable_length(data) do
		bits = _extract_variable_length(data)
		bs = bit_size(bits)
		padding_size = 8 - rem(bs, 8)
		byte_data = << <<0 :: size(padding_size)>> :: bitstring, bits :: bitstring >>
		bit_length = padding_size + bs
		<<length :: size(bit_length)>> = byte_data
		<<_ :: size(bit_length), chunks :: binary>> = data
		{length, chunks}
	end
	defp _extract_variable_length(<<msb :: 1, exp :: 7, _ :: binary>>) when msb == 0 do
		<<exp :: 7>>
	end
	defp _extract_variable_length(<<_ :: 1, exp :: 7, rest :: binary>>) do
		next = _extract_variable_length(rest)
		<< <<exp :: 7>> :: bitstring, next :: bitstring >>
	end
	def parse_event(data, prev \\ 0x00)
	def parse_event(<<status, _ :: binary>> = data, _) when status in [0xF0, 0xF7] do
		handle_sysex_event(data)
	end
	def parse_event(<<status, _ :: binary>> = data, _) when status == 0xFF do
		handle_meta_event(data)
	end
	def parse_event(<<status, tail :: binary>> = data, prev) do
		<<msb :: size(1), _ :: size(7)>> = <<status>>
		case msb do
			0 -> # running-status
				<<note_num, velocity, chunks :: binary>> = data
				{{:midi, midi_event(prev), note_num, velocity}, chunks}
			1 ->
				<<note_num, velocity, chunks :: binary>> = tail
				{{:midi, midi_event(status), note_num, velocity}, chunks}
		end
	end
	def handle_sysex_event(<<0xF0, tail :: binary>>) do
		{length, rest} = extract_variable_length(tail)
		<<message :: binary - size(length), 0xF7, chunks :: binary>> = rest
		{{:sysex, message}, chunks}
	end
	def handle_sysex_event(<<0xF7, tail :: binary>>) do
		{length, rest} = extract_variable_length(tail)
		<<message :: binary - size(length), chunks :: binary>> = rest
		{{:sysex, message}, chunks}
	end
	def handle_meta_event(<<_, tail :: binary>>) do
		<<meta_type, rest :: binary>> = tail
		{length, body_and_chunks} = extract_variable_length(rest)
		<<meta_data :: binary - size(length), chunks :: binary>> = body_and_chunks
		{{:meta, meta_type, meta_data}, chunks}
	end
	def handle_midi_event(<<type, tail :: binary>>) do
		event = midi_event(type)
		<<note_num, velocity, chunks :: binary>> = tail
		{{:midi, event, note_num, velocity}, chunks}
	end
	defp midi_event(x) when x in 0x80..0x8f do 
	    :note_off 
	end
	defp midi_event(x) when x in 0x90..0x9f do 
	    :note_on 
	end
	defp midi_event(x) when x in 0xb0..0xbf do 
	    :ctrl_change 
	end
	defp midi_event(_) do 
	    :unknown 
	end
end

defmodule Main do
	def main() do
		{:ok, {_status, _headers, body}} = :httpc.request('https://resolvenet.org/modules/contrib/webform/tests/files/sample.midi')
		body = IO.iodata_to_binary(body)
	    IO.inspect MidiParser.parse body
	end
end
Main.main






```

# 実行結果

```
{%{format: 0, num_of_tracks: 1, time_unit: 384},
 [
   [
     {0, {:meta, 3, "Sparks"}},
     {0, {:meta, 2, "Electronic Editorials 2003"}},
     {0, {:meta, 1, "Sparks\n"}},
     {0, {:meta, 1, "A PA eroll\n"}},
     {0, {:meta, 1, "Ampico roll number: 52404\n"}},
     {0, {:meta, 1, "Played by: Frank LaForge\n"}},
     {0, {:meta, 1, "Composed by: Moszkowski\n"}},
     {0, {:meta, 88, <<4, 2, 24, 8>>}},
     {0, {:meta, 89, <<0, 0>>}},
     {0, {:meta, 81, <<7, 161, 32>>}},
     {0, {:midi, :unknown, 0, 133}},
     {104, {:midi, :note_on, 82, 55}},
     {60, {:midi, :unknown, 82, 0}},
     {84, {:midi, :unknown, 82, 48}},
     {96, {:midi, :unknown, 82, 0}},
     {20, {:midi, :unknown, 77, 40}},
     {88, {:midi, :unknown, 77, 0}},
     {36, {:midi, :unknown, 79, 40}},
     {48, {:midi, :unknown, 79, 0}},
     {80, {:midi, :unknown, 79, 54}},
     {68, {:midi, :unknown, 79, 0}},
     {44, {:midi, :unknown, 74, 40}},
     {68, {:midi, :unknown, 74, 0}},
     {60, {:midi, :unknown, 75, 40}},
     {48, {:midi, :unknown, 75, 0}},
     {84, {:midi, :unknown, 75, 54}},
     {60, {:midi, :unknown, 75, 0}},
     {48, {:midi, :unknown, 70, 40}},
     {68, {:midi, :unknown, 70, 0}},
     {64, {:midi, :unknown, 72, 47}},
     {32, {:midi, :unknown, 72, 0}},
     {80, {:midi, :unknown, 72, 54}},
     {48, {:midi, :unknown, 72, 0}},
     {64, {:midi, :unknown, 67, 40}},
     {64, {:midi, :unknown, 67, 0}},
     {32, {:midi, :unknown, 69, 55}},
     {64, {:midi, :unknown, 69, 0}},
     {4, {:midi, :unknown, 65, 62}},
     {64, {:midi, :unknown, 67, 40}},
     {48, {:midi, :unknown, 69, 40}},
     {56, {:midi, :unknown, 70, 48}},
     {32, {:midi, :unknown, 69, ...}},
     {20, {:midi, :unknown, ...}},
     {32, {:midi, ...}},
     {16, {...}},
     {4, ...},
     {...},
     ...
   ]
 ]}
```

# 成果物

https://paiza.io/projects/v2sd1fy11hp-0iWKgmSaWA


以上。