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