aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.asoundrc1
-rw-r--r--README.md50
-rwxr-xr-xjackoffall146
-rwxr-xr-xjackonall237
-rwxr-xr-xjackscanall326
5 files changed, 760 insertions, 0 deletions
diff --git a/.asoundrc b/.asoundrc
new file mode 100644
index 0000000..7321f6c
--- /dev/null
+++ b/.asoundrc
@@ -0,0 +1 @@
</var/lib/JOAD/asoundrc>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..86dec60
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
1This is the JackOnAllDevices project, JOAD for short.
2
3The purpose is to scan for all ALSA / asound audio devices, and hook them
4all up to JACK. Then it starts up JACK, and hooks up any joysticks it
5finds as MIDI controllers. So any ALSA application gets routed through
6JACK.
7
8This is very rough for now, only just started writing it. jackoffall is
9particularly crude and violent, lots of killall.
10
11Since it isn't a package yet, some setup is needed.
12
13The packages you need installed are -
14 luajit
15 jackd2
16 jack-tools for jack-plumbing, but other patch persistance methods could be used.
17 a2jmidid
18 zita-ajbridge
19 aseqjoy
20
21 qjackctl can be used as a visual patchbay, though I prefer catia from the KXStudio repos.
22
23You need to have the snd-aloop kernel module loaded. The jackscanall
24script should be run at boot time, put it into /etc/boot.d/. jackonall
25should be called on user login. Probably don't need to run jackoffall on
26user logout.
27
28Alas ~/.asoundrc doesn't understand ~ or $HOME, or even "try the current
29directory" it seems. So you have to hard code the path. Make sure your
30~/.asoundrc includes something like this (an example is included) -
31
32</var/lib/JOAD/asoundrc>
33
34jackscanall scans for your sound devices and creates
35/var/lib/JOAD/asoundrc. Run jackscanall once as root to create that
36file, and each time you need to change your devices.
37
38jackonall starts up JACK and friends, and creates JACK devices for all
39the things jackscanall found. It creates the cloop and ploop devices
40that catch everything ALSA does. Then creates MIDI devices for all your
41joysticks.
42
43jackoffall closes down everything jackonall started up.
44
45NOTE - Seems both ALSA and JACK are per user. So you need to run
46jackonall and jackoffall for each user.
47
48TODO - Leave it running, and hotplug ALSA / asound audio devices.
49 a2jmidid takes care of hotplugging MIDI devices.
50 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 @@
1#!/usr/bin/env luajit
2
3
4local APT = {}
5
6
7APT.readCmd = function(cmd)
8 local result = {}
9 local output = io.popen(cmd)
10 if nil ~= output then
11 for l in output:lines() do
12 table.insert(result, l)
13 end
14 end
15 return result
16end
17
18
19APT.exe = function(c)
20 local exe = {status = 0, result = '', log = true, cmd = c .. ' ', command = c}
21
22 function exe:log()
23 self.log = true
24 return self
25 end
26 function exe:Nice(c)
27 if nil == c then
28 self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
29 else
30 self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
31 end
32 return self
33 end
34 function exe:timeout(c)
35 -- 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.
36 -- --kill-after means "send KILL after TERM fails.
37 if nil == c then
38 self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
39 else
40 self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
41 end
42 return self
43 end
44 function exe:also(c)
45 if nil == c then c = '' else c = ' ' .. c end
46 self.cmd = self.cmd .. ';' .. c .. ' '
47 return self
48 end
49 function exe:And(c)
50 if nil == c then c = '' else c = ' ' .. c end
51 self.cmd = self.cmd .. '&&' .. c .. ' '
52 return self
53 end
54 function exe:Or(c)
55 if nil == c then c = '' end
56 self.cmd = self.cmd .. '|| ' .. c .. ' '
57 return self
58 end
59 function exe:noErr()
60 self.cmd = self.cmd .. '2>/dev/null '
61 return self
62 end
63 function exe:wait(w)
64 self.cmd = self.cmd .. '&& touch ' .. w .. ' '
65 return self
66 end
67 function exe:Do()
68 --[[ "The condition expression of a control structure can return any
69 value. Both false and nil are considered false. All values different
70 from nil and false are considered true (in particular, the number 0
71 and the empty string are also true)."
72 says the docs, I beg to differ.]]
73 if true == self.log then D(" executing - &nbsp; <code>" .. self.cmd .. "</code>") end
74 --[[ Damn os.execute()
75 Lua 5.1 says it returns "a status code, which is system-dependent"
76 Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
77 I'm getting 7168 or 0. No idea what the fuck that is.
78 local ok, rslt, status = os.execute(s)
79 ]]
80 local f = APT.readCmd(self.cmd, 'r')
81 -- The last line will be the command's returned status, collect everything else in result.
82 self.status = '' -- Otherwise the result starts with 0.
83 self.result = '\n'
84 for i,l in ipairs(f) do
85 self.result = self.result .. l .. "\n"
86 end
87 f = APT.readCmd('echo "$?"', 'r')
88 for i,l in ipairs(f) do
89 self.status = tonumber(l)
90 if (137 == self.status) or (124 == self.status) then
91 print("timeout killed " .. self.status .. ' ' .. self.command)
92 E("timeout killed " .. self.status .. ' ' .. self.command)
93 elseif (0 ~= self.status) then
94 print("status |" .. self.status .. '| ' .. self.command)
95 E("status |" .. self.status .. '| ' .. self.command)
96 end
97 end
98 return self
99 end
100 function exe:fork(host)
101 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
102 self.cmd = '{ ' .. self.cmd .. '; } &'
103 if true == self.log then D(" forking - &nbsp; <code>" .. self.cmd .. "</code>") end
104 os.execute(self.cmd)
105 return self
106 end
107 return exe
108end
109
110
111APT.exe("a2j_control --stop"):Do()
112APT.exe("sleep 2"):Do()
113APT.exe("a2j_control --exit"):Do()
114APT.exe("sleep 2"):Do()
115APT.exe("killall -TERM alsa_out"):Do()
116APT.exe("killall -TERM alsa_in"):Do()
117APT.exe("killall -TERM zita-a2j"):Do()
118APT.exe("killall -TERM zita-j2a"):Do()
119APT.exe("killall -TERM aseqjoy"):Do()
120APT.exe("killall -TERM jack-plumbing"):Do()
121APT.exe("sleep 2"):Do()
122APT.exe("jack_control stop"):Do()
123APT.exe("sleep 2"):Do()
124APT.exe("jack_control exit"):Do()
125APT.exe("sleep 2"):Do()
126--APT.exe("a2j_control --stop; a2j_control --exit"):Do()
127--APT.exe("sleep 2"):Do()
128APT.exe("killall -TERM jmcore"):Do()
129APT.exe("pkill -TERM jackdbus; pkill -TERM a2jmidid"):Do()
130APT.exe("killall -TERM a2jmidid"):Do()
131APT.exe("killall -KILL jackdbus"):Do()
132APT.exe("sleep 2"):Do()
133APT.exe("killall -KILL a2jmidid"):Do()
134APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do()
135APT.exe("sleep 2"):Do()
136APT.exe("killall -KILL a2jmidid"):Do()
137APT.exe("killall -KILL jackdbus"):Do()
138APT.exe("sleep 2"):Do()
139APT.exe("killall -KILL a2jmidid"):Do()
140APT.exe("killall -KILL jackdbus"):Do()
141APT.exe("sleep 2"):Do()
142APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do()
143APT.exe("killall -TERM qjackctl"):Do()
144
145-- Catia is python, and no easy way to kill it.
146APT.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 @@
1#!/usr/bin/env luajit
2
3--[[
4This is part of the JackOnAllDevices project, JOAD for short.
5
6NOTE - Seems both ALSA and JACK are per user.
7
8]]
9
10
11-- CHANGE these to suit.
12--local GUI = 'qjackctl'
13local GUI = 'catia'
14local alias = {
15 {name='Screen', dev='HDMI9'},
16 }
17
18
19-- This APT stuff was copied from apt-panopticon.
20local APT = {}
21
22APT.readCmd = function(cmd)
23 local result = {}
24 local output = io.popen(cmd)
25 if nil ~= output then
26 for l in output:lines() do
27 table.insert(result, l)
28 end
29 end
30 return result
31end
32
33
34APT.exe = function(c)
35 local exe = {status = 0, result = '', lines = {}, log = true, cmd = c .. ' ', command = c}
36
37 function exe:log()
38 self.log = true
39 return self
40 end
41 function exe:Nice(c)
42 if nil == c then
43 self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
44 else
45 self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
46 end
47 return self
48 end
49 function exe:timeout(c)
50 -- 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.
51 -- --kill-after means "send KILL after TERM fails.
52 if nil == c then
53 self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
54 else
55 self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
56 end
57 return self
58 end
59 function exe:also(c)
60 if nil == c then c = '' else c = ' ' .. c end
61 self.cmd = self.cmd .. ';' .. c .. ' '
62 return self
63 end
64 function exe:And(c)
65 if nil == c then c = '' else c = ' ' .. c end
66 self.cmd = self.cmd .. '&&' .. c .. ' '
67 return self
68 end
69 function exe:Or(c)
70 if nil == c then c = '' end
71 self.cmd = self.cmd .. '|| ' .. c .. ' '
72 return self
73 end
74 function exe:noErr()
75 self.cmd = self.cmd .. '2>/dev/null '
76 return self
77 end
78 function exe:wait(w)
79 self.cmd = self.cmd .. '&& touch ' .. w .. ' '
80 return self
81 end
82 function exe:Do()
83 --[[ "The condition expression of a control structure can return any
84 value. Both false and nil are considered false. All values different
85 from nil and false are considered true (in particular, the number 0
86 and the empty string are also true)."
87 says the docs, I beg to differ.]]
88 if true == self.log then D(" executing - &nbsp; <code>" .. self.cmd .. "</code>") end
89 --[[ Damn os.execute()
90 Lua 5.1 says it returns "a status code, which is system-dependent"
91 Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
92 I'm getting 7168 or 0. No idea what the fuck that is.
93 local ok, rslt, status = os.execute(s)
94 ]]
95 local f = APT.readCmd(self.cmd, 'r')
96 -- The last line will be the command's returned status, collect everything else in result.
97 self.status = '' -- Otherwise the result starts with 0.
98 self.result = '\n'
99 self.lines = f
100 for i,l in ipairs(f) do
101 self.result = self.result .. l .. "\n"
102 end
103 f = APT.readCmd('echo "$?"', 'r')
104 for i,l in ipairs(f) do
105 self.status = tonumber(l)
106 if (137 == self.status) or (124 == self.status) then
107 print("timeout killed " .. self.status .. ' ' .. self.command)
108 E("timeout killed " .. self.status .. ' ' .. self.command)
109 elseif (0 ~= self.status) then
110 print("status |" .. self.status .. '| ' .. self.command)
111 E("status |" .. self.status .. '| ' .. self.command)
112 end
113 end
114 return self
115 end
116 function exe:fork(host)
117 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
118 self.cmd = '{ ' .. self.cmd .. '; } &'
119 if true == self.log then D(" forking - &nbsp; <code>" .. self.cmd .. "</code>") end
120 os.execute(self.cmd)
121 return self
122 end
123 return exe
124end
125
126
127
128local Cards = {}
129
130print('Scanning for audio devices.')
131local cards = APT.exe('ls -d1 /proc/asound/card[0-9]*'):noErr():Do()
132for i,l in ipairs(cards.lines) do
133 local f, e = io.open(l .. '/id', "r")
134 if nil == f then print("Could not open " .. l .. '/id') else
135 Cards[l] = {path = l, name = f:read("*a"):sub(1, -2), devs = {}, captureDevs = {}, playbackDevs = {}}
136 if "Loopback" ~= Cards[l]['name'] then
137 Cards[l]['capture'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do()
138 for j,c in ipairs(Cards[l]['capture'].lines) do
139 local n = c:match(".*pcm(%d+).*")
140 Cards[l]['captureDevs'][j] = n
141 Cards[l]['devs'][n] = n
142 print("\tFound capture device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['captureDevs'][j] .. ' ' .. n)
143 end
144 Cards[l]['playback'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do()
145 for j,p in ipairs(Cards[l]['playback'].lines) do
146 local n = p:match(".*pcm(%d+).*")
147 Cards[l]['playbackDevs'][j] = n
148 Cards[l]['devs'][n] = n
149 print("\tFound playback device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n)
150 end
151 end
152 end
153end
154
155
156print('')
157print("Start up JACK and friends.")
158print("jack_control")
159APT.exe('jack_control start'):Do()
160APT.exe('jack_control ds alsa'):Do()
161--jack_control dps device hw:RIG,0
162print("sleep 5")
163APT.exe('sleep 5'):Do()
164if nil ~= GUI then
165 print(GUI)
166 APT.exe(GUI):fork()
167end
168print("jack-plumbing")
169APT.exe('jack-plumbing -q 2>/dev/null'):fork()
170-- Bridge ALSA ports to JACK ports. Only handles MIDI.
171-- 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)/
172--a2j_control actually starts a2jmidid.
173----a2jmidid -e -u &
174-- 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.
175print("a2j_control")
176APT.exe('a2j_control --ehw && a2j_control --start'):Do()
177print("sleep 2")
178APT.exe('sleep 2'):Do()
179print("")
180
181
182--local AIN = "alsa_in"
183local AIN = "zita-a2j"
184--local AOUT = "alsa_out"
185local AOUT = "zita-j2a"
186
187print("Basic ALSA sound devices converted to JACK.")
188for i,C in pairs(alias) do
189 print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['dev'])
190 APT.exe('alsa_out -j ' .. C['name'] .. ' -d ' .. C['dev']):fork()
191 APT.exe('sleep 1'):Do()
192end
193print("HW playback: cloop\tDEVICE: cloop")
194-- No idea why, cloop wont work with zita-a2j.
195APT.exe('alsa_in -j cloop -d cloop'):fork()
196APT.exe('sleep 1'):Do()
197APT.exe('jack_connect cloop:capture_1 system:playback_1'):Do()
198APT.exe('jack_connect cloop:capture_2 system:playback_2'):Do()
199print("HW playback: ploop\tDEVICE: ploop")
200APT.exe('alsa_out -j ploop -d ploop'):fork()
201APT.exe('sleep 1'):Do()
202APT.exe('jack_connect system:capture_1 ploop:playback_1'):Do()
203APT.exe('jack_connect system:capture_2 ploop:playback_2'):Do()
204
205
206print("")
207
208print("Rest of ALSA sound devices converted to JACK.")
209for i,C in pairs(Cards) do
210 for j,c in ipairs(C['playbackDevs']) do
211 print("HW playback: " .. C['name'] .. "\tDEVICE: " .. C['playbackDevs'][j])
212 APT.exe(AOUT .. ' -j ' .. C['name'] .. "_" .. C['playbackDevs'][j] .. '-in -d ' .. C['name'] .. C['playbackDevs'][j]):fork()
213-- APT.exe('sleep 1'):Do()
214 end
215 for j,c in ipairs(C['captureDevs']) do
216 print("HW capture: " .. C['name'] .. "\tDEVICE: " .. C['captureDevs'][j])
217 APT.exe(AIN .. ' -j ' .. C['name'] .. "_" .. C['captureDevs'][j] .. '-out -d ' .. C['name'] .. C['captureDevs'][j]):fork()
218-- APT.exe('sleep 1'):Do()
219 end
220end
221print("")
222
223print("Scanning for joysticks.")
224local sticks = APT.exe('ls -1 /dev/input/js[0-9]*'):noErr():Do()
225for i,l in ipairs(sticks.lines) do
226 print("aseqjoy " .. l)
227 -- Buttons switch to that numbered MIDI channel, defaults to 1.
228 -- Axis are mapped to MIDI controllers 10 - 15
229 -- -r means to use high resolution MIDI values.
230 APT.exe('aseqjoy -d ' .. l:sub(-1,-1) .. ' -r'):fork()
231-- print("sleep 1")
232-- APT.exe('sleep 1'):Do()
233end
234print("")
235
236
237
diff --git a/jackscanall b/jackscanall
new file mode 100755
index 0000000..24d87df
--- /dev/null
+++ b/jackscanall
@@ -0,0 +1,326 @@
1#!/usr/bin/env luajit
2
3--[[
4This is part of the JackOnAllDevices project, JOAD for short.
5
6The purpose is to scan for all ALSA / asound audio devices, and hook them
7all up to JACK. Then it starts up JACK, and hooks up any joysticks it
8finds as MIDI controllers.
9
10
11Alas ~/.asoundrc doesn't understand ~ or $HOME, or even "try the current
12directory" it seems. So you have to hard cade the path. Make sure your
13~/.asoundrc or /etc/asoundrc includes something like this -
14
15</var/lib/JOAD/asoundrc>
16
17
18Run this once as root to create that file, and each time you need to
19change your devices.
20
21
22TODO - Leave it running, and hotplug ALSA / asound audio devices.
23 a2jmidid takes care of hotplugging MIDI devices.
24 Though I think I still need to deal with hotplugged joysticks.
25
26NOTE - Seems both ALSA and JACK are per user.
27
28]]
29
30
31-- CHANGE these to suit.
32local asoundrcPath = '/var/lib/JOAD'
33local asoundrc = 'asoundrc'
34
35
36
37-- This APT stuff was copied from apt-panopticon.
38local APT = {}
39
40APT.readCmd = function(cmd)
41 local result = {}
42 local output = io.popen(cmd)
43 if nil ~= output then
44 for l in output:lines() do
45 table.insert(result, l)
46 end
47 end
48 return result
49end
50
51
52APT.exe = function(c)
53 local exe = {status = 0, result = '', lines = {}, log = true, cmd = c .. ' ', command = c}
54
55 function exe:log()
56 self.log = true
57 return self
58 end
59 function exe:Nice(c)
60 if nil == c then
61 self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd
62 else
63 self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' '
64 end
65 return self
66 end
67 function exe:timeout(c)
68 -- 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.
69 -- --kill-after means "send KILL after TERM fails.
70 if nil == c then
71 self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd
72 else
73 self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd
74 end
75 return self
76 end
77 function exe:also(c)
78 if nil == c then c = '' else c = ' ' .. c end
79 self.cmd = self.cmd .. ';' .. c .. ' '
80 return self
81 end
82 function exe:And(c)
83 if nil == c then c = '' else c = ' ' .. c end
84 self.cmd = self.cmd .. '&&' .. c .. ' '
85 return self
86 end
87 function exe:Or(c)
88 if nil == c then c = '' end
89 self.cmd = self.cmd .. '|| ' .. c .. ' '
90 return self
91 end
92 function exe:noErr()
93 self.cmd = self.cmd .. '2>/dev/null '
94 return self
95 end
96 function exe:wait(w)
97 self.cmd = self.cmd .. '&& touch ' .. w .. ' '
98 return self
99 end
100 function exe:Do()
101 --[[ "The condition expression of a control structure can return any
102 value. Both false and nil are considered false. All values different
103 from nil and false are considered true (in particular, the number 0
104 and the empty string are also true)."
105 says the docs, I beg to differ.]]
106 if true == self.log then D(" executing - &nbsp; <code>" .. self.cmd .. "</code>") end
107 --[[ Damn os.execute()
108 Lua 5.1 says it returns "a status code, which is system-dependent"
109 Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
110 I'm getting 7168 or 0. No idea what the fuck that is.
111 local ok, rslt, status = os.execute(s)
112 ]]
113 local f = APT.readCmd(self.cmd, 'r')
114 -- The last line will be the command's returned status, collect everything else in result.
115 self.status = '' -- Otherwise the result starts with 0.
116 self.result = '\n'
117 self.lines = f
118 for i,l in ipairs(f) do
119 self.result = self.result .. l .. "\n"
120 end
121 f = APT.readCmd('echo "$?"', 'r')
122 for i,l in ipairs(f) do
123 self.status = tonumber(l)
124 if (137 == self.status) or (124 == self.status) then
125 print("timeout killed " .. self.status .. ' ' .. self.command)
126 E("timeout killed " .. self.status .. ' ' .. self.command)
127 elseif (0 ~= self.status) then
128 print("status |" .. self.status .. '| ' .. self.command)
129 E("status |" .. self.status .. '| ' .. self.command)
130 end
131 end
132 return self
133 end
134 function exe:fork(host)
135 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
136 self.cmd = '{ ' .. self.cmd .. '; } &'
137 if true == self.log then D(" forking - &nbsp; <code>" .. self.cmd .. "</code>") end
138 os.execute(self.cmd)
139 return self
140 end
141 return exe
142end
143
144
145
146local Cards = {}
147
148print('Scanning for audio devices.')
149local cards = APT.exe('ls -d1 /proc/asound/card[0-9]*'):noErr():Do()
150for i,l in ipairs(cards.lines) do
151 local f, e = io.open(l .. '/id', "r")
152 if nil == f then print("Could not open " .. l .. '/id') else
153 Cards[l] = {path = l, name = f:read("*a"):sub(1, -2), devs = {}, captureDevs = {}, playbackDevs = {}}
154 if "Loopback" ~= Cards[l]['name'] then
155 Cards[l]['capture'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do()
156 for j,c in ipairs(Cards[l]['capture'].lines) do
157 local n = c:match(".*pcm(%d+).*")
158 Cards[l]['captureDevs'][j] = n
159 Cards[l]['devs'][n] = n
160 print("\tFound capture device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['captureDevs'][j] .. ' ' .. n)
161 end
162 Cards[l]['playback'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do()
163 for j,p in ipairs(Cards[l]['playback'].lines) do
164 local n = p:match(".*pcm(%d+).*")
165 Cards[l]['playbackDevs'][j] = n
166 Cards[l]['devs'][n] = n
167 print("\tFound playback device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n)
168 end
169 end
170 end
171end
172
173APT.exe('mkdir -p ' .. asoundrcPath):Do()
174local a, e = io.open(asoundrcPath .. '/' .. asoundrc, "w")
175if nil == a then print("Could not open " .. asoundrcPath .. '/' .. asoundrc) else
176 for i,C in pairs(Cards) do
177 for j,c in pairs(C['devs']) do
178 a:write("pcm." .. C['name'] .. j .. " {\n")
179 a:write(" type hw\n")
180 a:write(" card " .. C['name'] .. "\n")
181 a:write(" device " .. C['devs'][j] .. "\n")
182 a:write("}\n")
183 a:write("ctl." .. C['name'] .. j .. " {\n")
184 a:write(" type hw\n")
185 a:write(" card " .. C['name'] .. "\n")
186 a:write(" device " .. C['devs'][j] .. "\n")
187 a:write("}\n\n")
188 end
189 end
190 a:write([[
191#################################################################################################################################
192
193# The complex way - https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge
194
195# More custom version, but it didn't work for me.
196# hardware 0,0 : used for ALSA playback
197#pcm.loophw00 {
198# type hw
199# card Loopback
200# device 0
201# subdevice 0
202# format S32_LE
203# rate 48000
204#}
205
206# playback PCM device: using loopback subdevice 0,0
207# Don't use a buffer size that is too small. Some apps
208# won't like it and it will sound crappy
209
210#pcm.amix {
211# type dmix
212# ipc_key 219345
213# slave {
214# pcm loophw00
215## period_size 4096
216## periods 2
217# }
218#}
219
220# software volume
221#pcm.asoftvol {
222# type softvol
223# slave.pcm "amix"
224
225# control { name PCM }
226
227# min_dB -51.0
228# max_dB 0.0
229#}
230
231
232# for jack alsa_in: looped-back signal at other ends
233#pcm.cloop {
234# type hw
235# card Loopback
236# device 1
237# subdevice 0
238# format S32_LE
239# rate 48000
240#}
241
242# hardware 0,1 : used for ALSA capture
243#pcm.loophw01 {
244# type hw
245# card Loopback
246# device 0
247# subdevice 1
248# format S32_LE
249# rate 48000
250#}
251
252# for jack alsa_out: looped-back signal at other end
253#pcm.ploop {
254# type hw
255# card Loopback
256# device 1
257# subdevice 1
258# format S32_LE
259# rate 48000
260#}
261
262# duplex device combining our PCM devices defined above
263#pcm.aduplex {
264# type asym
265# playback.pcm "asoftvol"
266# capture.pcm "loophw01"
267#}
268
269# default device
270#pcm.!default {
271# type plug
272# slave.pcm aduplex
273
274# hint {
275# show on
276# description "Duplex Loopback"
277# }
278#}
279
280
281
282# Generic method seems to work better.
283# playback PCM device: using loopback subdevice 0,0
284pcm.amix {
285 type dmix
286 ipc_key 219345
287 slave.pcm "hw:Loopback,0,0"
288}
289
290# capture PCM device: using loopback subdevice 0,1
291pcm.asnoop {
292 type dsnoop
293 ipc_key 219346
294 slave.pcm "hw:Loopback,0,1"
295}
296
297# duplex device combining our PCM devices defined above
298pcm.aduplex {
299 type asym
300 playback.pcm "amix"
301 capture.pcm "asnoop"
302}
303
304# ------------------------------------------------------
305# for jack alsa_in and alsa_out: looped-back signal at other ends
306pcm.ploop {
307 type plug
308 slave.pcm "hw:Loopback,1,1"
309}
310
311pcm.cloop {
312 type dsnoop
313 ipc_key 219348
314 slave.pcm "hw:Loopback,1,0"
315}
316
317# ------------------------------------------------------
318# default device
319
320pcm.!default {
321 type plug
322 slave.pcm "aduplex"
323}
324 ]])
325a:close()
326end