├── README.md ├── midi_mac_driver ├── jerusalem.mid ├── rach-pc1-1.mid ├── Standard-MIDI-file-format-updated.pdf ├── Readme ├── midi_test.erl ├── Makefile ├── midi_event_gen.erl ├── midi_event_listener.erl ├── erl_comm.c ├── midi_event_gen.c ├── midi_event_listener.c ├── midi.erl ├── midi_player.erl ├── midi_parser.erl ├── midi_synt_driver.c └── rpm.erl ├── stop_scsynth.sh ├── start_scsynth.sh ├── pd.erl ├── osc_scheduler ├── Makefile ├── synt.erl ├── Readme ├── sonic_pi_emulator.erl ├── osc_scheduler.erl └── osc.erl ├── sonic.erl ├── LICENSE ├── pd_osc.pd ├── sc.erl └── osc.erl /README.md: -------------------------------------------------------------------------------- 1 | # music_experiments -------------------------------------------------------------------------------- /midi_mac_driver/jerusalem.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joearms/music_experiments/HEAD/midi_mac_driver/jerusalem.mid -------------------------------------------------------------------------------- /midi_mac_driver/rach-pc1-1.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joearms/music_experiments/HEAD/midi_mac_driver/rach-pc1-1.mid -------------------------------------------------------------------------------- /stop_scsynth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ps -ax | grep scsynth 4 | killall scsynth 5 | ps -ax | grep scsynth 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /midi_mac_driver/Standard-MIDI-file-format-updated.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joearms/music_experiments/HEAD/midi_mac_driver/Standard-MIDI-file-format-updated.pdf -------------------------------------------------------------------------------- /start_scsynth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | App="/Applications/SuperCollider/SuperCollider.app/Contents/Resources/scsynth" 4 | 5 | ${App} -v 2 -u 4556 -a 1024 -m 131072 -D 0 -R 0 -l 1 & 6 | 7 | 8 | -------------------------------------------------------------------------------- /pd.erl: -------------------------------------------------------------------------------- 1 | -module(pd). 2 | -compile(export_all). 3 | 4 | %% start PD with the program 5 | %% pd_sc.pd - click on DSP and "1" (turn sound on) 6 | 7 | play(N) -> run_code(["/playNote", N]). 8 | 9 | run_code(M) -> 10 | E = osc:encode(M), 11 | {ok, Socket} = gen_udp:open(0,[binary]), 12 | ok = gen_udp:send(Socket, "localhost", 6677, E), 13 | gen_udp:close(Socket). 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /osc_scheduler/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .erl .beam 2 | 3 | MODS := $(wildcard *.erl) 4 | 5 | %.beam: %.erl 6 | erlc -W $< 7 | 8 | all: beams 9 | erl -noshell -s sonic_pi_emulator start 10 | 11 | 12 | one: beams 13 | erl -nosmp -noshell -s sonic_pi_emulator start 14 | 15 | client: beams 16 | erl -s client test 17 | 18 | 19 | beams: ${MODS:%.erl=%.beam} 20 | 21 | clean: 22 | rm -rf *.beam *~ erl_crash.dump config 23 | -------------------------------------------------------------------------------- /midi_mac_driver/Readme: -------------------------------------------------------------------------------- 1 | 1) start Garage band 2 | 3 | > make play 4 | 5 | Plays Jerusalem 6 | 7 | 2) test driver 8 | 9 | > make test -- plays music 10 | 11 | 3) Connect USB Midi device 12 | 13 | > make read -- reads keyboard 14 | 15 | 16 | Have to configure garage band to play things 17 | 18 | 1) start GB 19 | 2) http://thegaragebandguide.com/garageband-10-au-plug-in-fix 20 | https://tal-software.com//downloads/docs/TAL%20Noisemaker%20User%20Guide%201.0.pdf 21 | 22 | -------------------------------------------------------------------------------- /osc_scheduler/synt.erl: -------------------------------------------------------------------------------- 1 | -module(synt). 2 | -compile(export_all). 3 | 4 | start() -> 5 | Port = 6000, 6 | io:format("Synt waiting on port 6000~n"), 7 | {ok, Socket} = gen_udp:open(Port, [binary]), 8 | loop(Socket). 9 | 10 | loop(Socket) -> 11 | receive 12 | {udp, Socket, _IP, _Port, Bin} -> 13 | T1 = osc:now(), 14 | {cmd,D} = osc:decode(Bin), 15 | io:format("Synt (time ~p) :: ~p~n",[T1, D]), 16 | loop(Socket); 17 | Other -> 18 | io:format("Synt received ~p~n",[Other]), 19 | loop(Socket) 20 | end. 21 | -------------------------------------------------------------------------------- /osc_scheduler/Readme: -------------------------------------------------------------------------------- 1 | sonic_pi_emulator.erl -- emulates the sonic pi sending OSC messages to port 8104 2 | osc_scheduler.erl -- listens for osc commands on port 8104 3 | synt.erl -- listens for osc commands on port 6000 4 | 5 | The Sonic Pi emulator sends 6 | 7 | /forward "localhost" 6000 /cmd args ... 8 | 9 | Commands to port 8104 10 | 11 | At osc_scheduler.erl sends commands to port 6000 12 | 13 | The synt on port 6000 prints the time it receives a command and the command 14 | itself 15 | 16 | type 17 | 18 | $ make 19 | 20 | to run a sumulation 21 | 22 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_test.erl: -------------------------------------------------------------------------------- 1 | -module(midi_test). 2 | 3 | -compile(export_all). 4 | 5 | test() -> 6 | midi_event_gen:start(), 7 | send_notes(), 8 | midi_event_gen:stop(), 9 | init:stop(). 10 | 11 | send_notes() -> 12 | L = notes(), 13 | send_notes(L). 14 | 15 | send_notes([{sleep,I}|T]) -> 16 | sleep(I), 17 | send_notes(T); 18 | send_notes([B|T]) -> 19 | io:format("sending~p~n",[B]), 20 | midi_event_gen:send(B), 21 | send_notes(T); 22 | send_notes([]) -> 23 | true. 24 | 25 | sleep(T) -> 26 | receive 27 | after T 28 | -> 29 | true 30 | end. 31 | 32 | notes() -> 33 | lists:flatten([<<192,2,0>> | scale(60,69)]). 34 | 35 | scale(J,K) -> 36 | [[<<144,I,50>>,{sleep,150},<<144,I,0>>] || I <- lists:seq(J,K)]. 37 | -------------------------------------------------------------------------------- /sonic.erl: -------------------------------------------------------------------------------- 1 | -module(sonic). 2 | -compile(export_all). 3 | 4 | %% start sonic pi 5 | %% then run me 6 | 7 | %% to UDP port 4557 8 | %% osc messages "/run_code" "play 50\n" 9 | 10 | test1() -> 11 | run_code("use_synth :fm\nplay 50\n"). 12 | 13 | test2() -> 14 | run_code(["use_synth :fm\n", make_scale()]). 15 | 16 | make_scale() -> 17 | for(70,75, 18 | fun(I) -> 19 | ["play ",integer_to_list(I),"\n", 20 | "sleep 0.1\n"] 21 | end). 22 | 23 | for(Max,Max,F) -> [F(Max)]; 24 | for(I,Max,F) -> [F(I)|for(I+1,Max,F)]. 25 | 26 | run_code(Prog) -> 27 | %% Prog is a io-list 28 | P1 = lists:flatten(Prog), 29 | M = ["/run-code" , "erl-id", P1], 30 | E = osc:encode(M), 31 | {ok, Socket} = gen_udp:open(0,[binary]), 32 | ok = gen_udp:send(Socket, "localhost", 4557, E), 33 | gen_udp:close(Socket). 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /osc_scheduler/sonic_pi_emulator.erl: -------------------------------------------------------------------------------- 1 | -module(sonic_pi_emulator). 2 | 3 | -compile(export_all). 4 | 5 | start() -> 6 | %% start a synt 7 | spawn(fun() -> synt:start() end), 8 | %% spawn a relay 9 | osc_scheduler:start(), 10 | %% open a socket 11 | {ok, Socket} = gen_udp:open(0, [binary]), 12 | %% read the clock 13 | T = osc:now(), 14 | %% scedule some future events 15 | send(Socket, T+2, ["/forward", "localhost", 6000, "/sendmidi", 12, 34, 56]), 16 | send(Socket, T+2.3, ["/forward", "localhost", 6000, "/dothis", 1, 22, 3.134]), 17 | send(Socket, T+1, ["/forward", "localhost", 6000, "/andthat", 6.7, "hello", 123]), 18 | %% wait forever (because we're launched from a makefile) 19 | receive 20 | after infinity -> 21 | true 22 | end. 23 | 24 | 25 | send(Socket, Time, Data) -> 26 | io:format("At ~p do:~p~n",[Time,Data]), 27 | Bin = osc:pack_ts(Time, Data), 28 | gen_udp:send(Socket, "localhost", 8014, Bin). 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 joe armstrong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /midi_mac_driver/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .erl .beam 2 | 3 | MODS := $(wildcard *.erl) 4 | 5 | %.beam: %.erl 6 | erlc -W $< 7 | 8 | 9 | all: beams midi_event_gen midi_event_listener midi_synt_driver 10 | 11 | read: all 12 | erl -s midi_event_listener start 13 | 14 | play: all 15 | erl -s midi_player test 16 | 17 | play_internal: all 18 | erl -s midi_player test1 19 | 20 | test1: all 21 | erl -s midi test1 22 | 23 | 24 | test: all 25 | erl -s midi_test test 26 | 27 | midi_event_listener: midi_event_listener.c erl_comm.c 28 | gcc -o midi_event_listener midi_event_listener.c erl_comm.c \ 29 | -framework CoreMidi -framework CoreServices 30 | 31 | midi_event_gen: midi_event_gen.c erl_comm.c 32 | gcc -o midi_event_gen midi_event_gen.c erl_comm.c \ 33 | -framework CoreMidi -framework CoreServices 34 | 35 | midi_synt_driver: midi_synt_driver.c erl_comm.c 36 | gcc -o midi_synt_driver midi_synt_driver.c erl_comm.c \ 37 | -framework CoreMidi -framework CoreServices \ 38 | -framework AudioToolbox 39 | 40 | beams: ${MODS:%.erl=%.beam} 41 | 42 | clean: 43 | rm -rf midi_event_gen midi_synt_driver *.beam midi_event_listener 44 | 45 | 46 | -------------------------------------------------------------------------------- /pd_osc.pd: -------------------------------------------------------------------------------- 1 | #N canvas 23 23 774 624 10; 2 | #X text 24 39 UDP listener on port 6677; 3 | #X obj 60 73 dumpOSC 6677; 4 | #X obj 60 130 OSCroute /playNote; 5 | #X obj 440 238 sendOSC; 6 | #X floatatom 427 296 5 0 0 0 - - -; 7 | #X msg 470 94 disconnect; 8 | #X floatatom 500 281 5 0 0 0 - - -; 9 | #X msg 398 58 connect localhost 6677; 10 | #X obj 62 179 unpack f; 11 | #X floatatom 266 243 5 0 0 0 - - -; 12 | #X obj 268 9 pddp/dsp; 13 | #X text 402 17; 14 | #X obj 131 322 mtof; 15 | #X msg 336 372 0; 16 | #X obj 197 405 osc~; 17 | #X obj 265 562 dac~; 18 | #X obj 272 490 *~; 19 | #X msg 336 424 1; 20 | #X text 369 421 on; 21 | #X text 365 372 off; 22 | #X text 12 286 midi note number; 23 | #X text 35 371 frequency; 24 | #X text 128 243 midi note number note; 25 | #X msg 593 122 send /playNote 60; 26 | #X msg 598 170 send /playNote 80; 27 | #X floatatom 98 371 5 0 0 0 - - -; 28 | #X connect 1 0 2 0; 29 | #X connect 2 0 8 0; 30 | #X connect 3 0 4 0; 31 | #X connect 3 1 6 0; 32 | #X connect 5 0 3 0; 33 | #X connect 7 0 3 0; 34 | #X connect 8 0 9 0; 35 | #X connect 8 0 12 0; 36 | #X connect 12 0 14 0; 37 | #X connect 12 0 25 0; 38 | #X connect 13 0 16 1; 39 | #X connect 14 0 16 0; 40 | #X connect 16 0 15 0; 41 | #X connect 16 0 15 1; 42 | #X connect 17 0 16 1; 43 | #X connect 23 0 3 0; 44 | #X connect 24 0 3 0; 45 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_event_gen.erl: -------------------------------------------------------------------------------- 1 | -module(midi_event_gen). 2 | 3 | -compile(export_all). 4 | 5 | start(Type) -> 6 | Exec = driver(Type), 7 | Prog = filename:dirname(code:which(?MODULE)) ++ Exec, 8 | register(?MODULE, 9 | spawn(fun() -> 10 | process_flag(trap_exit, true), 11 | Port = open_port({spawn, Prog}, 12 | [{packet, 2}]), 13 | loop(Port) 14 | end)), 15 | sleep(1500). %% why since drivers takes a while to start 16 | 17 | driver(internal) -> "/midi_synt_driver"; 18 | driver(external) -> "/midi_event_gen". 19 | 20 | sleep(T) -> 21 | receive 22 | after T -> 23 | true 24 | end. 25 | 26 | stop() -> 27 | ?MODULE ! stop. 28 | 29 | send(M) -> call_port([1|M]). 30 | 31 | call_port(Msg) -> 32 | ?MODULE ! {call, self(), Msg}, 33 | receive 34 | {?MODULE, Result} -> 35 | Result 36 | end. 37 | 38 | loop(Port) -> 39 | receive 40 | {call, Caller, Msg} -> 41 | Port ! {self(), {command, Msg}}, 42 | receive 43 | {Port, {data, Data}} -> 44 | Caller ! {?MODULE, Data} 45 | end, 46 | loop(Port); 47 | stop -> 48 | Port ! {self(), close}, 49 | receive 50 | {Port, closed} -> 51 | exit(normal) 52 | end; 53 | {'EXIT', Port, Reason} -> 54 | exit({port_terminated, Reason}) 55 | end. 56 | 57 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_event_listener.erl: -------------------------------------------------------------------------------- 1 | -module(midi_event_listener). 2 | 3 | -compile(export_all). 4 | 5 | start() -> 6 | register(?MODULE, 7 | spawn(fun() -> 8 | process_flag(trap_exit, true), 9 | Port = open_port({spawn, "./midi_event_listener"}, 10 | [{packet, 2}]), 11 | io:format("Port=~p~n",[Port]), 12 | loop(Port) 13 | end)). 14 | 15 | sleep(T) -> 16 | receive 17 | after T -> 18 | true 19 | end. 20 | 21 | stop() -> 22 | ?MODULE ! stop. 23 | 24 | send(M) -> call_port([1|M]). 25 | 26 | call_port(Msg) -> 27 | ?MODULE ! {call, self(), Msg}, 28 | receive 29 | {?MODULE, Result} -> 30 | Result 31 | end. 32 | 33 | loop(Port) -> 34 | receive 35 | {call, Caller, Msg} -> 36 | Port ! {self(), {command, Msg}}, 37 | receive 38 | {Port, {data, Data}} -> 39 | Caller ! {?MODULE, Data} 40 | end, 41 | loop(Port); 42 | stop -> 43 | Port ! {self(), close}, 44 | receive 45 | {Port, closed} -> 46 | exit(normal) 47 | end; 48 | {'EXIT', Port, Reason} -> 49 | exit({port_terminated, Reason}); 50 | {Port,{data,Data}} -> 51 | io:format("~n~p bytes~n",[length(Data)]), 52 | Data1 = cvt(list_to_binary(Data)), 53 | io:format("event:~p~n",[Data1]), 54 | loop(Port); 55 | Any -> 56 | io:format("Received:~p~n",[Any]), 57 | loop(Port) 58 | end. 59 | 60 | cvt(<<1,T:64/unsigned-little-integer,B/binary>>) -> 61 | {T, B}. 62 | 63 | -------------------------------------------------------------------------------- /midi_mac_driver/erl_comm.c: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Programming Erlang, Second Edition", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jaerlang2 for more book information. 8 | ***/ 9 | /* erl_comm.c */ 10 | #include 11 | typedef unsigned char byte; 12 | 13 | int read_cmd(byte *buf); 14 | int write_cmd(byte *buf, int len); 15 | int read_exact(byte *buf, int len); 16 | int write_exact(byte *buf, int len); 17 | 18 | int read_cmd(byte *buf) 19 | { 20 | int len; 21 | if (read_exact(buf, 2) != 2) 22 | return(-1); 23 | len = (buf[0] << 8) | buf[1]; 24 | return read_exact(buf, len); 25 | } 26 | 27 | int write_cmd(byte *buf, int len) 28 | { 29 | byte li; 30 | li = (len >> 8) & 0xff; 31 | write_exact(&li, 1); 32 | li = len & 0xff; 33 | write_exact(&li, 1); 34 | return write_exact(buf, len); 35 | } 36 | 37 | int read_exact(byte *buf, int len) 38 | { 39 | int i, got=0; 40 | do { 41 | if ((i = read(0, buf+got, len-got)) <= 0) 42 | return(i); 43 | got += i; 44 | } while (got 2 | #include 3 | #include 4 | 5 | typedef unsigned char byte; 6 | 7 | int read_cmd(byte *buff); 8 | int write_cmd(byte *buff, int len); 9 | int sum(int x, int y); 10 | int twice(int x); 11 | 12 | int main() { 13 | MIDIClientRef theMidiClient; 14 | MIDIEndpointRef midiOut; 15 | MIDIPortRef outPort; 16 | char pktBuffer[1024]; 17 | MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer; 18 | MIDIPacket *pkt; 19 | Byte midiDataToSend[] = {0x91, 0x3c, 0x40}; 20 | Byte midiDataToSend1[] = {0x91, 0x3c, 0x00}; 21 | int i; 22 | 23 | MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL, 24 | &theMidiClient); 25 | MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"), 26 | &midiOut); 27 | MIDIOutputPortCreate(theMidiClient, CFSTR("Magical MIDI Out Port"), 28 | &outPort); 29 | 30 | int fn, arg1, arg2, result; 31 | byte buff[100]; 32 | int k; 33 | 34 | while ((k = read_cmd(buff)) > 0) { 35 | fn = buff[0]; 36 | 37 | if (fn == 1) { 38 | // send midi 39 | pkt = MIDIPacketListInit(pktList); 40 | pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, buff+1); 41 | result = 1; 42 | /* debug -- you can print to stderr to debug */ 43 | /* fprintf(stderr,"len=%d %d %d \n",buff[1], buff[2], buff[3]); */ 44 | if (pkt == NULL || MIDIReceived(midiOut, pktList)) { 45 | result = 0; 46 | } else { 47 | result = 1; 48 | } 49 | 50 | } else { 51 | /* just exit on unknown function */ 52 | exit(EXIT_FAILURE); 53 | } 54 | 55 | buff[0] = result; 56 | write_cmd(buff, 1); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /osc_scheduler/osc_scheduler.erl: -------------------------------------------------------------------------------- 1 | -module(osc_scheduler). 2 | -compile(export_all). 3 | 4 | %% We run on port 8014 5 | server_port() -> 8014. 6 | 7 | start() -> 8 | S = self(), 9 | register(?MODULE, spawn(fun() -> go(S) end)), 10 | receive 11 | ack -> 12 | true 13 | end. 14 | 15 | go(P) -> 16 | {ok, Socket} = gen_udp:open(server_port(), [binary]), 17 | io:format("Osc scheduler listening on port:~p~n",[server_port()]), 18 | P ! ack, 19 | loop(Socket). 20 | 21 | loop(Socket) -> 22 | receive 23 | {udp, Socket, _IP, _Port, Bin} -> 24 | %% io:format("received:~p~n",[Bin]), 25 | %% T2 = erlang:system_time()/1000000000, 26 | %% io:format("received packet :~p~n",[T2]), 27 | handle_message(Socket, Bin), 28 | loop(Socket); 29 | Any -> 30 | io:format("Any:~p~n",[Any]), 31 | loop(Socket) 32 | end. 33 | 34 | handle_message(Socket, Bin) -> 35 | %% io:format("received:~p~n",[Bin]), 36 | case osc:decode(Bin) of 37 | {bundle,Time,[{_Len,B1}]} -> 38 | Cmd = osc:decode(B1), 39 | %% io:format("Cmd=~p~n",[Cmd]), 40 | do_command(Socket, Cmd, Time) 41 | 42 | end. 43 | 44 | do_command(Socket, {cmd, ["/forward", Host, Port | Cmd]}, Time) -> 45 | %% Build the comand to send 46 | Bin = osc:encode(Cmd), 47 | at_time_send(Host, Port, Time, Socket, Bin); 48 | do_command(_, X, _) -> 49 | io:format("invalid command:~p~n", [X]). 50 | 51 | 52 | at_time_send(Host, Port, Time, Socket, Bin) -> 53 | %% io:format("at time ~p send",[Time]), 54 | spawn(fun() -> 55 | send_message_at_time(Host, Port, Time, Socket, Bin) 56 | end). 57 | 58 | send_message_at_time(Host, Port, WantedTime, Socket, Bin) -> 59 | Tnow = my_now(), 60 | Delta = trunc((WantedTime - Tnow)*1000), 61 | if 62 | Delta > 0 -> 63 | erlang:send_after(Delta, self(), ping), 64 | receive 65 | ping -> 66 | gen_udp:send(Socket, Host, Port, Bin) 67 | end; 68 | true -> 69 | void 70 | end. 71 | 72 | my_now() -> 73 | %% seconds past epoc 74 | erlang:system_time()/1000000000. 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /sc.erl: -------------------------------------------------------------------------------- 1 | -module(sc). 2 | -compile(export_all). 3 | 4 | test() -> 5 | start(), 6 | init(), 7 | play(10). 8 | 9 | play(0) -> 10 | true; 11 | play(N) -> 12 | play_70(), 13 | play(N-1). 14 | 15 | init() -> 16 | send(["/dumpOSC", 3]), 17 | send(["/clearSched"]), %% - clear all scheduled bundles 18 | send(["/g_freeAll", 0]), 19 | send(["/notify", 1]), %% - send me notifications 20 | send(["/d_loadDir", "/Applications/Sonic Pi.app/etc/synthdefs/compiled"]), 21 | send(["/sync", 1]), 22 | send(["/b_allocRead", 0, "/Applications/Sonic Pi.app/etc/buffers/rand-stream.wav", 0, 0]), 23 | send(["/sync", 2]), 24 | send(["/b_query", 0]), 25 | send(["/clearSched"]), 26 | send(["/g_freeAll", 0]), 27 | send(["/g_new",2, 0, 0]), %% add a new group ... fail duplicate node 28 | send(["/g_new",3, 2, 2]), 29 | send(["/g_new",4, 2, 3]), 30 | send(["/g_new",5, 3, 2]), 31 | send(["/s_new", "sonic-pi-mixer", 6, 0, 2, "in_bus", 10]), 32 | send(["/status"]), 33 | send(["/sync", 3]). 34 | 35 | play_70() -> 36 | send(["/g_new", 7, 1, 4]), 37 | send(["/s_new", "sonic-pi-basic_mixer", 8, 0, 2, 38 | %% 8 = syn ID 39 | %% 0 it's an add 40 | %% add target = 2 41 | amp, 1, 42 | amp_slide, 0.1, 43 | amp_slide_shape, 1, 44 | amp_slide_curve, 0, 45 | "in_bus", 12, 46 | "amp", 0.3, 47 | "out_bus", 10]), 48 | send(["/s_new", "sonic-pi-beep", 9, 0, 7, note, 70.0, "out_bus", 12, 49 | "/n_set", 8, "amp_slide", 1.0]), 50 | send(["/n_set", 8, "amp_slide", 1.0]), 51 | sleep(1000), 52 | send(["/n_set", 8, "amp", 0.0]), 53 | send(["/n_free", 8]), 54 | send(["/n_free", 7]). 55 | 56 | start() -> 57 | S = self(), 58 | register(server, spawn(fun() -> go(S) end)), 59 | receive 60 | ack -> 61 | true 62 | end. 63 | 64 | send(M) -> 65 | server ! {send, M}. 66 | 67 | sleep(T) -> 68 | receive 69 | after T -> 70 | true 71 | end. 72 | 73 | go(P) -> 74 | {ok, Socket} = gen_udp:open(0,[binary]), 75 | P ! ack, 76 | io:format("Socket:~p~n",[Socket]), 77 | loop(Socket). 78 | 79 | loop(Socket) -> 80 | receive 81 | {send, M} -> 82 | E = encode(M), 83 | io:format("Send: ~p~n",[M]), 84 | gen_udp:send(Socket, "localhost", 4556, E), 85 | loop(Socket); 86 | {udp, Socket, _Ip, 4556, Bin} -> 87 | Msg = osc:decode(Bin), 88 | io:format("received: ~p~n",[Msg]), 89 | loop(Socket); 90 | Any -> 91 | io:format("Any:~p~n",[Any]), 92 | loop(Socket) 93 | end. 94 | 95 | 96 | encode(M) -> 97 | osc:encode(M). 98 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_event_listener.c: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | #import 4 | 5 | #include 6 | #include 7 | 8 | typedef unsigned char byte; 9 | 10 | int read_cmd(byte *buff); 11 | int write_cmd(byte *buff, int len); 12 | int sum(int x, int y); 13 | int twice(int x); 14 | 15 | byte buff[100]; 16 | 17 | static void print_error(OSStatus error, const char *operation) 18 | { 19 | if (error == noErr) return; 20 | 21 | char str[20]; 22 | // see if it appears to be a 4-char-code 23 | *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error); 24 | if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { 25 | str[0] = str[5] = '\''; 26 | str[6] = '\0'; 27 | } else 28 | // no, format it as an integer 29 | sprintf(str, "Error1 %d", (int)error); 30 | 31 | fprintf(stderr, "Error: %s (%s)\n", operation, str); 32 | } 33 | 34 | 35 | 36 | static void MyMIDIReadProc(const MIDIPacketList *pktlist, 37 | void *refCon, void *connRefCon) 38 | { 39 | int *player; 40 | 41 | MIDIPacket *packet = (MIDIPacket *)pktlist->packet; 42 | UInt16 length; 43 | UInt64 t; 44 | unsigned long long ll2; 45 | 46 | for (int i = 0; i < pktlist->numPackets; i++) { 47 | length = packet -> length; 48 | t = packet -> timeStamp; 49 | // fprintf(stderr, "joe %llu %d %d %d %d\n", 50 | // t, length, packet->data[0], packet->data[1], 51 | // packet->data[2]); 52 | // fflush(stderr); 53 | //ll2 = t; 54 | //memcpy(buff+1, &t, 8); 55 | buff[0] = 1; 56 | buff[1] = t & 0xff; 57 | buff[2] = (t >> 8) & 0xff; 58 | buff[3] = (t >> 16) & 0xff; 59 | buff[4] = (t >> 24) & 0xff; 60 | buff[5] = (t >> 32) & 0xff; 61 | buff[6] = (t >> 40) & 0xff; 62 | buff[7] = (t >> 48) & 0xff; 63 | buff[8] = (t >> 56) & 0xff; 64 | for(i= 0; i < length; i++){ 65 | buff[9+i] = packet -> data[i]; 66 | }; 67 | write_cmd(buff, 9+length); 68 | // fflush(stdout); 69 | packet = MIDIPacketNext(packet); 70 | } 71 | } 72 | 73 | int main(int argc, const char* argv[]){ 74 | 75 | MIDIClientRef midiclient; 76 | MIDIPortRef midiin; 77 | OSStatus status; 78 | MIDIEndpointRef src; 79 | 80 | int iDevice = 0; // nanoKey we know this 81 | 82 | status = MIDIClientCreate(CFSTR("TeStInG"), NULL, NULL, &midiclient); 83 | if (status != 0){ 84 | print_error(status, "MIDICLientCreate"); 85 | // exit(status); 86 | }; 87 | 88 | status = MIDIInputPortCreate(midiclient, 89 | CFSTR("InPuT"), 90 | MyMIDIReadProc, 91 | nil, 92 | // NULL, 93 | &midiin); 94 | if(status != 0){ 95 | // fprintf(stderr,"%i\r\n", status); 96 | print_error(status, "MIDIInputPortCreate"); 97 | // exit(status); 98 | }; 99 | fprintf(stderr, "opening keyboard:%i\r\n", iDevice); 100 | src = MIDIGetSource(iDevice); 101 | MIDIPortConnectSource(midiin, src, NULL); 102 | fprintf(stderr, "keyboard connected to source %i\r\n", iDevice); 103 | CFRunLoopRun(); 104 | } 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /midi_mac_driver/midi.erl: -------------------------------------------------------------------------------- 1 | -module(midi). 2 | -compile(export_all). 3 | 4 | test1() -> 5 | %% test the internal driver 6 | midi_event_gen:start(internal), 7 | scale(60,70,100), 8 | instruments(20,30). 9 | 10 | instruments(Min, Max) -> 11 | for(Min, Max, 12 | fun(I) -> 13 | set_instrument(1, I), 14 | scale(60,70,50) 15 | end). 16 | 17 | set_instrument(Channel, Instrument) -> 18 | io:format("changing instrument to:~p~n", 19 | [instrument_name(Instrument)]), 20 | midi_player:do({programChange,1,Instrument}). 21 | 22 | scale(N, K, T) -> 23 | for(60,70, 24 | fun(I) -> play_note(I,T) end 25 | ). 26 | 27 | for(I,I,F) -> F(I); 28 | for(I,J,F) -> F(I), for(I+1,J,F). 29 | 30 | play_note(I,T) -> 31 | midi_player:do({noteOn,1,I,80}), 32 | timer:sleep(T), 33 | midi_player:do({noteOn,1,I,0}). 34 | 35 | instrument_name(I) -> 36 | element(I, instrument_names()). 37 | 38 | instrument_names() -> 39 | {"Acoustic Grand Piano", "Bright Acoustic Piano", 40 | "Electric Grand Piano", "Honky-tonk Piano", 41 | "Electric Piano 1", "Electric Piano 2", "Harpsichord", 42 | "Clavi", "Celesta", "Glockenspiel", "Music Box", 43 | "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", 44 | "Dulcimer", "Drawbar Organ", "Percussive Organ", 45 | "Rock Organ", "Church Organ", "Reed Organ", 46 | "Accordion", "Harmonica", "Tango Accordion", 47 | "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", 48 | "Electric Guitar (jazz)", "Electric Guitar (clean)", 49 | "Electric Guitar (muted)", "Overdriven Guitar", 50 | "Distortion Guitar", "Guitar harmonics", 51 | "Acoustic Bass", "Electric Bass (finger)", 52 | "Electric Bass (pick)", "Fretless Bass", 53 | "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", 54 | "Synth Bass 2", "Violin", "Viola", "Cello", 55 | "Contrabass", "Tremolo Strings", "Pizzicato Strings", 56 | "Orchestral Harp", "Timpani", "String Ensemble 1", 57 | "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", 58 | "Choir Aahs", "Voice Oohs", "Synth Voice", 59 | "Orchestra Hit", "Trumpet", "Trombone", "Tuba", 60 | "Muted Trumpet", "French Horn", "Brass Section", 61 | "SynthBrass 1", "SynthBrass 2", "Soprano Sax", 62 | "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", 63 | "English Horn", "Bassoon", "Clarinet", "Piccolo", 64 | "Flute", "Recorder", "Pan Flute", "Blown Bottle", 65 | "Shakuhachi", "Whistle", "Ocarina", "Lead 1 (square)", 66 | "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", 67 | "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", 68 | "Lead 8 (bass + lead)", "Pad 1 (new age)", "Pad 2 (warm)", 69 | "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", 70 | "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", 71 | "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", 72 | "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", 73 | "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", 74 | "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", 75 | "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", 76 | "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", 77 | "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", 78 | "Seashore", "Bird Tweet", "Telephone Ring", 79 | "Helicopter", "Applause", "Gunshot"}. 80 | 81 | -------------------------------------------------------------------------------- /osc.erl: -------------------------------------------------------------------------------- 1 | -module(osc). 2 | -compile(export_all). 3 | 4 | test() -> 5 | test1(["/foo",1000,-1,"hello",1.234,5.678]), 6 | test1(["/abc"]), 7 | test1(["/abc",1,2,3]), 8 | test1(["/abc",2,0,0]), 9 | test1(["/abc",2,"abc",0,2,3]). 10 | 11 | %% The next example shows the 40 bytes in the representation of the OSC Message 12 | %% with OSC Address Pattern "/foo" and 5 arguments: 13 | 14 | %% The int32 1000 15 | %% The int32 -1 16 | %% The string "hello" 17 | %% The float32 1.234 18 | %% The float32 5.678 19 | %% 2f (/) 66 (f) 6f (o) 6f (o) 20 | %% 0 () 0 () 0 () 0 () 21 | 22 | %% 2c (,) 69 (i) 69 (i) 73 (s) 23 | %% 66 (f) 66 (f) 0 () 0 () 24 | 25 | %% 0 () 0 () 3 () e8 (è) 26 | %% ff (ÿ) ff (ÿ) ff (ÿ) ff (ÿ) 27 | 28 | %% 68 (h) 65 (e) 6c (l) 6c (l) 29 | %% 6f (o) 0 () 0 () 0 () 30 | 31 | %% 3f (?) 9d () f3 (ó) b6 (¶) 32 | %% 40 (@) b5 (µ) b2 (”) 2d (-) 33 | 34 | test1([H|T]=M) -> 35 | io:format("testing:~p~n",[M]), 36 | B1 = osc_lib:encode({message,H,T}), 37 | B2 = encode(M), 38 | case B1 of 39 | B2 -> 40 | B2; 41 | _ -> 42 | io:format("oops ~p~nlib:~p~n me:~p~n",[M,B1,B2]) 43 | end, 44 | case decode(B2) of 45 | M -> 46 | ok; 47 | M1 -> 48 | io:format("input:~p~nrecon:~p~n",[M,M1]) 49 | end, 50 | B2. 51 | 52 | 53 | encode([Verb|Args]) -> 54 | Str = encode_arg(Verb), 55 | Flags = encode_flags(Args), 56 | Data = [encode_arg(I) || I <- Args], 57 | list_to_binary([Str,Flags,Data]). 58 | 59 | 60 | encode_arg(X) when is_list(X) -> encode_string(X); 61 | encode_arg(X) when is_atom(X) -> encode_string(atom_to_list(X)); 62 | encode_arg(X) when is_integer(X) -> <>; 63 | encode_arg(X) when is_float(X) -> <>. 64 | 65 | 66 | encode_flags(L) when is_list(L) -> 67 | %% flags starts with , and is terminated with a zero 68 | %% so it's really a string :-) 69 | L1 = [flag(I) || I <- L], 70 | encode_string([$,|L1]). 71 | 72 | flag(I) when is_integer(I) -> $i; 73 | flag(X) when is_list(X) -> $s; 74 | flag(X) when is_atom(X) -> $s; 75 | flag(X) when is_float(X) -> $f. 76 | 77 | encode_string(S) -> 78 | %% zero terminate S and pad to 4 byte boundary 79 | case length(S) rem 4 of 80 | 0 -> [S,0,0,0,0]; 81 | 1 -> [S,0,0,0]; 82 | 2 -> [S,0,0]; 83 | 3 -> [S,0] 84 | end. 85 | 86 | decode(B0) when is_binary(B0) -> 87 | {Verb, B1} = get_string(B0), 88 | {[$,|Flags], B2} = get_string(B1), 89 | [Verb|get_args(Flags, B2, [])]. 90 | 91 | get_args([$i|T1], <>, L) -> 92 | get_args(T1, T2, [I|L]); 93 | get_args([$f|T1], <>, L) -> 94 | get_args(T1, T2, [F|L]); 95 | get_args([$d|T1], <>, L) -> 96 | get_args(T1, T2, [Double|L]); 97 | get_args([$s|T1], B0, L) -> 98 | {Str, B1} = get_string(B0), 99 | get_args(T1, B1, [Str|L]); 100 | get_args([], _, L) -> 101 | lists:reverse(L). 102 | 103 | get_string(X) when is_binary(X) -> 104 | [Bin,After] = binary:split(X, <<0>>), 105 | %% skip to bounday 106 | K = case size(Bin) rem 4 of 107 | 0 -> 3; 108 | 1 -> 2; 109 | 2 -> 1; 110 | 3 -> 0 111 | end, 112 | {binary_to_list(Bin), skip(K, After)}. 113 | 114 | skip(0, B) -> B; 115 | skip(N, B) -> element(2, erlang:split_binary(B, N)). 116 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_player.erl: -------------------------------------------------------------------------------- 1 | -module(midi_player). 2 | -compile(export_all). 3 | 4 | test() -> 5 | midi_event_gen:start(external), 6 | %% play("jerusalem.mid"), 7 | play("rach-pc1-1.mid"), 8 | true. 9 | 10 | test1() -> 11 | midi_event_gen:start(internal), 12 | play("jerusalem.mid"), 13 | %% play("rach-pc1-1.mid"), 14 | true. 15 | 16 | 17 | play(F) -> 18 | L = midi_parser:parse_file(F), 19 | Tmap = make_tempo_map(L), 20 | [_,_|L0] = L, 21 | L1 = [normalise(I, Tmap) || I <- L0], 22 | L2 = lists:append(L1), 23 | L3 = lists:sort(L2), 24 | play1(0, L3), 25 | true. 26 | 27 | make_tempo_map([#{time := {ticks_per_quarter_note, PPQN}}, 28 | #{data := D} | _]) -> 29 | case find_time_sig(D) of 30 | no -> 31 | io:format("no time signature found~n"); 32 | {{timeSig,N,M,C,B}, Rest} -> 33 | io:format("PPQNQ = ~p~n",[PPQN]), 34 | io:format("Time Sig = ~p/~p~n",[N,M]), 35 | io:format("Ticks per clock = ~p~n",[C]), 36 | io:format("number of 1/32 note per quarter (=8) = ~p~n",[B]), 37 | make_tempo_map(Rest, 0, 0, PPQN, []) 38 | end. 39 | 40 | find_time_sig([{_,{timeSig,_,_,_,_}=Ts}|T]) -> 41 | {Ts, T}; 42 | find_time_sig([_|T]) -> 43 | find_time_sig(T); 44 | find_time_sig(_) -> 45 | no. 46 | 47 | make_tempo_map([{Dt,{setTempo, Tempo}}|T], Tabs, OldTick, PPQN, L) -> 48 | Now = Tabs + Dt*OldTick, 49 | BPM = 600000000/Tempo, 50 | Tick = 600/(BPM*PPQN), 51 | io:format("BPM = ~p~n",[BPM]), 52 | io:format("Tick = ~p~n",[Tick]), 53 | make_tempo_map(T, Now, Tick, PPQN, [{Dt, Tick}|L]); 54 | make_tempo_map([{Dt,X}|T], Tabs, Tick, PPQN, L) -> 55 | io:format("dropping~p~n",[X]), 56 | make_tempo_map(T, Tabs + Dt*Tick, Tick, PPQN, L); 57 | make_tempo_map([], _, _, _, L) -> 58 | lists:reverse(L). 59 | 60 | play1(Time, [{Time,D}|T]) -> 61 | do(D), 62 | play1(Time, T); 63 | play1(Time1, [{Time2,D}|T]) -> 64 | delay(Time2-Time1), 65 | do(D), 66 | play1(Time2, T). 67 | 68 | delay(T) -> 69 | timer:sleep(T). 70 | 71 | do({programChange, Channel, K}) -> 72 | %% Name = midi_sounds:sound_name(K), 73 | %% io:format("setting Channel:~p to ~p:~s~n",[Channel,K,Name]), 74 | midi_event(16#C0 + Channel, K, 0); 75 | 76 | do({controllerChange, Channel, Pitch, Vol}) -> 77 | midi_event(16#B0 + Channel, Pitch, Vol); 78 | do({noteOn, Channel, Pitch, Vol}) -> 79 | midi_event(16#90 + Channel, Pitch, Vol); 80 | do({noteOff, Channel, Pitch, Vol}) -> 81 | midi_event(16#80 + Channel, Pitch, Vol); 82 | do({lyric, X}) -> 83 | io:format("~s ~n",[X]); 84 | do(X) -> 85 | io:format("dropping:~p~n",[X]). 86 | 87 | 88 | normalise(#{type := track, data := Data}, Tmap) -> 89 | [{_,Delta}|_] = Tmap, 90 | Abs = 0, 91 | add_times(Data, Abs, Delta, Tmap, []). 92 | 93 | add_times([{T1,E}|T2], Abs, Delta, [], L) -> 94 | Abs1 = Abs + T1 * Delta, 95 | add_times(T2, Abs1, Delta, [], [{trunc(Abs1),E}|L]); 96 | add_times([{T1,E}|T2]=A, Abs, Delta, [{Tnext,Delta1}|T3]=A1, L) -> 97 | Abs1 = T1*Delta + Abs, 98 | if 99 | Abs1 >= Tnext -> 100 | %% change delta for next event 101 | add_times(T2, Abs1, Delta1, T3, [{trunc(Abs1*1000),E}|L]); 102 | true -> 103 | add_times(T2, Abs1, Delta, A1, [{trunc(Abs1*1000),E}|L]) 104 | end; 105 | add_times([], _, _, _, L) -> 106 | lists:reverse(L). 107 | 108 | add_times(T1, [{T2,D}|T], L) -> 109 | T3 = T1 + T2, 110 | add_times(T3, T, [{T3,D}|L]); 111 | add_times(_, [], L) -> 112 | lists:reverse(L). 113 | 114 | midi_event(X,Y,Z) -> 115 | midi_event_gen:send([X,Y,Z]). 116 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_parser.erl: -------------------------------------------------------------------------------- 1 | -module(midi_parser). 2 | -compile(export_all). 3 | 4 | info() -> info("rach-pc1-1.mid"). 5 | 6 | test() -> 7 | E = parse_file("jerusalem.mid"), 8 | E. 9 | 10 | parse_file(F) -> 11 | {ok, Bin} = file:read_file(F), 12 | parse_bin(Bin). 13 | 14 | parse_bin(B) -> 15 | Chunks = collect_chunks(B, []), 16 | [parse_chunk(I) || I <- Chunks]. 17 | 18 | %%---------------------------------------------------------------------- 19 | 20 | collect_chunks(<<"MThd", Len:32, B:Len/binary,Rest/binary>>, L) -> 21 | collect_chunks(Rest, [{header,B}|L]); 22 | collect_chunks(<<"MTrk", Len:32, B:Len/binary,Rest/binary>>, L) -> 23 | collect_chunks(Rest, [{track,B}|L]); 24 | collect_chunks(<<>>, L) -> 25 | lists:reverse(L). 26 | 27 | %%---------------------------------------------------------------------- 28 | 29 | parse_chunk({header,<>}) -> 30 | #{type => header, format=>Format, tracks => Tracks, time => get_ticks(TimeDiv)}; 31 | parse_chunk({track, Bin}) -> 32 | #{type => track, data => parse_track(Bin, <<>>, [])}. 33 | 34 | parse_track(<<>>, _Status, Acc) -> 35 | lists:reverse(Acc); 36 | parse_track(DataWTime, Status, Acc) -> 37 | {Delta, Data} = get_varint(DataWTime, 0), 38 | case get_next_event(Data, Status) of 39 | {NewStatus, {Event, Rest}} -> 40 | parse_track(Rest, NewStatus, [{Delta, Event} | Acc]); 41 | {Event, Rest} -> 42 | parse_track(Rest, Status, [{Delta, Event} | Acc]) 43 | end. 44 | 45 | get_next_event(<> = X, _) when Status >= 16#80 -> 46 | {Status, get_event(X)}; 47 | get_next_event(Data, RunningStatus) -> 48 | {RunningStatus, get_event(<>)}. 49 | 50 | get_event(<<16#FF,3,Len:8,Str:Len/binary, Rest/binary>>) -> 51 | {{trackName,Str}, Rest}; 52 | get_event(<<16#FF,4,Len:8,Str:Len/binary, Rest/binary>>) -> 53 | {{instrumentName,Str}, Rest}; 54 | get_event(<<16#FF,5,Len:8,Str:Len/binary, Rest/binary>>) -> 55 | {{lyric,Str}, Rest}; 56 | get_event(<<16#FF,16#2f,0,Rest/binary>>) -> 57 | {endOfTrack, Rest}; 58 | get_event(<<16#FF,16#51,3,T:24,Rest/binary>>) -> 59 | {{setTempo,T}, Rest}; 60 | get_event(<<16#FF,16#58,4,NN,DD,CC,BB,Rest/binary>>) -> 61 | {{timeSig, NN,DD, CC, BB}, Rest}; 62 | get_event(<<16#FF,16#59,2,SF,MI,Rest/binary>>) -> 63 | {{keySig, SF,MI}, Rest}; 64 | get_event(<<16#FF,16#7F,Len,B:Len/binary,Rest/binary>>) -> 65 | io:format("???~p~n",[B]), 66 | {{extension,B}, Rest}; 67 | get_event(<<8:4,C:4, K, W, R/binary>>) -> 68 | {{noteOff, C, K, W}, R}; 69 | get_event(<<9:4,C:4, K, W, R/binary>>) -> 70 | {{noteOn, C, K, W}, R}; 71 | get_event(<<16#A:4,C:4, K, W, R/binary>>) -> 72 | {{polyPhonicKeyPressure, C, K, W}, R}; 73 | get_event(<<16#B:4,C:4, K, W, R/binary>>) -> 74 | {{controllerChange, C, K, W}, R}; 75 | get_event(<<16#C:4,C:4, K, R/binary>>) -> 76 | {{programChange, C, K}, R}; 77 | get_event(<<16#D:4,C:4, K, R/binary>>) -> 78 | {{channelPressure, C, K}, R}; 79 | get_event(<<16#E:4,C:4, K1, K2, R/binary>>) -> 80 | {{pitchBend, C, K1, K2}, R}. 81 | 82 | 83 | %%---------------------------------------------------------------------- 84 | 85 | get_varint(<<0:1, N:7/integer-unsigned, B/binary>>, Acc) -> 86 | {Acc bsl 7 + N, B}; 87 | get_varint(<<1:1, N:7/integer-unsigned, B/binary>>, Acc) -> 88 | get_varint(B, Acc bsl 7 + N). 89 | 90 | 91 | %%---------------------------------------------------------------------- 92 | %% rewrite ... 93 | 94 | get_ticks(<<1:1, SMPTEFrames:7/integer-unsigned, TicksPerFrame:16/integer-unsigned>>) -> 95 | Milliseconds = case SMPTEFrames of 96 | 128-29 -> 97 | 29.97 * TicksPerFrame; 98 | _ -> 99 | (128-SMPTEFrames) * TicksPerFrame 100 | end, 101 | {milliseconds, Milliseconds}; 102 | get_ticks(<<0:1, TicksPerQuarter:15/integer-unsigned>>) -> 103 | {ticks_per_quarter_note, TicksPerQuarter}. 104 | 105 | 106 | info(F) -> 107 | P = parse_file(F), 108 | O = [summary(I) || I <- P], 109 | O. 110 | 111 | 112 | summary(#{format := _} = X) -> 113 | {header, X}; 114 | summary(#{data:=D} ) -> 115 | {track, get_elements(D, #{})}. 116 | 117 | get_elements([], D) -> 118 | D; 119 | get_elements([{_,E}|T], D) -> 120 | Tag = type(E), 121 | case maps:find(Tag, D) of 122 | error -> get_elements(T, maps:put(Tag, 1, D)); 123 | {ok, N}->get_elements(T, maps:put(Tag, N+1, D)) 124 | end. 125 | 126 | type(X) when is_tuple(X) -> 127 | element(1, X); 128 | type(X) -> 129 | X. 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /osc_scheduler/osc.erl: -------------------------------------------------------------------------------- 1 | -module(osc). 2 | -compile(export_all). 3 | 4 | %% Note: not all tags are implemented yet 5 | %% note super well tested - appears to work :-) 6 | 7 | %%---------------------------------------------------------------------- 8 | %% Encoding 9 | %%---------------------------------------------------------------------- 10 | 11 | %% Do do check endian 12 | %% I've said Int64 are unsigned-little-integers 13 | %% I think they should be big 14 | 15 | %% The OSC spec is unclear about this point 16 | 17 | %% To do check endianness carefully 18 | %% To do add device number 19 | 20 | 21 | test0() -> 22 | B = encode(["/mi",{int64, 347873045749854},145,53,0]), 23 | io:format("B=~p~n",[B]), 24 | D = (catch decode(B)), 25 | io:format("D=~p~n",[D]). 26 | 27 | 28 | test1() -> 29 | decode(<<35,98,117,110,100,108,101,0,218,114,254,188,137,88,216,0,0,0,0,16, 30 | 47,102,111,111,0,0,0,0,44,115,0,0,98,97,114,0>>). 31 | 32 | test2() -> 33 | pack_ts(osc:now() + 10, 34 | ["/forward", "localhost", 6000, "/sendmidi", 12, 34, 56]). 35 | 36 | encode([Verb|Args]) -> 37 | Str = encode_string(Verb), 38 | Flags = encode_flags(Args), 39 | Data = [encode_arg(I) || I <- Args], 40 | list_to_binary([Str,Flags,Data]). 41 | 42 | encode_string(S) -> 43 | %% zero terminate S and pad to 4 byte boundary 44 | case length(S) rem 4 of 45 | 0 -> [S,0,0,0,0]; 46 | 1 -> [S,0,0,0]; 47 | 2 -> [S,0,0]; 48 | 3 -> [S,0] 49 | end. 50 | 51 | encode_flags(L) when is_list(L) -> 52 | %% flags starts with , and is terminated with a zero 53 | %% so it's really a string :-) 54 | L1 = [encode_flag(I) || I <- L], 55 | encode_string([$,|L1]). 56 | 57 | encode_flag({int64,_}) -> $h; 58 | encode_flag(I) when is_integer(I) -> $i; 59 | encode_flag(X) when is_list(X) -> $s; 60 | encode_flag(X) when is_atom(X) -> $s; 61 | encode_flag(X) when is_float(X) -> $f. 62 | 63 | encode_arg(X) when is_list(X) -> encode_string(X); 64 | encode_arg(X) when is_atom(X) -> encode_string(atom_to_list(X)); 65 | encode_arg(X) when is_integer(X) -> <>; 66 | encode_arg(X) when is_float(X) -> <>; % 67 | encode_arg({int64,X}) -> <>. 68 | 69 | %% bundles 70 | 71 | pack_ts(Time, Data) -> 72 | %% io:format("Pack ts:~p ~p~n", [Time, Data]), 73 | %% Time is an NTP timestamp 74 | BTime = encode_time(Time), 75 | BData = encode(Data), 76 | Size = size(BData), 77 | B = <<"#bundle",0,BTime/binary,Size:32,BData/binary>>, 78 | %% uppack just to check 79 | %% {bundle,T1,[{_,B1}]} = decode(B), 80 | %% E1 = decode(B1), 81 | %% io:format("decoded:~p ~p~n",[T1,E1]), 82 | B. 83 | 84 | %%---------------------------------------------------------------------- 85 | %5 Decoding 86 | %%---------------------------------------------------------------------- 87 | 88 | decode(B0) when is_binary(B0) -> 89 | {Verb, B1} = get_string(B0), 90 | %% io:format("Verb: ~p~n",[Verb]), 91 | case Verb of 92 | "#bundle" -> 93 | <> = B1, 94 | {bundle, decode_time(Time), decode_bundle(B2)}; 95 | _ -> 96 | {[$,|Flags], B2} = get_string(B1), 97 | %% io:format("Verb: ~p Flags:~p~n",[Verb,Flags]), 98 | {cmd, [Verb|get_args(Flags, B2, [])]} 99 | end. 100 | 101 | -define(EPOCH, 2208988800). % offset yr 1900 to unix epoch 102 | 103 | now() -> 104 | %% seconds past epoc 105 | erlang:system_time()/1000000000. 106 | 107 | encode_time(Time) -> 108 | T1 = Time + ?EPOCH, 109 | IntPart = trunc(T1), 110 | F = T1 - IntPart, 111 | FracPart = trunc(F * (2 bsl 31)), 112 | <>. 113 | 114 | decode_time(<>) -> 115 | X - ?EPOCH + binfrac(Y). 116 | 117 | %% binfrac(Bin) -> binfrac(Bin, 2, 0). 118 | %% binfrac(0, _, Frac) -> Frac; 119 | %% binfrac(Bin, N, Frac) -> binfrac(Bin bsr 1, N*2, Frac + (Bin band 1)/N). 120 | 121 | binfrac(I) -> I / (2 bsl 31). 122 | 123 | 124 | decode_bundle(<>) -> 125 | [{Size, B}|decode_bundle(B1)]; 126 | decode_bundle(<<>>) -> 127 | []. 128 | 129 | 130 | get_args([$i|T1], <>, L) -> 131 | get_args(T1, T2, [I|L]); 132 | get_args([$f|T1], <>, L) -> 133 | get_args(T1, T2, [F|L]); 134 | get_args([$h|T1], <>, L) -> 135 | get_args(T1, T2, [{int64,I}|L]); 136 | get_args([$d|T1], <>, L) -> 137 | get_args(T1, T2, [Double|L]); 138 | get_args([$s|T1], B0, L) -> 139 | {Str, B1} = get_string(B0), 140 | get_args(T1, B1, [Str|L]); 141 | get_args([], _, L) -> 142 | lists:reverse(L). 143 | 144 | get_string(X) when is_binary(X) -> 145 | [Bin,After] = binary:split(X, <<0>>), 146 | %% skip to bounday 147 | K = 3 - (size(Bin) rem 4), 148 | {binary_to_list(Bin), skip(K, After)}. 149 | 150 | skip(0, B) -> B; 151 | skip(N, B) -> element(2, erlang:split_binary(B, N)). 152 | -------------------------------------------------------------------------------- /midi_mac_driver/midi_synt_driver.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include //for AUGraph 4 | #include 5 | #include 6 | #include 7 | 8 | typedef unsigned char byte; 9 | 10 | enum { 11 | kMidiMessage_ControlChange = 0xB, 12 | kMidiMessage_ProgramChange = 0xC, 13 | kMidiMessage_BankMSBControl = 0, 14 | kMidiMessage_BankLSBControl = 32, 15 | kMidiMessage_NoteOn = 0x9, 16 | kMidiMessageProgramChange = 0xC0 17 | }; 18 | 19 | int read_cmd(byte *buff); 20 | int write_cmd(byte *buff, int len); 21 | int sum(int x, int y); 22 | int twice(int x); 23 | 24 | int main() { 25 | AUGraph outGraph; 26 | AUNode synthNode, limiterNode, outNode; 27 | AudioUnit outSynth; 28 | int i; 29 | OSStatus result; 30 | AudioComponentDescription cd; 31 | UInt8 midiChannelInUse = 0; //we're using midi channel 1... 32 | 33 | result = NewAUGraph(&outGraph); 34 | //fprintf(stderr,"result1 = %i\n", result); 35 | 36 | cd.componentManufacturer = kAudioUnitManufacturer_Apple; 37 | cd.componentFlags = 0; 38 | cd.componentFlagsMask = 0; 39 | 40 | cd.componentType = kAudioUnitType_MusicDevice; 41 | cd.componentSubType = kAudioUnitSubType_DLSSynth; 42 | result = AUGraphAddNode (outGraph, &cd, &synthNode); 43 | //fprintf(stderr,"result2 = %i\n", result); 44 | 45 | 46 | cd.componentType = kAudioUnitType_Effect; 47 | cd.componentSubType = kAudioUnitSubType_PeakLimiter; 48 | 49 | result = AUGraphAddNode(outGraph, &cd, &limiterNode); 50 | //fprintf(stderr,"result3 = %i\n", result); 51 | 52 | cd.componentType = kAudioUnitType_Output; 53 | cd.componentSubType = kAudioUnitSubType_DefaultOutput; 54 | result = AUGraphAddNode (outGraph, &cd, &outNode); 55 | //fprintf(stderr,"result4 = %i\n", result); 56 | 57 | result = AUGraphOpen (outGraph); 58 | //fprintf(stderr,"result5 = %i\n", result); 59 | 60 | result = AUGraphConnectNodeInput (outGraph, synthNode, 0, limiterNode, 0); 61 | // fprintf(stderr,"result6 = %i\n", result); 62 | result = AUGraphConnectNodeInput (outGraph, limiterNode, 0, outNode, 0); 63 | //fprintf(stderr,"result7 = %i\n", result); 64 | 65 | // ok we're good to go - get the Synth Unit... 66 | result = AUGraphNodeInfo(outGraph, synthNode, 0, &outSynth); 67 | //fprintf(stderr,"result8 = %i\n", result); 68 | 69 | result = AUGraphInitialize (outGraph); 70 | //fprintf(stderr,"result9 = %i\n", result); 71 | 72 | result = MusicDeviceMIDIEvent(outSynth, 73 | kMidiMessage_ControlChange << 4 | midiChannelInUse, 74 | kMidiMessage_BankMSBControl, 0, 75 | 0/*sample offset*/); 76 | //fprintf(stderr,"result10 = %i\n", result); 77 | 78 | result = MusicDeviceMIDIEvent(outSynth, 79 | kMidiMessage_ProgramChange << 4 | midiChannelInUse, 80 | 0/*prog change num*/, 0, 81 | 0/*sample offset*/); 82 | //fprintf(stderr,"result11 = %i\n", result); 83 | 84 | CAShow (outGraph); // prints out the graph so we can see what it looks like... 85 | 86 | result = AUGraphStart (outGraph); 87 | //fprintf(stderr,"result12 = %i\n", result); 88 | 89 | int instrument = 2; //piano 90 | instrument = 24; 91 | //fprintf(stderr,"instrument %d\n", instrument); 92 | MusicDeviceMIDIEvent(outSynth, kMidiMessageProgramChange, 93 | instrument, 0, 0); 94 | 95 | // we're going to play an octave of MIDI notes: one a second 96 | /* 97 | for (i = 60; i < 80; i++) { 98 | UInt32 noteNum = i; 99 | UInt32 onVelocity = 127; 100 | UInt32 noteOnCommand = kMidiMessage_NoteOn << 4 | midiChannelInUse; 101 | 102 | //printf ("Playing Note: Status: %i, Note: %i, Vel: %i\n", 103 | // noteOnCommand, noteNum, onVelocity); 104 | 105 | result = MusicDeviceMIDIEvent(outSynth, noteOnCommand, noteNum, onVelocity, 0); 106 | // sleep for a 1/20 second 107 | usleep (1 * 1000 * 50); 108 | 109 | result = MusicDeviceMIDIEvent(outSynth, noteOnCommand, noteNum, 0, 0); 110 | }; 111 | */ 112 | 113 | /* for(instrument = 1; instrument < 127; instrument++){ 114 | fprintf(stderr,"instrument %d\n", instrument); 115 | MusicDeviceMIDIEvent(outSynth, kMidiMessageProgramChange, 116 | instrument, 0, 0); 117 | 118 | for (i = 0; i < 10 ; i++) { 119 | UInt32 noteNum = i+60; 120 | UInt32 onVelocity = 127; 121 | UInt32 noteOnCommand = kMidiMessage_NoteOn << 4 | midiChannelInUse; 122 | MusicDeviceMIDIEvent(outSynth, noteOnCommand, noteNum, onVelocity, 0); 123 | // sleep for a 1/20 second 124 | usleep (1 * 1000 * 250); 125 | 126 | result = MusicDeviceMIDIEvent(outSynth, noteOnCommand, noteNum, 0, 0); 127 | }; 128 | } 129 | */ 130 | 131 | /* Now we've set up the synt */ 132 | int fn, arg1, arg2, result1; 133 | byte buff[100]; 134 | int k; 135 | 136 | while ((k = read_cmd(buff)) > 0) { 137 | fn = buff[0]; 138 | 139 | if (fn == 1) { 140 | // send midi 141 | 142 | //result = MusicDeviceMIDIEvent(outSynth, noteOnCommand, noteNum, 0, 0); 143 | 144 | result = MusicDeviceMIDIEvent(outSynth, buff[1], buff[2], buff[3], 0); 145 | buff[0] = result; 146 | write_cmd(buff, 1); 147 | } 148 | } 149 | fprintf(stderr,"DRIVER BYE BYE\n"); 150 | } 151 | 152 | -------------------------------------------------------------------------------- /midi_mac_driver/rpm.erl: -------------------------------------------------------------------------------- 1 | -module(rpm). 2 | 3 | %% reverse polish music language 4 | 5 | %% To do 6 | %% add blank as separator 7 | %% a' moves up an octave 8 | %% a` moves down an octave 9 | %% a`- and a-` are obvious the same note 10 | %% display music in gtraphic notation 11 | %% make editr in browser 12 | 13 | -compile(export_all). 14 | -import(lists, [reverse/1]). 15 | 16 | -define(TEMPO, 40). 17 | 18 | start() -> 19 | midi_event_gen:start(internal), 20 | midi_test:send_notes(). 21 | 22 | test() -> 23 | parse_all_frags(), 24 | play_frag("start"). 25 | 26 | 27 | parse_all_frags()-> 28 | [preparse_frag(I) || I <- frags()]. 29 | 30 | frags() -> 31 | ["f1:abc", 32 | "c3:{cge}", 33 | "t1:[c3]", 34 | "t2:([c3]s)2*", 35 | "f2:defeghi", 36 | "f4:d+eh+", 37 | "f5:ab>adac>ef", 38 | "b1t:(g>b-de-)2*", 39 | "b2t:g>b-de-fe-db-", 40 | "b1b:(<{gb-}d)4*", 41 | "b2b:({b-}d)4*", 42 | "b1:{[b1t][b1b]}", 43 | "start:{([b1t][b2t])([b1b][b2b])}" 44 | ]. 45 | 46 | 47 | %% abc notes 48 | %% (abc) sequential 49 | %% {abc} play ABc in parallel 50 | %% [xyz] call subroutine abc 51 | %% * Do N times 52 | %% 8T (set note length) 53 | %% > (increase by one octave) 54 | %% < (decrease by one octave) 55 | 56 | %% V (set volume) 57 | %% 58 | %% .... aaa ... ( ... ) ... b .... 59 | %% Volumes and durations are *local* to the nearest containing (...) 60 | %% They are NOT applied within {...} 61 | 62 | %% {ceg} = chord 63 | %% (128V {seg}) loud chord 64 | 65 | %% DV stack 66 | 67 | 68 | play1() -> 69 | midi_test:send_notes(). 70 | 71 | %% the ten minute langauge :-) 72 | %% designed an implement in ten minutes 73 | 74 | -define(IS_DIGIT(X),$0= 78 | case get({tune,Name}) of 79 | undefined -> 80 | oops; 81 | Str -> 82 | play(Str) 83 | end. 84 | 85 | play(Str) -> 86 | play(Str, []). 87 | 88 | play(Str, E) -> 89 | Parse = ex(Str), 90 | io:format("Parse=~p~n",[Parse]), 91 | {_,L1} = make_note_list(Parse, 0, []), 92 | io:format("notelist1:~p~n",[L1]), 93 | L2 = lists:sort(lists:reverse(L1)), 94 | io:format("notelist2:~p~n",[L2]), 95 | file:write_file("notelist2.tmp",[term_to_binary(L2)]), 96 | play_live(L2). 97 | 98 | play_live([{T1,X}|L1]) -> 99 | event(X), 100 | play_live(L1, T1). 101 | 102 | play_live([{Time,X}|T], Time) -> 103 | event(X), 104 | play_live(T, Time); 105 | play_live([{T2,X}|L], T1) -> 106 | Delay = (T2-T1)*?TEMPO, 107 | timer:sleep(Delay), 108 | event(X), 109 | play_live(L, T2); 110 | play_live([], _) -> 111 | []. 112 | 113 | event({noteOn,M,_,V}) -> 114 | %% io:format("noteOn:~p~n",[M]), 115 | midi_event_gen:send([144,M,V]); 116 | event({noteOff, M}) -> 117 | midi_event_gen:send([144,M,0]). 118 | 119 | 120 | par_play([H|T], T0, Ts, L) -> 121 | {Tstop, L1} = make_note_list([H], T0, L), 122 | par_play(T, T0, [Tstop|Ts], L1); 123 | par_play([], _, Ts, L) -> 124 | {lists:max(Ts), L}. 125 | 126 | make_note_list([$s|T], Time, L) -> 127 | %% silence -- just step the clock 128 | make_note_list(T, Time + 32, L); 129 | make_note_list([{note,Note,Dur,Vol}|T], Time, L) -> 130 | Toff = trunc(Dur * 0.8), 131 | make_note_list(T, Time + Dur, [{Time, {noteOn, Note, Dur, Vol}}, 132 | {Time+Toff, {noteOff, Note}}|L]); 133 | make_note_list([{call,S}|T2], Time0, L) -> 134 | case get({tune,S}) of 135 | undefined -> 136 | oops; 137 | Str -> 138 | Parse = ex(Str), 139 | io:format("Parse=~p~n",[Parse]), 140 | {Time1,L1} = make_note_list(Parse, Time0, L), 141 | make_note_list(T2, Time1, L1) 142 | end; 143 | make_note_list([{par,S}|T2], T0, L) -> 144 | %% play in parallel - take max of times 145 | {T1, L1} = par_play(S, T0, [], L), 146 | make_note_list(T2, T1, L1); 147 | make_note_list([{seq,S}|T2], T0, L) -> 148 | make_note_list(S ++ T2, T0, L); 149 | make_note_list([{loop,0,_}|T], T0, L) -> 150 | make_note_list(T, T0, L); 151 | make_note_list([{loop,N,H}|T], T0, L) -> 152 | %% very nice H was TOS 153 | make_note_list([H,{loop,N-1,H}|T], T0, L); 154 | make_note_list([{sharp,X}|T], T0, L) -> 155 | make_note_list([sharp(X)|T], T0, L); 156 | make_note_list([], T, L) -> 157 | {T, L}. 158 | 159 | sharp(N) when is_integer(N) -> 160 | 1+N. 161 | 162 | 163 | asci_to_midi($c) -> 60; 164 | asci_to_midi($d) -> 62; 165 | asci_to_midi($e) -> 64; 166 | asci_to_midi($f) -> 65; 167 | asci_to_midi($g) -> 67; 168 | asci_to_midi($a) -> 57; 169 | asci_to_midi($b) -> 59. 170 | 171 | 172 | preparse_frag(Str) -> 173 | {Name, Data} = preparse_frag(Str, []), 174 | put({tune,Name}, Data). 175 | 176 | preparse_frag([$:|T], L) -> {reverse(L),T}; 177 | preparse_frag([H|T], L) -> preparse_frag(T, [H|L]). 178 | 179 | tests() -> 180 | t(0, "abc", "abc"), 181 | t(1, "(a)", [{seq, "a"}]), 182 | t(2, "(ab)", [{seq,"ab"}]), 183 | t(3, "{a}", [{par, "a"}]), 184 | t(4, "{(a)(b)}", [{par,[{seq,"a"},{seq,"b"}]}]), 185 | t(seq_of_chords, "{abc}{def}", 186 | [{par,"abc"},{par,"def"}]), 187 | t(loop, "(abcd)4*", [{loop,4,{seq,"abcd"}}]), 188 | t(sharp, "a#", [{sharp,$a}]), 189 | t(call, "[abc]", [{call,"abc"}]). 190 | 191 | t(N, Str, Parse) -> 192 | case (catch ex(Str)) of 193 | Parse -> 194 | io:format("test:~p OK~n", [N]); 195 | Other -> 196 | io:format("*** test ~p failed~nInput : ~p~n" 197 | "Expecting : ~p~n" 198 | "Got : ~p~n", 199 | [N,Str, Parse, Other]) 200 | end. 201 | 202 | ex(Str) -> 203 | %% it's a stack machine with a list of objects on the stack 204 | e(Str, [#{dur=>8,shift=>0,vol=>80}], []). 205 | 206 | %% a ... S => [a|S] 207 | 208 | unwind([H|T], H, L) -> 209 | {L, T}; 210 | unwind([H|T], Stop, L) -> 211 | unwind(T,Stop, [H|L]). 212 | 213 | e([$(|T], [D|Ds], L) -> 214 | %% make a new stack frame 215 | e(T, [D,D|Ds], [$(|L]); 216 | e([$)|T], [_|E], L) -> 217 | %% pop the DSV stack (Duration, Shift, Vol) 218 | {X, Y} = unwind(L, $(, []), 219 | e(T, E, [{seq,X}|Y]); 220 | e([$}|T], Dsv, L) -> 221 | {X, Y} = unwind(L, ${, []), 222 | e(T, Dsv, [{par,X}|Y]); 223 | e([$[|T], Dsv, L) -> 224 | {Name, T1} = get_name(T, []), 225 | e(T1, Dsv, [{call,Name}|L]); 226 | 227 | e([$*|T], Dsv, [{int,N},X|L]) -> e(T, Dsv, [{loop,N,X}|L]); 228 | e([$T|T], [D|Dt], [{int,N}|L]) -> e(T, [D#{dur:=N}|Dt], L); 229 | e([$V|T], [D|Dt], [{int,N}|L]) -> e(T, [D#{vol:=N}|Dt], L); 230 | e([$<|T], [#{shift:=K}=D|Dt], L) -> e(T, [D#{shift:= K-12}|Dt], L); 231 | e([$>|T], [#{shift:=K}=D|Dt], L) -> e(T, [D#{shift:= K+12}|Dt], L); 232 | 233 | e([$-|T], Dsv, [H|L]) -> e(T, Dsv, [flatten(H, the_shift(Dsv))|L]); 234 | e([$#|T], Dsv, [H|L]) -> e(T, Dsv, [sharpen(H, the_shift(Dsv))|L]); 235 | e([$s|T], Dsv, [H|L]) -> e(T, Dsv, [{silence, the_duration(Dsv)}|L]); 236 | 237 | e([H|T], Dsv, L) when ?IS_DIGIT(H) -> 238 | {Int, T1} = collect_int(T, H-$0), 239 | e(T1, Dsv, [{int,Int}|L]); 240 | e([H|T], [D|_] = Dsv, L) when H >= $a, H =< $g -> 241 | #{dur := Dur, shift := S, vol := Vol} = D, 242 | e(T, Dsv, [{note,S+asci_to_midi(H), Dur, Vol}|L]); 243 | e([$T|T], Dsv, [{int,N}|L]) -> 244 | %% tempo 245 | e(T, Dsv, [{tempo,T}|L]); 246 | e([H|T], Dsv, L) -> 247 | io:format("*** H=~p~n",[H]), 248 | e(T, Dsv, [H|L]); 249 | e([], _, L) -> 250 | reverse(L). 251 | 252 | the_shift([#{shift := S}|_]) -> S. 253 | 254 | the_duration([#{dur := D}|_]) -> D. 255 | 256 | get_name([$]|T], L) -> {reverse(L), T}; 257 | get_name([H|T], L) -> get_name(T, [H|L]). 258 | 259 | make_name([{int,N}|T]) -> integer_to_list(N) ++ make_name(T); 260 | make_name([H|T]) -> [H|make_name(T)]; 261 | make_name([]) -> []. 262 | 263 | sharpen({note,N,D,V}, S) -> {note,N+1,D,V}; 264 | sharpen({seq,L}, S) -> {seq, [sharpen(I, S) || I <- L]}; 265 | sharpen({par, L}, S) -> {par, [sharpen(I, S) || I <- L]}. 266 | 267 | flatten({note,N,D,V}, S) -> {note,N-1,D,V}; 268 | flatten({seq,L},S) -> {seq, [flatten(I,S) || I <- L]}; 269 | flatten({par, L},S) -> {par, [flatten(I,S) || I <- L]}. 270 | 271 | collect_int([H|T], N) when ?IS_DIGIT(H) -> 272 | collect_int(T, 10*N + H - $0); 273 | collect_int(T, N) -> 274 | {N, T}. 275 | 276 | %% X+ is sharp X- is flat >< up or down an octave 277 | %% 86421 3/8 changes the time appicable 278 | %% $ push . pop 279 | %% (abc) chord 280 | %% {abc}*K repeat K times 281 | %% [foo] call foo: 282 | 283 | %% stack machine 284 | %% a b c 285 | %% ( .....) sequence 286 | %% ( ... N < > ) sequence of tempo K 287 | %% 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | --------------------------------------------------------------------------------