Skip to content

Instantly share code, notes, and snippets.

@astarasikov
Created September 28, 2013 00:02
Show Gist options
  • Save astarasikov/6736879 to your computer and use it in GitHub Desktop.
Save astarasikov/6736879 to your computer and use it in GitHub Desktop.
-module(midi).
-export([parse/1, parse_file/1]).
-define(BEINT, big-unsigned-integer-unit).
-define(BEBIT, 1/?BEINT:1).
-define(BEINT7, 1/?BEINT:7).
-define(BEINT16, 1/?BEINT:16).
-define(BEINT32, 1/?BEINT:32).
-define(MIDI_HEADER, 16#4d546864:?BEINT32).
-define(TRACK_HEADER, 16#4d54726b:?BEINT32).
%-define(dprint(Format, Args), io:format(Format, Args)).
-define(dprint(Format, Args), ok).
parse_var_length(Data, Vl_accum) ->
case Data of
<<1:?BEBIT, VChunk:?BEINT7, Rest/binary>>
-> parse_var_length(Rest, VChunk bor (Vl_accum bsr 7));
<<0:?BEBIT, VChunk:?BEINT7, Rest/binary>>
-> {ok, VChunk bor (Vl_accum bsr 7), Rest};
_ -> {error, badvlen}
end.
parse_meta(Data) ->
case parse_var_length(Data, 0) of
{ok, MetaLength, MetaRest} ->
case MetaRest of
<<MetaData:MetaLength/binary, Rest/binary>> ->
?dprint("Meta length ~w~n", [MetaLength]),
parse_event(Rest);
_ -> {error, badmeta}
end;
_ -> {error, badmeta}
end.
note_name(NoteNumber) ->
lists:nth(NoteNumber rem 12,
["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
).
parse_note(EventType, NoteNumber, Velocity) ->
case EventType of
_ when EventType >= 8 andalso EventType =< 10
-> io:format("Note ~p number=~w velocity=~w~n",
[note_name(NoteNumber), NoteNumber, Velocity]);
_ -> ok
end.
parse_channel_event(EventType, Channel, Data) ->
case Data of
<<Param1:1/?BEINT:8, Param2:1/?BEINT:8, Rest/binary>>
when EventType =/= 16#C andalso EventType =/= 16#d
-> ?dprint("Event type=~w channel=~w param1=~w param2=~w~n",
[EventType, Channel, Param1, Param2]),
parse_note(EventType, Param1, Param2),
parse_event(Rest);
<<Param1:1/?BEINT:8, Rest/binary>>
-> ?dprint("Event type=~w channel=~w param1=~w~n", [EventType, Channel, Param1]),
parse_event(Rest);
_ -> {error, badevent}
end.
parse_event_data(Data) ->
case Data of
<<16#ff:1/?BEINT:8, EventType:1/?BEINT:8, Rest/binary>> ->
?dprint("Meta type=~w~n", [EventType]),
parse_meta(Rest);
<<EventType:1/?BEINT:4, Channel:1/?BEINT:4, Rest/binary>>
when EventType =/= 15 andalso Channel =/= 15
->
parse_channel_event(EventType, Channel, Rest);
_ -> {error, badevent}
end.
parse_event(RawData) ->
case parse_var_length(RawData, 0) of
{ok, DeltaTime, Data} -> parse_event_data(Data);
_ -> {error, badevent}
end.
parse_track(Bin) ->
case Bin of
<<?TRACK_HEADER, ChunkSize:?BEINT32,
TrackData:ChunkSize/binary, Rest/binary>>
-> ?dprint("Track size=~w~n", [ChunkSize]),
parse_event(TrackData),
parse_track(Rest);
_ -> {error, badtrack}
end.
frames_per_second(Fps) ->
case Fps of
<<FrameCount:?BEINT7, TicksPerFrame:1/?BEINT:8>> ->
?dprint("midi: FrameCount=~w, TicksPerFrame=~w~n", [FrameCount, TicksPerFrame]);
_ -> {error, badfps}
end.
time_division(Div) ->
case Div of
<<0:?BEBIT, TicksPerBeat:1/?BEINT:15>> ->
?dprint("midi: TicksPerBeat=~w~n", [TicksPerBeat]);
<<1:?BEBIT, FPSBin:15/bitstring>> ->
frames_per_second(FPSBin);
_ -> {error, badtimediv}
end.
parse(Mid) ->
case Mid of
<<?MIDI_HEADER, 6:?BEINT32, FileFormat:?BEINT16, TrackCount:?BEINT16,
TimeDivision:2/binary, Rest/binary>>
->
?dprint("midi: format=~w, track count=~w~n", [FileFormat, TrackCount]),
time_division(TimeDivision),
parse_track(Rest),
ok;
_ -> {error, badformat}
end.
parse_file(Name) ->
case file:read_file(Name) of
{ok, Data} -> parse(Data);
err -> err
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment