diff options
Diffstat (limited to '')
| -rwxr-xr-x | aataaj.lua | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/aataaj.lua b/aataaj.lua new file mode 100755 index 0000000..1ddb45d --- /dev/null +++ b/aataaj.lua | |||
| @@ -0,0 +1,568 @@ | |||
| 1 | #!/usr/bin/env luajit | ||
| 2 | |||
| 3 | --[[ This block is what system V LSB expects. | ||
| 4 | ### BEGIN INIT INFO | ||
| 5 | # Provides: aataajScan | ||
| 6 | # Required-Start: $local_fs | ||
| 7 | # Required-Stop: | ||
| 8 | # X-Start-Before: alsa-utils espeakup | ||
| 9 | # Default-Start: S | ||
| 10 | # Default-Stop: | ||
| 11 | # Short-Description: Scan for audio devices. | ||
| 12 | # Description: Scan for all audio devices and produce an | ||
| 13 | # asound.conf include file fragment. | ||
| 14 | ### END INIT INFO | ||
| 15 | ]] | ||
| 16 | |||
| 17 | local Help = [[ | ||
| 18 | This is part of the AllAudioToALSAandJACK project, aataaj for short, | ||
| 19 | pronounced like "attach". | ||
| 20 | |||
| 21 | The purpose is to scan for all ALSA / asound audio devices, and hook them | ||
| 22 | all up to ALSA and JACK. Then it starts up JACK, and hooks up any | ||
| 23 | joysticks it finds as MIDI controllers. So any ALSA application gets routed through | ||
| 24 | JACK. | ||
| 25 | |||
| 26 | This is very rough for now, only just started writing it. The stop | ||
| 27 | command is particularly crude and violent, lots of killall. | ||
| 28 | |||
| 29 | Since it isn't a package yet, some setup is needed. | ||
| 30 | |||
| 31 | The packages you need installed are - | ||
| 32 | luajit | ||
| 33 | jackd2 | ||
| 34 | jack-tools for jack-plumbing, but other patch persistance methods could be used. | ||
| 35 | a2jmidid | ||
| 36 | zita-ajbridge | ||
| 37 | aseqjoy | ||
| 38 | |||
| 39 | qjackctl can be used as a visual patchbay, though I prefer catia from the KXStudio repos. | ||
| 40 | |||
| 41 | You need to have the snd-aloop kernel module loaded. | ||
| 42 | |||
| 43 | The aataaj.lua script should be run at boot time, put it into | ||
| 44 | /etc/boot.d/ and activate it with - | ||
| 45 | |||
| 46 | update-rc.d aataaj.lua defaults | ||
| 47 | |||
| 48 | It scans for your sound devices and creates /var/lib/aataaj/asoundrc. | ||
| 49 | You can run it manually with "aataaj start" each time you need to change | ||
| 50 | your devices. | ||
| 51 | |||
| 52 | |||
| 53 | |||
| 54 | "aataaj JACK" should be called on user login. Probably don't need to run "aataaj stop" on | ||
| 55 | user logout. | ||
| 56 | |||
| 57 | It starts up JACK and friends, and creates JACK devices for all the | ||
| 58 | things "aataaj start" found. It creates the cloop and ploop devices that | ||
| 59 | catch everything ALSA does. Then creates MIDI devices for all your | ||
| 60 | joysticks. | ||
| 61 | |||
| 62 | |||
| 63 | Alas ~/.asoundrc doesn't understand ~ or $HOME, or even "try the current | ||
| 64 | directory" it seems. So you have to hard cade the path. Make sure your | ||
| 65 | ~/.asoundrc or /etc/asoundrc includes something like this - | ||
| 66 | |||
| 67 | </var/lib/aataaj/asoundrc> | ||
| 68 | |||
| 69 | |||
| 70 | |||
| 71 | "aataaj stop" closes down everything "aataaj JACK" started up. | ||
| 72 | |||
| 73 | |||
| 74 | |||
| 75 | TODO - Leave it running, and hotplug ALSA / asound audio devices. | ||
| 76 | a2jmidid takes care of hotplugging MIDI devices. | ||
| 77 | Though I think I still need to deal with hotplugged joysticks. | ||
| 78 | |||
| 79 | NOTE - Seems both ALSA and JACK are per user. So you need to run | ||
| 80 | "aataaj JACK" for each user. | ||
| 81 | |||
| 82 | ]] | ||
| 83 | |||
| 84 | |||
| 85 | local args = {...} | ||
| 86 | if 0 ~= #args then | ||
| 87 | -- for i,a in pairs(args) do | ||
| 88 | -- print('Argument ' .. i .. ' = ' .. a) | ||
| 89 | -- end | ||
| 90 | |||
| 91 | if 'start' == args[1] then | ||
| 92 | elseif 'stop' == args[1] then | ||
| 93 | APT.exe("killall -TERM qsynth"):Do() | ||
| 94 | APT.exe("a2j_control --stop"):Do() | ||
| 95 | APT.exe("sleep 2"):Do() | ||
| 96 | APT.exe("a2j_control --exit"):Do() | ||
| 97 | APT.exe("sleep 2"):Do() | ||
| 98 | APT.exe("killall -TERM alsa_out"):Do() | ||
| 99 | APT.exe("killall -TERM alsa_in"):Do() | ||
| 100 | APT.exe("killall -TERM zita-a2j"):Do() | ||
| 101 | APT.exe("killall -TERM zita-j2a"):Do() | ||
| 102 | APT.exe("killall -TERM aseqjoy"):Do() | ||
| 103 | APT.exe("killall -TERM jack-plumbing"):Do() | ||
| 104 | APT.exe("sleep 2"):Do() | ||
| 105 | APT.exe("jack_control stop"):Do() | ||
| 106 | APT.exe("sleep 2"):Do() | ||
| 107 | APT.exe("jack_control exit"):Do() | ||
| 108 | APT.exe("sleep 2"):Do() | ||
| 109 | --APT.exe("a2j_control --stop; a2j_control --exit"):Do() | ||
| 110 | --APT.exe("sleep 2"):Do() | ||
| 111 | APT.exe("killall -TERM jmcore"):Do() | ||
| 112 | APT.exe("pkill -TERM jackdbus; pkill -TERM a2jmidid"):Do() | ||
| 113 | APT.exe("killall -TERM a2jmidid"):Do() | ||
| 114 | APT.exe("killall -KILL jackdbus"):Do() | ||
| 115 | APT.exe("sleep 2"):Do() | ||
| 116 | APT.exe("killall -KILL a2jmidid"):Do() | ||
| 117 | APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do() | ||
| 118 | APT.exe("sleep 2"):Do() | ||
| 119 | APT.exe("killall -KILL a2jmidid"):Do() | ||
| 120 | APT.exe("killall -KILL jackdbus"):Do() | ||
| 121 | APT.exe("sleep 2"):Do() | ||
| 122 | APT.exe("killall -KILL a2jmidid"):Do() | ||
| 123 | APT.exe("killall -KILL jackdbus"):Do() | ||
| 124 | APT.exe("sleep 2"):Do() | ||
| 125 | APT.exe("pkill -KILL jackdbus; pkill -KILL a2jmidid"):Do() | ||
| 126 | APT.exe("killall -TERM qjackctl"):Do() | ||
| 127 | |||
| 128 | -- Catia is python, and no easy way to kill it. | ||
| 129 | APT.exe("ps auxw | grep python"):Do() | ||
| 130 | return(0) | ||
| 131 | elseif 'JACK' == args[1] then | ||
| 132 | elseif 'restart' == args[1] then args[1] = 'start' | ||
| 133 | elseif 'force-reload' == args[1] then args[1] = 'start' | ||
| 134 | elseif 'status' == args[1] then | ||
| 135 | return(0) | ||
| 136 | elseif 'help' == args[1] then | ||
| 137 | print(Help) | ||
| 138 | return(0) | ||
| 139 | elseif '--help' == args[1] then | ||
| 140 | print(Help) | ||
| 141 | return(0) | ||
| 142 | else | ||
| 143 | print("Usage: /etc/init.d/aataajScan.lua {start|stop|restart|force-reload|status}") | ||
| 144 | return(1) | ||
| 145 | end | ||
| 146 | else | ||
| 147 | print("Usage: /etc/init.d/aataajScan.lua {help|start|stop|restart|force-reload|status|JACK}") | ||
| 148 | return(1) | ||
| 149 | end | ||
| 150 | |||
| 151 | |||
| 152 | |||
| 153 | -- CHANGE these to suit. | ||
| 154 | local asoundrcPath = '/var/lib/aataaj' | ||
| 155 | local asoundrc = 'asoundrc' | ||
| 156 | local GUI = 'qjackctl' | ||
| 157 | --local GUI = 'catia' | ||
| 158 | local alias = { | ||
| 159 | -- {name='Screen', dev='HDMI9'}, | ||
| 160 | } | ||
| 161 | |||
| 162 | |||
| 163 | |||
| 164 | |||
| 165 | -- This APT stuff was copied from apt-panopticon. | ||
| 166 | local APT = {} | ||
| 167 | |||
| 168 | APT.readCmd = function(cmd) | ||
| 169 | local result = {} | ||
| 170 | local output = io.popen(cmd) | ||
| 171 | if nil ~= output then | ||
| 172 | for l in output:lines() do | ||
| 173 | table.insert(result, l) | ||
| 174 | end | ||
| 175 | end | ||
| 176 | return result | ||
| 177 | end | ||
| 178 | |||
| 179 | |||
| 180 | APT.exe = function(c) | ||
| 181 | local exe = {status = 0, result = '', lines = {}, log = true, cmd = c .. ' ', command = c} | ||
| 182 | |||
| 183 | function exe:log() | ||
| 184 | self.log = true | ||
| 185 | return self | ||
| 186 | end | ||
| 187 | function exe:Nice(c) | ||
| 188 | if nil == c then | ||
| 189 | self.cmd = 'ionice -c3 nice -n 19 ' .. self.cmd | ||
| 190 | else | ||
| 191 | self.cmd = self.cmd .. 'ionice -c3 nice -n 19 ' .. c .. ' ' | ||
| 192 | end | ||
| 193 | return self | ||
| 194 | end | ||
| 195 | function exe:timeout(c) | ||
| 196 | -- 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. | ||
| 197 | -- --kill-after means "send KILL after TERM fails. | ||
| 198 | if nil == c then | ||
| 199 | self.cmd = 'timeout --kill-after=10.0 --foreground 42.0s ' .. self.cmd | ||
| 200 | else | ||
| 201 | self.cmd = 'timeout --kill-after=10.0 --foreground ' .. c .. ' ' .. self.cmd | ||
| 202 | end | ||
| 203 | return self | ||
| 204 | end | ||
| 205 | function exe:also(c) | ||
| 206 | if nil == c then c = '' else c = ' ' .. c end | ||
| 207 | self.cmd = self.cmd .. ';' .. c .. ' ' | ||
| 208 | return self | ||
| 209 | end | ||
| 210 | function exe:And(c) | ||
| 211 | if nil == c then c = '' else c = ' ' .. c end | ||
| 212 | self.cmd = self.cmd .. '&&' .. c .. ' ' | ||
| 213 | return self | ||
| 214 | end | ||
| 215 | function exe:Or(c) | ||
| 216 | if nil == c then c = '' end | ||
| 217 | self.cmd = self.cmd .. '|| ' .. c .. ' ' | ||
| 218 | return self | ||
| 219 | end | ||
| 220 | function exe:noErr() | ||
| 221 | self.cmd = self.cmd .. '2>/dev/null ' | ||
| 222 | return self | ||
| 223 | end | ||
| 224 | function exe:wait(w) | ||
| 225 | self.cmd = self.cmd .. '&& touch ' .. w .. ' ' | ||
| 226 | return self | ||
| 227 | end | ||
| 228 | function exe:Do() | ||
| 229 | --[[ "The condition expression of a control structure can return any | ||
| 230 | value. Both false and nil are considered false. All values different | ||
| 231 | from nil and false are considered true (in particular, the number 0 | ||
| 232 | and the empty string are also true)." | ||
| 233 | says the docs, I beg to differ.]] | ||
| 234 | if true == self.log then D(" executing - <code>" .. self.cmd .. "</code>") end | ||
| 235 | --[[ Damn os.execute() | ||
| 236 | Lua 5.1 says it returns "a status code, which is system-dependent" | ||
| 237 | Lua 5.2 says it returns true/nil, "exit"/"signal", the status code. | ||
| 238 | I'm getting 7168 or 0. No idea what the fuck that is. | ||
| 239 | local ok, rslt, status = os.execute(s) | ||
| 240 | ]] | ||
| 241 | local f = APT.readCmd(self.cmd, 'r') | ||
| 242 | -- The last line will be the command's returned status, collect everything else in result. | ||
| 243 | self.status = '' -- Otherwise the result starts with 0. | ||
| 244 | self.result = '\n' | ||
| 245 | self.lines = f | ||
| 246 | for i,l in ipairs(f) do | ||
| 247 | self.result = self.result .. l .. "\n" | ||
| 248 | end | ||
| 249 | f = APT.readCmd('echo "$?"', 'r') | ||
| 250 | for i,l in ipairs(f) do | ||
| 251 | self.status = tonumber(l) | ||
| 252 | if (137 == self.status) or (124 == self.status) then | ||
| 253 | print("timeout killed " .. self.status .. ' ' .. self.command) | ||
| 254 | E("timeout killed " .. self.status .. ' ' .. self.command) | ||
| 255 | elseif (0 ~= self.status) then | ||
| 256 | print("status |" .. self.status .. '| ' .. self.command) | ||
| 257 | E("status |" .. self.status .. '| ' .. self.command) | ||
| 258 | end | ||
| 259 | end | ||
| 260 | return self | ||
| 261 | end | ||
| 262 | function exe:fork(host) | ||
| 263 | 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 | ||
| 264 | self.cmd = '{ ' .. self.cmd .. '; } &' | ||
| 265 | if true == self.log then D(" forking - <code>" .. self.cmd .. "</code>") end | ||
| 266 | os.execute(self.cmd) | ||
| 267 | return self | ||
| 268 | end | ||
| 269 | return exe | ||
| 270 | end | ||
| 271 | |||
| 272 | |||
| 273 | |||
| 274 | local Cards = {} | ||
| 275 | |||
| 276 | print('Scanning for audio devices.') | ||
| 277 | local cards = APT.exe('ls -d1 /proc/asound/card[0-9]*'):noErr():Do() | ||
| 278 | for i,l in ipairs(cards.lines) do | ||
| 279 | local f, e = io.open(l .. '/id', "r") | ||
| 280 | if nil == f then print("Could not open " .. l .. '/id') else | ||
| 281 | Cards[l] = {path = l, name = f:read("*a"):sub(1, -2), devs = {}, captureDevs = {}, playbackDevs = {}} | ||
| 282 | if "Loopback" ~= Cards[l]['name'] then | ||
| 283 | Cards[l]['capture'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do() | ||
| 284 | for j,c in ipairs(Cards[l]['capture'].lines) do | ||
| 285 | local n = c:match(".*pcm(%d+).*") | ||
| 286 | Cards[l]['captureDevs'][j] = n | ||
| 287 | Cards[l]['devs'][n] = n | ||
| 288 | print("\tFound capture device: " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['captureDevs'][j] .. ' ' .. n) | ||
| 289 | end | ||
| 290 | Cards[l]['playback'] = APT.exe('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do() | ||
| 291 | for j,p in ipairs(Cards[l]['playback'].lines) do | ||
| 292 | local n = p:match(".*pcm(%d+).*") | ||
| 293 | Cards[l]['playbackDevs'][j] = n | ||
| 294 | Cards[l]['devs'][n] = n | ||
| 295 | print("\tFound playback device " .. i - 1 .. " : " .. Cards[l]['name'] .. "\tDEVICE: " .. Cards[l]['playbackDevs'][j] .. ' ' .. n) | ||
| 296 | if 'JACK' ~= args[1] then | ||
| 297 | -- print('\t\tALSA_CARD=' .. i - 1 .. ' espeak "Found playback device ' .. i - 1 .. ' : ' .. Cards[l]['name'] .. ' DEVICE: ' .. Cards[l]['playbackDevs'][j] .. ' ' .. n .. '"') | ||
| 298 | APT.exe('ALSA_CARD=' .. i - 1 .. ' espeak "Found playback device ' .. i - 1 .. ' : ' .. Cards[l]['name'] .. ' DEVICE: ' .. Cards[l]['playbackDevs'][j] .. ' ' .. n .. '"'):noErr():Do() | ||
| 299 | APT.exe('sleep 1') | ||
| 300 | end | ||
| 301 | end | ||
| 302 | end | ||
| 303 | end | ||
| 304 | end | ||
| 305 | |||
| 306 | if 'start' == args[1] then | ||
| 307 | APT.exe('mkdir -p ' .. asoundrcPath):Do() | ||
| 308 | local a, e = io.open(asoundrcPath .. '/jack-plumbing', "w") | ||
| 309 | if nil == a then print("Could not open " .. asoundrcPath .. '/jack-plumbing') else | ||
| 310 | a:write([[ | ||
| 311 | (connect "system:capture_1" "ploop:playback_1") | ||
| 312 | (connect "system:capture_2" "ploop:playback_2") | ||
| 313 | (connect "cloop:capture_1" "system:playback_1") | ||
| 314 | (connect "cloop:capture_2" "system:playback_2") | ||
| 315 | (connect "cloop:capture_1" "(.*)-in:playback_1") | ||
| 316 | (connect "cloop:capture_2" "(.*)-in:playback_2") | ||
| 317 | |||
| 318 | (connect "qsynth:left" "(.*)-in:playback_1") | ||
| 319 | (connect "qsynth:right" "(.*)-in:playback_2") | ||
| 320 | ]]) | ||
| 321 | a:close() | ||
| 322 | end | ||
| 323 | local a, e = io.open(asoundrcPath .. '/' .. asoundrc, "w") | ||
| 324 | if nil == a then print("Could not open " .. asoundrcPath .. '/' .. asoundrc) else | ||
| 325 | for i,C in pairs(Cards) do | ||
| 326 | for j,c in pairs(C['devs']) do | ||
| 327 | a:write("pcm." .. C['name'] .. j .. " {\n") | ||
| 328 | a:write(" type hw\n") | ||
| 329 | a:write(" card " .. C['name'] .. "\n") | ||
| 330 | a:write(" device " .. C['devs'][j] .. "\n") | ||
| 331 | a:write("}\n") | ||
| 332 | a:write("ctl." .. C['name'] .. j .. " {\n") | ||
| 333 | a:write(" type hw\n") | ||
| 334 | a:write(" card " .. C['name'] .. "\n") | ||
| 335 | a:write(" device " .. C['devs'][j] .. "\n") | ||
| 336 | a:write("}\n\n") | ||
| 337 | end | ||
| 338 | end | ||
| 339 | a:write([[ | ||
| 340 | ################################################################################################################################# | ||
| 341 | |||
| 342 | # The complex way - https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge | ||
| 343 | |||
| 344 | # More custom version, but it didn't work for me. | ||
| 345 | # hardware 0,0 : used for ALSA playback | ||
| 346 | #pcm.loophw00 { | ||
| 347 | # type hw | ||
| 348 | # card Loopback | ||
| 349 | # device 0 | ||
| 350 | # subdevice 0 | ||
| 351 | # format S32_LE | ||
| 352 | # rate 48000 | ||
| 353 | #} | ||
| 354 | |||
| 355 | # playback PCM device: using loopback subdevice 0,0 | ||
| 356 | # Don't use a buffer size that is too small. Some apps | ||
| 357 | # won't like it and it will sound crappy | ||
| 358 | |||
| 359 | #pcm.amix { | ||
| 360 | # type dmix | ||
| 361 | # ipc_key 219345 | ||
| 362 | # slave { | ||
| 363 | # pcm loophw00 | ||
| 364 | ## period_size 4096 | ||
| 365 | ## periods 2 | ||
| 366 | # } | ||
| 367 | #} | ||
| 368 | |||
| 369 | # software volume | ||
| 370 | #pcm.asoftvol { | ||
| 371 | # type softvol | ||
| 372 | # slave.pcm "amix" | ||
| 373 | |||
| 374 | # control { name PCM } | ||
| 375 | |||
| 376 | # min_dB -51.0 | ||
| 377 | # max_dB 0.0 | ||
| 378 | #} | ||
| 379 | |||
| 380 | |||
| 381 | # for jack alsa_in: looped-back signal at other ends | ||
| 382 | #pcm.cloop { | ||
| 383 | # type hw | ||
| 384 | # card Loopback | ||
| 385 | # device 1 | ||
| 386 | # subdevice 0 | ||
| 387 | # format S32_LE | ||
| 388 | # rate 48000 | ||
| 389 | #} | ||
| 390 | |||
| 391 | # hardware 0,1 : used for ALSA capture | ||
| 392 | #pcm.loophw01 { | ||
| 393 | # type hw | ||
| 394 | # card Loopback | ||
| 395 | # device 0 | ||
| 396 | # subdevice 1 | ||
| 397 | # format S32_LE | ||
| 398 | # rate 48000 | ||
| 399 | #} | ||
| 400 | |||
| 401 | # for jack alsa_out: looped-back signal at other end | ||
| 402 | #pcm.ploop { | ||
| 403 | # type hw | ||
| 404 | # card Loopback | ||
| 405 | # device 1 | ||
| 406 | # subdevice 1 | ||
| 407 | # format S32_LE | ||
| 408 | # rate 48000 | ||
| 409 | #} | ||
| 410 | |||
| 411 | # duplex device combining our PCM devices defined above | ||
| 412 | #pcm.aduplex { | ||
| 413 | # type asym | ||
| 414 | # playback.pcm "asoftvol" | ||
| 415 | # capture.pcm "loophw01" | ||
| 416 | #} | ||
| 417 | |||
| 418 | # default device | ||
| 419 | #pcm.!default { | ||
| 420 | # type plug | ||
| 421 | # slave.pcm aduplex | ||
| 422 | |||
| 423 | # hint { | ||
| 424 | # show on | ||
| 425 | # description "Duplex Loopback" | ||
| 426 | # } | ||
| 427 | #} | ||
| 428 | |||
| 429 | |||
| 430 | |||
| 431 | # Generic method seems to work better. | ||
| 432 | # playback PCM device: using loopback subdevice 0,0 | ||
| 433 | pcm.amix { | ||
| 434 | type dmix | ||
| 435 | ipc_key 219345 | ||
| 436 | slave.pcm "hw:Loopback,0,0" | ||
| 437 | } | ||
| 438 | |||
| 439 | # capture PCM device: using loopback subdevice 0,1 | ||
| 440 | pcm.asnoop { | ||
| 441 | type dsnoop | ||
| 442 | ipc_key 219346 | ||
| 443 | slave.pcm "hw:Loopback,0,1" | ||
| 444 | } | ||
| 445 | |||
| 446 | # duplex device combining our PCM devices defined above | ||
| 447 | pcm.aduplex { | ||
| 448 | type asym | ||
| 449 | playback.pcm "amix" | ||
| 450 | capture.pcm "asnoop" | ||
| 451 | } | ||
| 452 | |||
| 453 | # ------------------------------------------------------ | ||
| 454 | # for jack alsa_in and alsa_out: looped-back signal at other ends | ||
| 455 | pcm.ploop { | ||
| 456 | type plug | ||
| 457 | slave.pcm "hw:Loopback,1,1" | ||
| 458 | } | ||
| 459 | |||
| 460 | pcm.cloop { | ||
| 461 | type dsnoop | ||
| 462 | ipc_key 219348 | ||
| 463 | slave.pcm "hw:Loopback,1,0" | ||
| 464 | } | ||
| 465 | |||
| 466 | # ------------------------------------------------------ | ||
| 467 | # default device | ||
| 468 | |||
| 469 | pcm.!default { | ||
| 470 | type plug | ||
| 471 | slave.pcm "aduplex" | ||
| 472 | } | ||
| 473 | ]]) | ||
| 474 | a:close() | ||
| 475 | end | ||
| 476 | elseif 'JACK' == args[1] then | ||
| 477 | print('') | ||
| 478 | print("Start up JACK and friends.") | ||
| 479 | print("jack_control") | ||
| 480 | APT.exe('jack_control start'):Do() | ||
| 481 | APT.exe('jack_control ds alsa'):Do() | ||
| 482 | --jack_control dps device hw:RIG,0 | ||
| 483 | local r = APT.exe('jack_control status'):Do().status | ||
| 484 | while r ~= 0 do | ||
| 485 | -- if 0 ~= r then | ||
| 486 | print("Waiting for JACK - sleep 1") | ||
| 487 | APT.exe('sleep 1'):Do() | ||
| 488 | r = APT.exe('jack_control status'):Do().status | ||
| 489 | -- end | ||
| 490 | end | ||
| 491 | if nil ~= GUI then | ||
| 492 | print(GUI) | ||
| 493 | APT.exe(GUI):fork() | ||
| 494 | end | ||
| 495 | print("jack-plumbing") | ||
| 496 | APT.exe('jack-plumbing -o /var/lib/aataaj/jack-plumbing 2>/dev/null'):fork() | ||
| 497 | -- Bridge ALSA ports to JACK ports. Only handles MIDI. | ||
| 498 | -- 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)/ | ||
| 499 | --a2j_control actually starts a2jmidid. | ||
| 500 | ----a2jmidid -e -u & | ||
| 501 | -- 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. | ||
| 502 | print("a2j_control") | ||
| 503 | APT.exe('a2j_control --ehw && a2j_control --start'):Do() | ||
| 504 | print("sleep 2") | ||
| 505 | APT.exe('sleep 2'):Do() | ||
| 506 | print("") | ||
| 507 | |||
| 508 | |||
| 509 | --local AIN = "alsa_in" | ||
| 510 | local AIN = "zita-a2j" | ||
| 511 | --local AOUT = "alsa_out" | ||
| 512 | local AOUT = "zita-j2a" | ||
| 513 | |||
| 514 | print("Basic ALSA sound devices converted to JACK.") | ||
| 515 | for i,C in pairs(alias) do | ||
| 516 | print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['dev']) | ||
| 517 | APT.exe('alsa_out -j ' .. C['name'] .. ' -d ' .. C['dev']):fork() | ||
| 518 | end | ||
| 519 | print("HW playback: cloop\tDEVICE: cloop") | ||
| 520 | -- No idea why, cloop wont work with zita-a2j. | ||
| 521 | APT.exe('alsa_in -j cloop -d cloop'):fork() | ||
| 522 | --APT.exe('sleep 1'):Do() | ||
| 523 | --APT.exe('jack_connect cloop:capture_1 system:playback_1'):Do() | ||
| 524 | --APT.exe('jack_connect cloop:capture_2 system:playback_2'):Do() | ||
| 525 | print("HW playback: ploop\tDEVICE: ploop") | ||
| 526 | APT.exe('alsa_out -j ploop -d ploop'):fork() | ||
| 527 | --APT.exe('sleep 1'):Do() | ||
| 528 | --APT.exe('jack_connect system:capture_1 ploop:playback_1'):Do() | ||
| 529 | --APT.exe('jack_connect system:capture_2 ploop:playback_2'):Do() | ||
| 530 | |||
| 531 | |||
| 532 | print("") | ||
| 533 | |||
| 534 | print("Rest of ALSA sound devices converted to JACK.") | ||
| 535 | for i,C in pairs(Cards) do | ||
| 536 | for j,c in ipairs(C['playbackDevs']) do | ||
| 537 | print("HW playback: " .. C['name'] .. "\tDEVICE: " .. C['playbackDevs'][j]) | ||
| 538 | APT.exe(AOUT .. ' -j ' .. C['name'] .. "_" .. C['playbackDevs'][j] .. '-in -d ' .. C['name'] .. C['playbackDevs'][j]):fork() | ||
| 539 | -- APT.exe('sleep 1'):Do() | ||
| 540 | -- APT.exe('jack_connect cloop:capture_1 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_1'):Do() | ||
| 541 | -- APT.exe('jack_connect cloop:capture_2 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_2'):Do() | ||
| 542 | end | ||
| 543 | for j,c in ipairs(C['captureDevs']) do | ||
| 544 | print("HW capture: " .. C['name'] .. "\tDEVICE: " .. C['captureDevs'][j]) | ||
| 545 | APT.exe(AIN .. ' -j ' .. C['name'] .. "_" .. C['captureDevs'][j] .. '-out -d ' .. C['name'] .. C['captureDevs'][j]):fork() | ||
| 546 | end | ||
| 547 | end | ||
| 548 | print("") | ||
| 549 | |||
| 550 | print("Scanning for joysticks.") | ||
| 551 | local sticks = APT.exe('ls -1 /dev/input/js[0-9]*'):noErr():Do() | ||
| 552 | for i,l in ipairs(sticks.lines) do | ||
| 553 | print("aseqjoy " .. l) | ||
| 554 | -- Buttons switch to that numbered MIDI channel, defaults to 1. | ||
| 555 | -- Axis are mapped to MIDI controllers 10 - 15 | ||
| 556 | -- -r means to use high resolution MIDI values. | ||
| 557 | APT.exe('aseqjoy -d ' .. l:sub(-1,-1) .. ' -r'):fork() | ||
| 558 | end | ||
| 559 | |||
| 560 | print('qsynth') | ||
| 561 | APT.exe('qsynth'):fork() | ||
| 562 | |||
| 563 | print("") | ||
| 564 | |||
| 565 | print('Stop our jack-plumbing, eventually.') | ||
| 566 | APT.exe('sleep 4'):Do() | ||
| 567 | APT.exe("killall -TERM jack-plumbing"):Do() | ||
| 568 | end | ||
