From 140e45bc021d6c868a366a11f667b026b74607d3 Mon Sep 17 00:00:00 2001
From: dvs1
Date: Thu, 3 Oct 2024 17:29:25 +1000
Subject: First commit.
---
.asoundrc | 1 +
README.md | 50 ++++++++++
jackoffall | 146 +++++++++++++++++++++++++++
jackonall | 237 +++++++++++++++++++++++++++++++++++++++++++
jackscanall | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 760 insertions(+)
create mode 100644 .asoundrc
create mode 100644 README.md
create mode 100755 jackoffall
create mode 100755 jackonall
create mode 100755 jackscanall
diff --git a/.asoundrc b/.asoundrc
new file mode 100644
index 0000000..7321f6c
--- /dev/null
+++ b/.asoundrc
@@ -0,0 +1 @@
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..86dec60
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+This is the JackOnAllDevices project, JOAD for short.
+
+The purpose is to scan for all ALSA / asound audio devices, and hook them
+all up to 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. jackoffall is
+particularly crude and violent, lots of killall.
+
+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 jackscanall
+script should be run at boot time, put it into /etc/boot.d/. jackonall
+should be called on user login. Probably don't need to run jackoffall on
+user logout.
+
+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 includes something like this (an example is included) -
+
+
+
+jackscanall scans for your sound devices and creates
+/var/lib/JOAD/asoundrc. Run jackscanall once as root to create that
+file, and each time you need to change your devices.
+
+jackonall starts up JACK and friends, and creates JACK devices for all
+the things jackscanall found. It creates the cloop and ploop devices
+that catch everything ALSA does. Then creates MIDI devices for all your
+joysticks.
+
+jackoffall closes down everything jackonall started up.
+
+NOTE - Seems both ALSA and JACK are per user. So you need to run
+jackonall and jackoffall for each user.
+
+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.
diff --git a/jackoffall b/jackoffall
new file mode 100755
index 0000000..6c7cec2
--- /dev/null
+++ b/jackoffall
@@ -0,0 +1,146 @@
+#!/usr/bin/env luajit
+
+
+local APT = {}
+
+
+APT.readCmd = function(cmd)
+ local result = {}
+ local output = io.popen(cmd)
+ if nil ~= output then
+ for l in output:lines() do
+ table.insert(result, l)
+ end
+ end
+ return result
+end
+
+
+APT.exe = function(c)
+ local exe = {status = 0, result = '', log = true, cmd = c .. ' ', command = c}
+
+ function exe:log()
+ self.log = true
+ return self
+ end
+ function exe:Nice(c)
+ if nil == c then
+ self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
+ else
+ self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
+ end
+ return self
+ end
+ function exe:timeout(c)
+ -- timeout returns a status of - command status if --preserve-status; "128+9" (actually 137) if --kill-after ends up being done; 124 if it had to TERM; command status if all went well.
+ -- --kill-after means "send KILL after TERM fails.
+ if nil == c then
+ self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
+ else
+ self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
+ end
+ return self
+ end
+ function exe:also(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. ';' .. c .. ' '
+ return self
+ end
+ function exe:And(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. '&&' .. c .. ' '
+ return self
+ end
+ function exe:Or(c)
+ if nil == c then c = '' end
+ self.cmd = self.cmd .. '|| ' .. c .. ' '
+ return self
+ end
+ function exe:noErr()
+ self.cmd = self.cmd .. '2>/dev/null '
+ return self
+ end
+ function exe:wait(w)
+ self.cmd = self.cmd .. '&& touch ' .. w .. ' '
+ return self
+ end
+ function exe:Do()
+ --[[ "The condition expression of a control structure can return any
+ value. Both false and nil are considered false. All values different
+ from nil and false are considered true (in particular, the number 0
+ and the empty string are also true)."
+ says the docs, I beg to differ.]]
+ if true == self.log then D(" executing - " .. self.cmd .. "
") end
+ --[[ Damn os.execute()
+ Lua 5.1 says it returns "a status code, which is system-dependent"
+ Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
+ I'm getting 7168 or 0. No idea what the fuck that is.
+ local ok, rslt, status = os.execute(s)
+ ]]
+ local f = APT.readCmd(self.cmd, 'r')
+ -- The last line will be the command's returned status, collect everything else in result.
+ self.status = '' -- Otherwise the result starts with 0.
+ self.result = '\n'
+ for i,l in ipairs(f) do
+ self.result = self.result .. l .. "\n"
+ end
+ f = APT.readCmd('echo "$?"', 'r')
+ for i,l in ipairs(f) do
+ self.status = tonumber(l)
+ if (137 == self.status) or (124 == self.status) then
+ print("timeout killed " .. self.status .. ' ' .. self.command)
+ E("timeout killed " .. self.status .. ' ' .. self.command)
+ elseif (0 ~= self.status) then
+ print("status |" .. self.status .. '| ' .. self.command)
+ E("status |" .. self.status .. '| ' .. self.command)
+ end
+ end
+ return self
+ end
+ function exe:fork(host)
+ if nil ~= host then self.cmd = self.cmd .. '; r=$?; if [ $r -ge 124 ]; then echo "$r ' .. host .. ' failed forked command ' .. string.gsub(self.cmd, '"', "'") .. '"; fi' end
+ self.cmd = '{ ' .. self.cmd .. '; } &'
+ if true == self.log then D(" forking - " .. self.cmd .. "
") end
+ os.execute(self.cmd)
+ return self
+ end
+ return exe
+end
+
+
+APT.exe("a2j_control --stop"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("a2j_control --exit"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("killall -TERM alsa_out"):Do()
+APT.exe("killall -TERM alsa_in"):Do()
+APT.exe("killall -TERM zita-a2j"):Do()
+APT.exe("killall -TERM zita-j2a"):Do()
+APT.exe("killall -TERM aseqjoy"):Do()
+APT.exe("killall -TERM jack-plumbing"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("jack_control stop"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("jack_control exit"):Do()
+APT.exe("sleep 2"):Do()
+--APT.exe("a2j_control --stop; a2j_control --exit"):Do()
+--APT.exe("sleep 2"):Do()
+APT.exe("killall -TERM jmcore"):Do()
+APT.exe("pkill -TERM jackdbus; pkill -TERM a2jmidid"):Do()
+APT.exe("killall -TERM a2jmidid"):Do()
+APT.exe("killall -KILL jackdbus"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("killall -KILL a2jmidid"):Do()
+APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("killall -KILL a2jmidid"):Do()
+APT.exe("killall -KILL jackdbus"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("killall -KILL a2jmidid"):Do()
+APT.exe("killall -KILL jackdbus"):Do()
+APT.exe("sleep 2"):Do()
+APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do()
+APT.exe("killall -TERM qjackctl"):Do()
+
+-- Catia is python, and no easy way to kill it.
+APT.exe("ps auxw | grep python"):Do()
diff --git a/jackonall b/jackonall
new file mode 100755
index 0000000..e786188
--- /dev/null
+++ b/jackonall
@@ -0,0 +1,237 @@
+#!/usr/bin/env luajit
+
+--[[
+This is part of the JackOnAllDevices project, JOAD for short.
+
+NOTE - Seems both ALSA and JACK are per user.
+
+]]
+
+
+-- CHANGE these to suit.
+--local GUI = 'qjackctl'
+local GUI = 'catia'
+local alias = {
+ {name='Screen', dev='HDMI9'},
+ }
+
+
+-- This APT stuff was copied from apt-panopticon.
+local APT = {}
+
+APT.readCmd = function(cmd)
+ local result = {}
+ local output = io.popen(cmd)
+ if nil ~= output then
+ for l in output:lines() do
+ table.insert(result, l)
+ end
+ end
+ return result
+end
+
+
+APT.exe = function(c)
+ local exe = {status = 0, result = '', lines = {}, log = true, cmd = c .. ' ', command = c}
+
+ function exe:log()
+ self.log = true
+ return self
+ end
+ function exe:Nice(c)
+ if nil == c then
+ self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
+ else
+ self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
+ end
+ return self
+ end
+ function exe:timeout(c)
+ -- timeout returns a status of - command status if --preserve-status; "128+9" (actually 137) if --kill-after ends up being done; 124 if it had to TERM; command status if all went well.
+ -- --kill-after means "send KILL after TERM fails.
+ if nil == c then
+ self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
+ else
+ self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
+ end
+ return self
+ end
+ function exe:also(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. ';' .. c .. ' '
+ return self
+ end
+ function exe:And(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. '&&' .. c .. ' '
+ return self
+ end
+ function exe:Or(c)
+ if nil == c then c = '' end
+ self.cmd = self.cmd .. '|| ' .. c .. ' '
+ return self
+ end
+ function exe:noErr()
+ self.cmd = self.cmd .. '2>/dev/null '
+ return self
+ end
+ function exe:wait(w)
+ self.cmd = self.cmd .. '&& touch ' .. w .. ' '
+ return self
+ end
+ function exe:Do()
+ --[[ "The condition expression of a control structure can return any
+ value. Both false and nil are considered false. All values different
+ from nil and false are considered true (in particular, the number 0
+ and the empty string are also true)."
+ says the docs, I beg to differ.]]
+ if true == self.log then D(" executing - " .. self.cmd .. "
") end
+ --[[ Damn os.execute()
+ Lua 5.1 says it returns "a status code, which is system-dependent"
+ Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
+ I'm getting 7168 or 0. No idea what the fuck that is.
+ local ok, rslt, status = os.execute(s)
+ ]]
+ local f = APT.readCmd(self.cmd, 'r')
+ -- The last line will be the command's returned status, collect everything else in result.
+ self.status = '' -- Otherwise the result starts with 0.
+ self.result = '\n'
+ self.lines = f
+ for i,l in ipairs(f) do
+ self.result = self.result .. l .. "\n"
+ end
+ f = APT.readCmd('echo "$?"', 'r')
+ for i,l in ipairs(f) do
+ self.status = tonumber(l)
+ if (137 == self.status) or (124 == self.status) then
+ print("timeout killed " .. self.status .. ' ' .. self.command)
+ E("timeout killed " .. self.status .. ' ' .. self.command)
+ elseif (0 ~= self.status) then
+ print("status |" .. self.status .. '| ' .. self.command)
+ E("status |" .. self.status .. '| ' .. self.command)
+ end
+ end
+ return self
+ end
+ function exe:fork(host)
+ if nil ~= host then self.cmd = self.cmd .. '; r=$?; if [ $r -ge 124 ]; then echo "$r ' .. host .. ' failed forked command ' .. string.gsub(self.cmd, '"', "'") .. '"; fi' end
+ self.cmd = '{ ' .. self.cmd .. '; } &'
+ if true == self.log then D(" forking - " .. self.cmd .. "
") end
+ os.execute(self.cmd)
+ return self
+ end
+ return exe
+end
+
+
+
+local Cards = {}
+
+print('Scanning for audio devices.')
+local cards = APT.exe('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'] = APT.exe('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'] = APT.exe('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: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n)
+ end
+ end
+ end
+end
+
+
+print('')
+print("Start up JACK and friends.")
+print("jack_control")
+APT.exe('jack_control start'):Do()
+APT.exe('jack_control ds alsa'):Do()
+--jack_control dps device hw:RIG,0
+print("sleep 5")
+APT.exe('sleep 5'):Do()
+if nil ~= GUI then
+ print(GUI)
+ APT.exe(GUI):fork()
+end
+print("jack-plumbing")
+APT.exe('jack-plumbing -q 2>/dev/null'):fork()
+-- 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")
+APT.exe('a2j_control --ehw && a2j_control --start'):Do()
+print("sleep 2")
+APT.exe('sleep 2'):Do()
+print("")
+
+
+--local AIN = "alsa_in"
+local AIN = "zita-a2j"
+--local AOUT = "alsa_out"
+local AOUT = "zita-j2a"
+
+print("Basic ALSA sound devices converted to JACK.")
+for i,C in pairs(alias) do
+ print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['dev'])
+ APT.exe('alsa_out -j ' .. C['name'] .. ' -d ' .. C['dev']):fork()
+ APT.exe('sleep 1'):Do()
+end
+print("HW playback: cloop\tDEVICE: cloop")
+-- No idea why, cloop wont work with zita-a2j.
+APT.exe('alsa_in -j cloop -d cloop'):fork()
+APT.exe('sleep 1'):Do()
+APT.exe('jack_connect cloop:capture_1 system:playback_1'):Do()
+APT.exe('jack_connect cloop:capture_2 system:playback_2'):Do()
+print("HW playback: ploop\tDEVICE: ploop")
+APT.exe('alsa_out -j ploop -d ploop'):fork()
+APT.exe('sleep 1'):Do()
+APT.exe('jack_connect system:capture_1 ploop:playback_1'):Do()
+APT.exe('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])
+ APT.exe(AOUT .. ' -j ' .. C['name'] .. "_" .. C['playbackDevs'][j] .. '-in -d ' .. C['name'] .. C['playbackDevs'][j]):fork()
+-- APT.exe('sleep 1'):Do()
+ end
+ for j,c in ipairs(C['captureDevs']) do
+ print("HW capture: " .. C['name'] .. "\tDEVICE: " .. C['captureDevs'][j])
+ APT.exe(AIN .. ' -j ' .. C['name'] .. "_" .. C['captureDevs'][j] .. '-out -d ' .. C['name'] .. C['captureDevs'][j]):fork()
+-- APT.exe('sleep 1'):Do()
+ end
+end
+print("")
+
+print("Scanning for joysticks.")
+local sticks = APT.exe('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.
+ APT.exe('aseqjoy -d ' .. l:sub(-1,-1) .. ' -r'):fork()
+-- print("sleep 1")
+-- APT.exe('sleep 1'):Do()
+end
+print("")
+
+
+
diff --git a/jackscanall b/jackscanall
new file mode 100755
index 0000000..24d87df
--- /dev/null
+++ b/jackscanall
@@ -0,0 +1,326 @@
+#!/usr/bin/env luajit
+
+--[[
+This is part of the JackOnAllDevices project, JOAD for short.
+
+The purpose is to scan for all ALSA / asound audio devices, and hook them
+all up to JACK. Then it starts up JACK, and hooks up any joysticks it
+finds as MIDI controllers.
+
+
+Alas ~/.asoundrc doesn't understand ~ or $HOME, or even "try the current
+directory" it seems. So you have to hard cade the path. Make sure your
+~/.asoundrc or /etc/asoundrc includes something like this -
+
+
+
+
+Run this once as root to create that file, and each time you need to
+change your devices.
+
+
+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.
+
+]]
+
+
+-- CHANGE these to suit.
+local asoundrcPath = '/var/lib/JOAD'
+local asoundrc = 'asoundrc'
+
+
+
+-- This APT stuff was copied from apt-panopticon.
+local APT = {}
+
+APT.readCmd = function(cmd)
+ local result = {}
+ local output = io.popen(cmd)
+ if nil ~= output then
+ for l in output:lines() do
+ table.insert(result, l)
+ end
+ end
+ return result
+end
+
+
+APT.exe = function(c)
+ local exe = {status = 0, result = '', lines = {}, log = true, cmd = c .. ' ', command = c}
+
+ function exe:log()
+ self.log = true
+ return self
+ end
+ function exe:Nice(c)
+ if nil == c then
+ self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
+ else
+ self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
+ end
+ return self
+ end
+ function exe:timeout(c)
+ -- timeout returns a status of - command status if --preserve-status; "128+9" (actually 137) if --kill-after ends up being done; 124 if it had to TERM; command status if all went well.
+ -- --kill-after means "send KILL after TERM fails.
+ if nil == c then
+ self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
+ else
+ self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
+ end
+ return self
+ end
+ function exe:also(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. ';' .. c .. ' '
+ return self
+ end
+ function exe:And(c)
+ if nil == c then c = '' else c = ' ' .. c end
+ self.cmd = self.cmd .. '&&' .. c .. ' '
+ return self
+ end
+ function exe:Or(c)
+ if nil == c then c = '' end
+ self.cmd = self.cmd .. '|| ' .. c .. ' '
+ return self
+ end
+ function exe:noErr()
+ self.cmd = self.cmd .. '2>/dev/null '
+ return self
+ end
+ function exe:wait(w)
+ self.cmd = self.cmd .. '&& touch ' .. w .. ' '
+ return self
+ end
+ function exe:Do()
+ --[[ "The condition expression of a control structure can return any
+ value. Both false and nil are considered false. All values different
+ from nil and false are considered true (in particular, the number 0
+ and the empty string are also true)."
+ says the docs, I beg to differ.]]
+ if true == self.log then D(" executing - " .. self.cmd .. "
") end
+ --[[ Damn os.execute()
+ Lua 5.1 says it returns "a status code, which is system-dependent"
+ Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
+ I'm getting 7168 or 0. No idea what the fuck that is.
+ local ok, rslt, status = os.execute(s)
+ ]]
+ local f = APT.readCmd(self.cmd, 'r')
+ -- The last line will be the command's returned status, collect everything else in result.
+ self.status = '' -- Otherwise the result starts with 0.
+ self.result = '\n'
+ self.lines = f
+ for i,l in ipairs(f) do
+ self.result = self.result .. l .. "\n"
+ end
+ f = APT.readCmd('echo "$?"', 'r')
+ for i,l in ipairs(f) do
+ self.status = tonumber(l)
+ if (137 == self.status) or (124 == self.status) then
+ print("timeout killed " .. self.status .. ' ' .. self.command)
+ E("timeout killed " .. self.status .. ' ' .. self.command)
+ elseif (0 ~= self.status) then
+ print("status |" .. self.status .. '| ' .. self.command)
+ E("status |" .. self.status .. '| ' .. self.command)
+ end
+ end
+ return self
+ end
+ function exe:fork(host)
+ if nil ~= host then self.cmd = self.cmd .. '; r=$?; if [ $r -ge 124 ]; then echo "$r ' .. host .. ' failed forked command ' .. string.gsub(self.cmd, '"', "'") .. '"; fi' end
+ self.cmd = '{ ' .. self.cmd .. '; } &'
+ if true == self.log then D(" forking - " .. self.cmd .. "
") end
+ os.execute(self.cmd)
+ return self
+ end
+ return exe
+end
+
+
+
+local Cards = {}
+
+print('Scanning for audio devices.')
+local cards = APT.exe('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'] = APT.exe('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'] = APT.exe('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: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n)
+ end
+ end
+ end
+end
+
+APT.exe('mkdir -p ' .. asoundrcPath):Do()
+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
--
cgit v1.1