Current section
Files
Jump to
Current section
Files
src/midiutil.erl
-module(midiutil).
-export([
bpm_to_mpq/1,
event_to_text/1, event_to_text/2,
mpq_to_bpm/1,
note_length/1,
note_names/0,
note_to_string/1,
quantize/2,
seq_to_text/1, seq_to_text/2,
track_to_text/1, track_to_text/2]).
-author("Jim Menard, jim@jimmenard.com").
note_names() ->
["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"].
note_length(whole) -> 4.0;
note_length(half) -> 2.0;
note_length(quarter) -> 1.0;
note_length(eighth) -> 0.5;
note_length('8th') -> 0.5;
note_length(sixteenth) -> 0.25;
note_length('16th') -> 0.25;
note_length(thirtysecond) -> 0.125;
note_length('thirty second') -> 0.125;
note_length('32nd') -> 0.125;
note_length(sixtyfourth) -> 0.0625;
note_length('sixty fourth') -> 0.0625;
note_length('64th') -> 0.0625.
-define(MICROSECS_PER_MINUTE, 1000000 * 60).
%% Translates beats per minute to microseconds per quarter note (beat).
bpm_to_mpq(Bpm) ->
?MICROSECS_PER_MINUTE / Bpm.
%% Translates microseconds per quarter note (beat) to beats per minute.
mpq_to_bpm(Mpq) ->
?MICROSECS_PER_MINUTE / Mpq.
%% Quantize a lists's event's delta times by returning a new list of events
%% where the delta time of each is moved to the nearest multiple of Boundary.
quantize({track, ListOfEvents}, Boundary) ->
quantize(ListOfEvents, Boundary);
quantize([], _Boundary) ->
[];
quantize(ListOfEvents, Boundary) ->
{NewListOfEvents, _} =
lists:mapfoldl(fun(E, BeatsFromStart) ->
quantized_event(E, BeatsFromStart, Boundary)
end,
0, ListOfEvents),
NewListOfEvents.
%% Return a tuple containing a quantized copy of Event and the beats from
%% the start of this event before it was quantized.
quantized_event(Event, BeatsFromStart, Boundary) ->
io:format("qe ~p, ~p, ~p~n", [Event, BeatsFromStart, Boundary]),
{Name, DeltaTime, Values} = Event,
NewDeltaTime = quantized_delta_time(BeatsFromStart, DeltaTime, Boundary),
{{Name, NewDeltaTime, Values}, BeatsFromStart + DeltaTime}.
quantized_delta_time(BeatsFromStart, DeltaTime, Boundary) ->
Diff = (BeatsFromStart + DeltaTime) div Boundary,
if
Diff >= Boundary / 2 ->
DeltaTime - Diff;
true ->
DeltaTime - Diff + Boundary
end.
%% Given a MIDI note number, return the name and octave as a string.
note_to_string(Num) ->
Note = Num rem 12,
Octave = Num div 12,
lists:concat([lists:nth(Note + 1, note_names()), Octave - 1]).
seq_to_text(Seq) ->
seq_to_text(Seq, false).
seq_to_text({seq, _, Tracks}, ShowChanEvents) ->
seq_to_text({seq, Tracks}, ShowChanEvents);
seq_to_text({seq, _Header, _MetaTrack, Tracks}, ShowChanEvents) ->
seq_to_text({seq, Tracks}, ShowChanEvents);
seq_to_text({seq, Tracks}, ShowChanEvents) ->
lists:map(fun(T) -> track_to_text(T, ShowChanEvents) end, Tracks),
ok.
track_to_text(Track) ->
track_to_text(Track, false).
track_to_text(Track, ShowChanEvents) ->
io:format("~n*** Track start ***~n~n"),
{track, Events} = Track,
lists:map(fun(E) -> event_to_text(E, ShowChanEvents) end, Events),
ok.
event_to_text(Event) ->
event_to_text(Event, false).
event_to_text({Name, _} = Event, ShowChanEvents) ->
event_to_text(Event, Name, ShowChanEvents);
event_to_text({Name, _, _} = Event, ShowChanEvents) ->
event_to_text(Event, Name, ShowChanEvents).
event_to_text(Event, Name, ShowChanEvents) ->
IsChanEvent = lists:member(Name, [off, on, poly_press, controller, program,
chan_press, pitch_bend]),
if
Name == track_end ->
ok;
ShowChanEvents; not IsChanEvent ->
io:format("~p~n", [Event]);
true ->
io:format("~p~n", [Name])
end.