%% Jack Daemon wrapper. Note that this insists on managing the %% daemon. I.e. it is "framework-y". %% Some breadcrumbs: %% %% - In exo, this runs in the main supervisor. Only jack_daemon is %% stared, which then starts other functionality. %% %% - Exo also starts midi_raw, which is independent of all jack code. %% Its purpose is to be a hub for midi devices that are not %% connected to jack. %% %% - While jack daemon is starting up, the daemon's stdout is parsed %% and for each 'added' line, handle_connect/4 is called, which... %% %% - ... lazy starts jack_control, jack_midi and jack_audio clients. %% %% - jack_control handles port/alias events %% %% - jack_midi is a C port and Erlang wrapper that does some "data %% plane" midi operations (e.g. clock generation, sequencer, sysex) %% inside the C application, and furthermore bridges the Jack MIDI %% world and the Erlang message world. Some thought has been put in %% here, so have a look at jack_midi.c %% %% - jack_audio is currently a dummy memcpy audio sink %% Notes %% %% - This evolved in a very ad-hoc way. I currently do not have the %% time to redesign it. I guess it is ok, just that startup is a %% little messy. %% %% - It is probably possible to remove the stdout parsing, but at this %% time it is still used to ensure the jack clients are only started %% once the daemon is up. Once control deamon is up, events are %% handled that way. -module(jack_daemon). -export([start_link/1, handle/2, studio_elf/0, start_client/2, system_port/3]). %% Wrap the daemon and listen on its stdout as a simple way to get %% MIDI port connect notifications. %% Once midi port aliases are known, connect them to a specified port %% number on the jack client. start_link(Init = #{ hubs := _}) -> {ok, serv:start( {handler, fun() -> log:set_info_name(?MODULE), %% timer:send_after(2000, start), self() ! start, Init end, fun ?MODULE:handle/2})}. handle(start, State) -> SH = code:priv_dir(studio) ++ "/start_jackd.sh", tools:info("jackd_open: ~s~n",[SH]), Opts = [{line,1024}, binary, use_stdio, exit_status], Port = open_port({spawn, SH}, Opts), maps:put(port, Port, State); handle({Port, {data, Data}}, #{port := Port} = State) -> case Data of {eol, Line} -> log:info("~s~n", [Line]), handle({line, Line}, State); _ -> log:info("UNEXPECTED: Data=~p~n", [Data]) end; handle({Port, {exit_status, _}}=Msg, State = #{ port := Port }) -> log:info("WARNING: ~p~n", [Msg]), timer:send_after(2000, start), maps:remove(port, State); %% This requires some explanation. jackd will emit "scan:" lines such %% as is printed out above the regexp below. Note that the numbering %% scheme is not likely to be stable across restarts, so we use only %% the human readable name part. handle({line, <<"scan: ", Rest/binary>>=_Line}, State) -> %% tools:info("~s~n",[_Line]), {match,[_|[Action,_HwAddr,Dir,Addr,Name]]} = re:run( Rest, %% %% added port hw:1,0,0 in-hw-1-0-0-M-Audio-Delta-1010-MIDI <<"(\\S+) port (\\S+) (\\S+)\\-(hw\\-\\d+\\-\\d+\\-\\d+)\\-(\\S+)\n*">>, [{capture,all,binary}]), PortAlias = <