#!/usr/bin/env luajit --[[ This block is what system V LSB expects. ### BEGIN INIT INFO # Provides: aataaj # Required-Start: $local_fs # Required-Stop: # X-Start-Before: alsa-utils espeakup # Default-Start: S # Default-Stop: # Short-Description: Scan for audio devices. # Description: Scan for all audio devices and produce an # asound.conf include file fragment. ### END INIT INFO ]] local _ = require '_' local D = _.D local I = _.I local T = _.T local W = _.W local E = _.E local C = _.C local __ = _._ local Help = [[ This is part of the AllAudioToALSAandJACK project, aataaj for short, pronounced like "attach". The purpose is to scan for all ALSA / asound audio devices, and hook them all up to ALSA and JACK. Then it starts up JACK, and hooks up any joysticks it finds as MIDI controllers. So any ALSA application gets routed through JACK. This is very rough for now, only just started writing it. The stop command is particularly crude and violent, lots of pkill. Since it isn't a package yet, some setup is needed. The packages you need installed are - luajit jackd2 jack-tools for jack-plumbing, but other patch persistance methods could be used. a2jmidid zita-ajbridge aseqjoy qjackctl can be used as a visual patchbay, though I prefer catia from the KXStudio repos. You need to have the snd-aloop kernel module loaded. The aataaj.lua script should be run at boot time, put it and _.lua into /etc/init.d/ and activate it with - update-rc.d aataaj.lua defaults Note that _.lua might need to be in /usr/local/share/lua/5.1/ "aataaj JACK" should be called on user login. Probably don't need to run "aataaj stop" on user logout. It starts up JACK and friends, and creates JACK devices for all the things "aataaj start" found. It creates the cloop and ploop devices that catch everything ALSA does. Then creates MIDI devices for all your joysticks. Alas ~/.asoundrc doesn't understand ~ or $HOME, or even "try the current directory" it seems. So you have to hard code the path. Make sure your ~/.asoundrc or /etc/asoundrc includes something like this - "aataaj stop" closes down everything "aataaj JACK" started up. TODO - Leave it running, and hotplug ALSA / asound audio devices. a2jmidid takes care of hotplugging MIDI devices. Though I think I still need to deal with hotplugged joysticks. NOTE - Seems both ALSA and JACK are per user. So you need to run "aataaj JACK" for each user. ]] local args = {...} if 0 ~= #args then -- for i,a in pairs(args) do -- print('Argument ' .. i .. ' = ' .. a) -- end if 'start' == args[1] then elseif 'stop' == args[1] then _.killEmAll{'qsynth'} __[[ a2j_control --stop sleep 2 a2j_control --exit sleep 2 ]]:Do() _.killEmAll{'alsa_in', 'alsa_out', 'zita-a2j', 'zita-j2a', 'aseqjoy', 'jack-plumbing'} __[[ sleep 2 jack_control stop sleep 2 jack_control exit sleep 2 ]]:Do() _.killEmAll{'jmcore', 'qjackctl'} -- Catia is python, and no easy way to kill it. -- Also it keeps jackdbus alive, no matter how hard you kill it. __"pkill -TERM -u $USER -f catia":Do() __"sleep 2":Do() _.killEmAll{'jackdbus', 'a2jmidid'} return(0) elseif 'JACK' == args[1] then elseif 'restart' == args[1] then args[1] = 'start' elseif 'force-reload' == args[1] then args[1] = 'start' elseif 'status' == args[1] then return(0) elseif 'help' == args[1] then print(Help) return(0) elseif '--help' == args[1] then print(Help) return(0) else print("Usage: /etc/init.d/aataaj.lua {start|stop|restart|force-reload|status}") return(1) end else print("Usage: /etc/init.d/aataaj.lua {help|start|stop|restart|force-reload|status|JACK}") return(1) end -- CHANGE these to suit. local asoundrcPath = '/var/lib/aataaj' local asoundrc = 'asoundrc' local GUI = 'qjackctl' if _.runnable('catia') then GUI = 'catia' end local alias = { -- {name='Screen', dev='HDMI9'}, } local speaker = 'espeak' if _.runnable('espeak-ng') then speaker = 'espeak-ng' end local Cards = {} print('Scanning for audio devices.') local cards = __'ls -d1 /proc/asound/card[0-9]*':noErr():Do() for i,l in ipairs(cards.lines) do local f, e = io.open(l .. '/id', "r") if nil == f then print("Could not open " .. l .. '/id') else Cards[l] = {path = l, name = f:read("*a"):sub(1, -2), devs = {}, captureDevs = {}, playbackDevs = {}} if "Loopback" ~= Cards[l]['name'] then Cards[l]['capture'] = __('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do() for j,c in ipairs(Cards[l]['capture'].lines) do local n = c:match(".*pcm(%d+).*") Cards[l]['captureDevs'][j] = n Cards[l]['devs'][n] = n print("\tFound capture device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['captureDevs'][j] .. ' ' .. n) end Cards[l]['playback'] = __('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do() for j,p in ipairs(Cards[l]['playback'].lines) do local n = p:match(".*pcm(%d+).*") Cards[l]['playbackDevs'][j] = n Cards[l]['devs'][n] = n print("\tFound playback device " .. i - 1 .. " : " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n) if 'JACK' ~= args[1] then print('\t\tALSA_CARD=' .. i - 1 .. ' ' .. speaker .. ' "Found playback device ' .. i - 1 .. ' : ' .. Cards[l]['name'] .. ' DEVICE: ' .. Cards[l]['playbackDevs'][j] .. ' ' .. n .. '"') __('ALSA_CARD=' .. i - 1 .. ' ' .. speaker .. ' "Found playback device ' .. i - 1 .. ' : ' .. Cards[l]['name'] .. ' DEVICE: ' .. Cards[l]['playbackDevs'][j] .. ' ' .. n .. '"'):noErr():Do() __'sleep 1':Do() end end end end end if 'start' == args[1] then __('mkdir -p ' .. asoundrcPath):Do() local a, e = io.open(asoundrcPath .. '/jack-plumbing', "w") if nil == a then print("Could not open " .. asoundrcPath .. '/jack-plumbing') else a:write([[ (connect "system:capture_1" "ploop:playback_1") (connect "system:capture_2" "ploop:playback_2") (connect "cloop:capture_1" "system:playback_1") (connect "cloop:capture_2" "system:playback_2") (connect "cloop:capture_1" "(.*)-in:playback_1") (connect "cloop:capture_2" "(.*)-in:playback_2") (connect "qsynth:left" "(.*)-in:playback_1") (connect "qsynth:right" "(.*)-in:playback_2") ]]) a:close() end local a, e = io.open(asoundrcPath .. '/' .. asoundrc, "w") if nil == a then print("Could not open " .. asoundrcPath .. '/' .. asoundrc) else for i,C in pairs(Cards) do for j,c in pairs(C['devs']) do a:write("pcm." .. C['name'] .. j .. " {\n") a:write(" type hw\n") a:write(" card " .. C['name'] .. "\n") a:write(" device " .. C['devs'][j] .. "\n") a:write("}\n") a:write("ctl." .. C['name'] .. j .. " {\n") a:write(" type hw\n") a:write(" card " .. C['name'] .. "\n") a:write(" device " .. C['devs'][j] .. "\n") a:write("}\n\n") end end a:write([[ ################################################################################################################################# # The complex way - https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge # More custom version, but it didn't work for me. # hardware 0,0 : used for ALSA playback #pcm.loophw00 { # type hw # card Loopback # device 0 # subdevice 0 # format S32_LE # rate 48000 #} # playback PCM device: using loopback subdevice 0,0 # Don't use a buffer size that is too small. Some apps # won't like it and it will sound crappy #pcm.amix { # type dmix # ipc_key 219345 # slave { # pcm loophw00 ## period_size 4096 ## periods 2 # } #} # software volume #pcm.asoftvol { # type softvol # slave.pcm "amix" # control { name PCM } # min_dB -51.0 # max_dB 0.0 #} # for jack alsa_in: looped-back signal at other ends #pcm.cloop { # type hw # card Loopback # device 1 # subdevice 0 # format S32_LE # rate 48000 #} # hardware 0,1 : used for ALSA capture #pcm.loophw01 { # type hw # card Loopback # device 0 # subdevice 1 # format S32_LE # rate 48000 #} # for jack alsa_out: looped-back signal at other end #pcm.ploop { # type hw # card Loopback # device 1 # subdevice 1 # format S32_LE # rate 48000 #} # duplex device combining our PCM devices defined above #pcm.aduplex { # type asym # playback.pcm "asoftvol" # capture.pcm "loophw01" #} # default device #pcm.!default { # type plug # slave.pcm aduplex # hint { # show on # description "Duplex Loopback" # } #} # Generic method seems to work better. # playback PCM device: using loopback subdevice 0,0 pcm.amix { type dmix ipc_key 219345 slave.pcm "hw:Loopback,0,0" } # capture PCM device: using loopback subdevice 0,1 pcm.asnoop { type dsnoop ipc_key 219346 slave.pcm "hw:Loopback,0,1" } # duplex device combining our PCM devices defined above pcm.aduplex { type asym playback.pcm "amix" capture.pcm "asnoop" } # ------------------------------------------------------ # for jack alsa_in and alsa_out: looped-back signal at other ends pcm.ploop { type plug slave.pcm "hw:Loopback,1,1" } pcm.cloop { type dsnoop ipc_key 219348 slave.pcm "hw:Loopback,1,0" } # ------------------------------------------------------ # default device pcm.!default { type plug slave.pcm "aduplex" } ]]) a:close() end elseif 'JACK' == args[1] then print('') print("Start up JACK and friends.") print("jack_control") __[[jack_control start jack_control ds alsa]]:Do() --jack_control dps device hw:RIG,0 while 0 ~= __'jack_control status':Do().status do print("Waiting for JACK") __'sleep 1':Do() end if nil ~= GUI then print(GUI) __(GUI):forkOnce() end if _.runnable'jack-plumbing' then print("jack-plumbing") __'jack-plumbing -o /var/lib/aataaj/jack-plumbing 2>/dev/null':fork() end if _.runnable'a2j_control' then -- Bridge ALSA ports to JACK ports. Only handles MIDI. -- MIDI via a2jmidid. The --ehw enables hardware ports as well, equal to using the seq MIDI drivare according to https://freeshell.de/~murks/posts/ALSA_and_JACK_MIDI_explained_(by_a_dummy_for_dummies)/ --a2j_control actually starts a2jmidid. ----a2jmidid -e -u & -- I think the jack_control start and my current alsa config means a2jmidid gets started anyway. But seem to need this bit to get the joystick covered. print("a2j_control") __'a2j_control --ehw && a2j_control --start':Do() -- print("sleep 2") -- __('sleep 2'):Do() print("") end local AIN = "alsa_in" if _.runnable'zita-a2j' then AIN = 'zita-a2j' end local AOUT = "alsa_out" if _.runnable'zita-j2a' then AOUT = 'zita-j2a' end print("Basic ALSA sound devices converted to JACK.") for i,C in pairs(alias) do print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['dev']) __(AOUT .. ' -j ' .. C['name'] .. ' -d ' .. C['dev']):fork() end print("HW playback: cloop\tDEVICE: cloop") -- No idea why, cloop wont work with zita-a2j. __'alsa_in -j cloop -d cloop':fork() --__[[sleep 1 -- jack_connect cloop:capture_1 system:playback_1o() -- jack_connect cloop:capture_2 system:playback_2]]:Do() print("HW playback: ploop\tDEVICE: ploop") __'alsa_out -j ploop -d ploop':fork() --__[[sleep 1 -- jack_connect system:capture_1 ploop:playback_1 -- jack_connect system:capture_2 ploop:playback_2]]:Do() print("") print("Rest of ALSA sound devices converted to JACK.") for i,C in pairs(Cards) do for j,c in ipairs(C['playbackDevs']) do print("HW playback: " .. C['name'] .. "\tDEVICE: " .. C['playbackDevs'][j]) __(AOUT .. ' -j ' .. C['name'] .. "_" .. C['playbackDevs'][j] .. '-in -d ' .. C['name'] .. C['playbackDevs'][j]):fork() -- __'sleep 1':Do() -- __('jack_connect cloop:capture_1 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_1'):Do() -- __('jack_connect cloop:capture_2 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_2'):Do() end for j,c in ipairs(C['captureDevs']) do print("HW capture: " .. C['name'] .. "\tDEVICE: " .. C['captureDevs'][j]) __(AIN .. ' -j ' .. C['name'] .. "_" .. C['captureDevs'][j] .. '-out -d ' .. C['name'] .. C['captureDevs'][j]):fork() end end print("") if _.runnable('aseqjoy') then print("Scanning for joysticks.") local sticks = __'ls -1 /dev/input/js[0-9]*':noErr():Do() for i,l in ipairs(sticks.lines) do print("aseqjoy " .. l) -- Buttons switch to that numbered MIDI channel, defaults to 1. -- Axis are mapped to MIDI controllers 10 - 15 -- -r means to use high resolution MIDI values. __('aseqjoy -d ' .. l:sub(-1,-1) .. ' -r'):fork() end print("") end if _.runnable('jack-plumbing') then print('Stop our jack-plumbing, eventually.') __'sleep 4':Do() _.killEmAll{"jack-plumbing"} end if _.runnable('~/.aataaj_JACK.lua') then print('Running users aataaj_JACK.lua') __'~/.aataaj_JACK.lua':Do() end end