Skip to content

Instantly share code, notes, and snippets.

@rymdolle
Created November 26, 2020 19:03
Show Gist options
  • Save rymdolle/84e852fbeb9571351ab0e8f01fea4479 to your computer and use it in GitHub Desktop.
Save rymdolle/84e852fbeb9571351ab0e8f01fea4479 to your computer and use it in GitHub Desktop.
dtxplorer MIDI remap
#!/usr/bin/env escript
-module(midi).
-export([parse_string/1]).
-export([save_file/2]).
-export([events/1]).
-export([varint/1]).
-export([varinte/1]).
main([File, Out]) ->
{ok, Data} = file:read_file(File),
Chunks = parse_string(Data),
save_file(Out, [events(Chunk) || Chunk <- Chunks]);
main(_) ->
Script = filename:basename(escript:script_name()),
io:format("Usage:\n"),
io:format(" ~s INPUT_FILE OUTPUT_FILE\n\n", [Script]).
parse_string(String) ->
[[Chunk, Data] || <<Chunk:4/bytes, Size:32, Data:Size/bytes>> <= String].
save_file(File, Chunks) ->
file:write_file(File, [[Chunk, <<(iolist_size(Data)):32>>, Data] || [Chunk, Data] <- Chunks]).
events([Type = <<"MTrk">>, Chunk]) ->
io:format("<track start>\n", []),
[Type, event(Chunk)];
events([Type = <<"MThd">>, Chunk]) ->
<<Format:16, Tracks:16, Division:16>> = Chunk,
io:format("<head> f:~b t:~b d:~b\n", [Format, Tracks, Division]),
[Type, Chunk].
event(Data) ->
{Delta, Rest} = varint(Data),
event(Delta, Rest).
%% meta end event
event(Delta, Event = <<16#ff, 16#2f, 0>>) ->
io:format("<track end>\n", []),
[varinte(Delta), Event];
%% meta-event
event(Delta, <<16#ff, Type, Buffer/bytes>>) ->
{Data, Rest} = data(Buffer),
io:format("<meta> ~b: 0x~2.16.0b ~p\n", [Delta, Type, Data]),
[[varinte(Delta), 16#ff, Type, varinte(byte_size(Data)), Data] | event(Rest)];
%% sysex-event
event(Delta, <<16#f0, Buffer/bytes>>) ->
{Data, Rest} = data(Buffer),
io:format("<sysx> ~b: ~p\n", [Delta, Data]),
[[varinte(Delta), 16#f0, varinte(byte_size(Data)), Data] | event(Rest)];
%% sysex-event
event(Delta, <<16#f7, Buffer/bytes>>) ->
{Data, Rest} = data(Buffer),
io:format("<sysx> ~b: ~p\n", [Delta, Data]),
[[varinte(Delta), 16#f7, varinte(byte_size(Data)), Data] | event(Rest)];
%% Note off
event(Delta, <<16#8:4, Channel:4, Key, Velocity, Buffer/bytes>>) ->
NewKey = dtx_remap(Key),
if Key /= NewKey ->
io:format("<noff> ~5.10.0b: 0x~2.16.0b => 0x~2.16.0b [C:~2.10.0b]\n",
[Delta, Key, NewKey, Channel+1]);
true -> ok
end,
[[varinte(Delta), <<16#8:4, Channel:4>>, NewKey, Velocity] | event(Buffer)];
%% Note on
event(Delta, <<16#9:4, Channel:4, Key, Velocity, Buffer/bytes>>) ->
NewKey = dtx_remap(Key),
if Key /= NewKey ->
io:format("<noff> ~5.10.0b: 0x~2.16.0b => 0x~2.16.0b [C:~2.10.0b]\n",
[Delta, Key, NewKey, Channel+1]);
true -> ok
end,
[[varinte(Delta), <<16#9:4, Channel:4>>, NewKey, Velocity] | event(Buffer)];
%% Polyphonic key pressure
event(Delta, <<16#a:4, Channel:4, Key, Pressure, Buffer/bytes>>) ->
%%io:format("<poly> ~b: 0x~2.16.0b\n", [Delta, Key]),
[[varinte(Delta), <<16#a:4, Channel:4>>, Key, Pressure] | event(Buffer)];
%% Controller change
event(Delta, <<16#b:4, Channel:4, Number, Value, Buffer/bytes>>) ->
%%io:format("<ctrl> ~b: 0x~2.16.0b\n", [Delta, Number]),
[[varinte(Delta), <<16#b:4, Channel:4>>, Number, Value] | event(Buffer)];
%% Program change
event(Delta, <<16#c:4, Channel:4, ProgramNumber, Buffer/bytes>>) ->
io:format("<prog> ~b: 0x~2.16.0b\n", [Delta, ProgramNumber]),
[[varinte(Delta), <<16#c:4, Channel:4>>, ProgramNumber] | event(Buffer)];
%% Channel key pressure
event(Delta, <<16#d:4, Channel:4, Pressure, Buffer/bytes>>) ->
io:format("<pres> ~b: 0x~2.16.0b\n", [Delta, Pressure]),
[[varinte(Delta), <<16#d:4, Channel:4>>, Pressure] | event(Buffer)];
%% Pitch bend
event(Delta, <<16#e:4, Channel:4, LSB, MSB, Buffer/bytes>>) ->
%%io:format("<pitc> ~b: 0x~2.16.0b\n", [Delta]),
[[varinte(Delta), <<16#e:4, Channel:4>>, LSB, MSB] | event(Buffer)].
dtx_remap(31) -> 38; % G0 Snare
dtx_remap(37) -> 37; % C#1 Cross Stick
dtx_remap(34) -> 34; % A#0 Rim Shot
dtx_remap(48) -> 50; % C2 Hi Tom
dtx_remap(47) -> 47; % B1 Mid Tom
dtx_remap(43) -> 45; % G1 Lo Tom
dtx_remap(51) -> 51; % D#2 Ride bow
dtx_remap(52) -> 53; % E2 Ride edge
dtx_remap(49) -> 49; % C#2 Crash
dtx_remap(57) -> 57; % A2 Crash edge
dtx_remap(46) -> 46; % A#1 Hi-hat open
dtx_remap(42) -> 42; % F#1 Hi-hat closed
dtx_remap(44) -> 44; % G#1 Hi-hat pedal CLOSE
dtx_remap(46) -> 44; % A#1 Hi-hat pedal SPLASH
dtx_remap(33) -> 36; % A0 Bass Drum
dtx_remap(55) -> 52; % G2 China Lo
dtx_remap(Key) ->
Key.
data(Data) ->
{Size, Rest} = varint(Data),
<<Ret:Size/bytes, Rest2/bytes>> = Rest,
{Ret, Rest2}.
varint(Data) ->
varint(Data, 0).
varint(<<0:1, I:7, Rest/bytes>>, Acc) ->
{Acc bsl 7 + I, Rest};
varint(<<1:1, I:7, Rest/bytes>>, Acc) ->
varint(Rest, Acc bsl 7 + I).
varinte(N) ->
varinte(N bsr 7, N band 16#7f).
varinte(0, Acc) when Acc band 16#80 > 0 ->
<<(Acc band 16#ff), (varinte(0, Acc bsr 8))/bytes>>;
varinte(0, Acc) ->
<<(Acc band bnot 16#80)>>;
varinte(N, Acc) when Acc =< 16#ffffff ->
varinte(N bsr 7, Acc bsl 8 + (N band 16#7f bor 16#80)).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
varint_data() ->
[
{<<16#00>>, 16#00}, % 0
{<<16#7F>>, 16#7F}, % 127
{<<16#81_00:16>>, 16#80}, % 128
{<<16#87_68:16>>, 16#03_E8}, % 1000
{<<16#BD_84_40:24>>, 16#0F_42_40}, % 1000000
{<<16#FF_FF_FF_7F:32>>, 16#0F_FF_FF_FF} % 268435455
].
varint_test_() ->
Values = varint_data(),
[?_assertMatch({Result, <<>>}, varint(Data)) || {Data, Result} <- Values].
varinte_test_() ->
Values = varint_data(),
[?_assertMatch(Result, varinte(Data)) || {Result, Data} <- Values].
-endif.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment