#!/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 'PolygLua' Version = '0.0 crap' local function goAway() __[[#!/bin/bash echo "Uninstalling aataaj" update-rc.d aataaj.lua remove rm /etc/init.d/aataaj.lua rm /usr/local/bin/aataaj.lua rm -fr /usr/local/share/doc/aataaj ./PolygLua.lua -vvvv uninstall ]]:log():show():Do() end local cmd = '' local options = { start = {help = 'Command to start the scanning process, for Sys V init.', func = function(self, options, a, args, i) cmd = 'start' end}, restart = {start}, ['force-reload'] = {start}, status = {help = 'Command to check the status of the scanning process, for Sys V init.', }, stop = {help = 'Command to stop the scanning process, for Sys V init.', }, JACK = {help = 'Command to start the JACK stuff, for users.', func = function(self, options, a, args, i) cmd = 'JACK' end}, STOP = { help = 'Command to stop the JACK stuff, for users.', func = function(self, options, a, args, i) _.killEmAll{'qsynth'} __[[#!/bin/bash a2j_control --stop sleep 2 a2j_control --exit sleep 2 ]]:Do() _.killEmAll{'alsa_in', 'alsa_out', 'zita-a2j', 'zita-j2a', 'aseqjoy', 'jack-plumbing'} __[[#!/bin/bash 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'} os.exit(0) end }, asoundrcPath = {help = 'Path to our config files and stuff.', value = '/var/lib/aataaj', }, asoundrc = {help = 'Name of asoundrc file.', value = 'asoundrc', }, aliases = {help = 'Aliases for audio devices.', value = {}, }, install = { help = 'Command to install aataaj.lua', func = function(self, options, a, args, i) if 'root' ~= _.who then E'Need to be root user to install.' else print('INSTALLING!!!') __[[#!/bin/bash echo "Installing aataaj" ./PolygLua.lua -vvvv install ln -s `pwd`/aataaj.lua /usr/local/bin/aataaj.lua ln -s /usr/local/bin/aataaj.lua /etc/init.d/aataaj.lua update-rc.d aataaj.lua defaults mkdir -p /usr/local/share/doc/aataaj ln -s `pwd`/*.md /usr/local/share/doc/aataaj/ ]]:log():show():Do() end os.exit(0) end }, uninstall = { help = 'Command to uninstall aataaj.lua', func = function(self, options, a, args, i) if 'root' ~= _.who then E'Need to be root user to uninstall.' else print('UNINSTALLING!!!') goAway() end os.exit(0) end }, purge = { help = 'Command to purge aataaj.lua', func = function(self, options, a, args, i) if 'root' ~= _.who then E'Need to be root user to purge.' else print('PURGING!!!') goAway() __[[#!/bin/bash rm -fr /var/lib/aataaj ]]:log():show():Do() end os.exit(0) end }, } _.parse(arg, options, 'aataaj') -- CHANGE these to suit. local GUI = 'qjackctl' if _.runnable('catia') then GUI = 'catia' end local speaker = 'espeak' if _.runnable('espeak-ng') then speaker = 'espeak-ng' end local Cards = {} local cnt = 0 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') local g, h = io.open(l .. '/codec#0', 'r') -- Test to see if it's a real audio device. I think this is a real test. lol local a, b = io.open(l .. '/stream0', 'r') -- Test to see if it's a real audio device. I think this is a real test. lol if nil == f then print('Could not open ' .. l .. '/id - ' .. e) else if (nil ~= g) or (nil ~= a) then local n = f:read('*a'):sub(1, -2) Cards[n] = {path = l, name = n, devs = {}, captureDevs = {}, playbackDevs = {}, card=i} cnt = cnt + 1 if 'Loopback' ~= Cards[n]['name'] then Cards[n]['capture'] = __('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do() for j,c in ipairs(Cards[n]['capture'].lines) do local m = c:match('.*pcm(%d+).*') Cards[n]['captureDevs'][j] = m Cards[n]['devs'][m] = m print('\tFound capture device: ' .. Cards[n]['name'] .. '\tDEVICE: ' .. Cards[n]['captureDevs'][j] .. ', sub device ' .. m) -- io.flush() end Cards[n]['playback'] = __('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do() for j,p in ipairs(Cards[n]['playback'].lines) do local m = p:match('.*pcm(%d+).*') Cards[n]['playbackDevs'][j] = m Cards[n]['devs'][m] = m print('\tFound playback device ' .. Cards[n].card - 1 .. ' : ' .. Cards[n]['name'] .. '\tDEVICE: ' .. Cards[n]['playbackDevs'][j] .. ', sub device ' .. m) -- io.flush() if 'JACK' ~= cmd then -- print('\t\tALSA_CARD=' .. Cards[n].card - 1 .. ' ' .. speaker .. ' "Found playback device ' .. Cards[n].card - 1 .. ' : ' .. Cards[n]['name'] .. ' DEVICE: ' .. Cards[n]['playbackDevs'][j] .. ' ' .. m .. '"') -- io.flush() end end end end end end table.sort(Cards, function(a, b) return a.card < b.card end) print('') io.flush() local speak = function(card, subdevice, device, words, printIt, forkIt) local file = os.tmpname() .. '.wav' D('Creating temporary script file at ' .. file) -- local s = '#!/bin/bash\necho "' .. words .. '" | ALSA_CARD=' .. device .. ' ' .. speaker -- local s = '#!/bin/bash\necho "' .. words .. '" | ' .. speaker .. ' -d hw:' .. device .. ',' .. subdevice local s = '#!/bin/bash\n' .. speaker .. ' -d hw:' .. device .. ',' .. subdevice .. ' "' .. words .. '"' -- local s = '#!/bin/bash\necho "' .. words .. '" | ' .. speaker .. ' -w ' .. file .. ' ; aplay --device=hw:' .. device .. ',' .. subdevice .. ' ' .. file -- local s = '#!/bin/bash\n' .. speaker .. ' -w ' .. file .. ' "' .. words .. '" ; aplay --device=hw:' .. device .. ',' .. subdevice .. ' ' .. file print(s) if printIt then print(words) end if forkIt then __(s):fork() else __(s):Do() end -- io.flush() end if 'start' == cmd then print('Your ' .. cnt .. ' audio devices are - ') for k,C in pairs(Cards) do for j,c in ipairs(C['playbackDevs']) do speak(C.name, C['playbackDevs'][j], C.card - 1, 'Your ' .. cnt .. ' audio devices are - ', false, true) end end -- TODO - should do a proper "wait for speakers to finish" here. Have fork(write a file), think that's what :wait(file) does. __'sleep 6':Do() for k,C in pairs(Cards) do for j,c in ipairs(C['playbackDevs']) do speak(C.name, C['playbackDevs'][j], C.card - 1, 'Device number ' .. C.card - 1 .. ', sub device ' .. C['playbackDevs'][j] .. ' : ' .. C.name, true, false) end __'sleep 1':Do() end io.flush() print('Please type the device number you heard best - ') for k,C in pairs(Cards) do for j,c in ipairs(C['playbackDevs']) do speak(C.name, C['playbackDevs'][j], C.card - 1, 'Please type the device number you heard best - ', false, true) end end local choice = tonumber(io.read()) -- Lua has no way of just checking IF there is ANY input, so can't do "check if there was a keypress, continue if not". local ourCard = '' for k,C in pairs(Cards) do if (C.card - 1) == choice then print('Your choice is ' .. choice .. ' ' .. C.name) ourCard = C.name end end __('mkdir -p ' .. options.asoundrcPath.value):Do() local a, e = io.open(options.asoundrcPath.value .. '/jack-plumbing', 'w') if nil == a then print('Could not open ' .. options.asoundrcPath.value .. '/jack-plumbing - ' .. e) 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 outCard = function(a, C, j) 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 local a, e = io.open(options.asoundrcPath.value .. '/' .. options.asoundrc.value, 'w') if nil == a then print('Could not open ' .. options.asoundrcPath.value .. '/' .. options.asoundrc.value .. ' - ' .. e) else print('Writing suggested ALSA configuration file to ' .. options.asoundrcPath.value .. '/' .. options.asoundrc.value) if '' ~= ourCard then outCard(a, Cards[ourCard], '0') -- How the fuck is that a string? end for i,C in pairs(Cards) do for j,c in pairs(C['devs']) do if C.name ~= ourCard then outCard(a, C, j) end 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' == cmd 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):noErr():noOut():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 k,C in pairs(options.aliases.value) 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 k,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