From 7c2c1f51065aad51d3ac33f5d268e7d9b3665f69 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 3 Oct 2012 18:21:09 +0100 Subject: [PATCH] Cadence: Initial code for ALSA-Audio support --- src/cadence.py | 308 +++++++++++++++++++++++++++++++----- src/cadence_aloop_daemon.py | 54 ++++--- src/ui/cadence.ui | 14 +- 3 files changed, 309 insertions(+), 67 deletions(-) diff --git a/src/cadence.py b/src/cadence.py index f0725dd..35c42d8 100755 --- a/src/cadence.py +++ b/src/cadence.py @@ -47,16 +47,6 @@ if haveWine: # --------------------------------------------------------------------- -# jackdbus indexes -iGraphVersion = 0 -iJackClientId = 1 -iJackClientName = 2 -iJackPortId = 3 -iJackPortName = 4 -iJackPortNewName = 5 -iJackPortFlags = 5 -iJackPortType = 6 - DESKTOP_X_IMAGE = [ "eog.desktop", "kde4/digikam.desktop", @@ -97,6 +87,165 @@ WINEASIO_PREFIX = "HKEY_CURRENT_USER\Software\Wine\WineASIO" # --------------------------------------------------------------------- +global jackClientIdALSA, jackClientIdPulse +jackClientIdALSA = -1 +jackClientIdPulse = -1 + +iAlsaFileNone = 0 +iAlsaFileLoop = 1 +iAlsaFileJACK = 2 +iAlsaFilePulse = 3 +iAlsaFileMax = 4 + +# jackdbus indexes +iGraphVersion = 0 +iJackClientId = 1 +iJackClientName = 2 +iJackPortId = 3 +iJackPortName = 4 +iJackPortNewName = 5 +iJackPortFlags = 5 +iJackPortType = 6 + +asoundrc_aloop = ("" +"# ------------------------------------------------------\n" +"# Custom asoundrc file for use with snd-aloop and JACK\n" +"#\n" +"# use it like this:\n" +"# env JACK_SAMPLE_RATE=44100 JACK_PERIOD_SIZE=1024 alsa_in (...)\n" +"#\n" +"\n" +"# ------------------------------------------------------\n" +"# playback device\n" +"pcm.aloopPlayback {\n" +" type dmix\n" +" ipc_key 1\n" +" ipc_key_add_uid true\n" +" slave {\n" +" pcm \"hw:Loopback,0,0\"\n" +" format S32_LE\n" +" rate {\n" +" @func igetenv\n" +" vars [ JACK_SAMPLE_RATE ]\n" +" default 44100\n" +" }\n" +" period_size {\n" +" @func igetenv\n" +" vars [ JACK_PERIOD_SIZE ]\n" +" default 1024\n" +" }\n" +" buffer_size 4096\n" +" }\n" +"}\n" +"\n" +"# capture device\n" +"pcm.aloopCapture {\n" +" type dsnoop\n" +" ipc_key 2\n" +" ipc_key_add_uid true\n" +" slave {\n" +" pcm \"hw:Loopback,0,1\"\n" +" format S32_LE\n" +" rate {\n" +" @func igetenv\n" +" vars [ JACK_SAMPLE_RATE ]\n" +" default 44100\n" +" }\n" +" period_size {\n" +" @func igetenv\n" +" vars [ JACK_PERIOD_SIZE ]\n" +" default 1024\n" +" }\n" +" buffer_size 4096\n" +" }\n" +"}\n" +"\n" +"# duplex device\n" +"pcm.aloopDuplex {\n" +" type asym\n" +" playback.pcm \"aloopPlayback\"\n" +" capture.pcm \"aloopCapture\"\n" +"}\n" +"\n" +"# ------------------------------------------------------\n" +"# default device\n" +"pcm.!default {\n" +" type plug\n" +" slave.pcm \"aloopDuplex\"\n" +"}\n" +"\n" +"# ------------------------------------------------------\n" +"# alsa_in -j alsa_in -dcloop -q 1\n" +"pcm.cloop {\n" +" type dsnoop\n" +" ipc_key 3\n" +" ipc_key_add_uid true\n" +" slave {\n" +" pcm \"hw:Loopback,1,0\"\n" +" format S32_LE\n" +" rate {\n" +" @func igetenv\n" +" vars [ JACK_SAMPLE_RATE ]\n" +" default 44100\n" +" }\n" +" period_size {\n" +" @func igetenv\n" +" vars [ JACK_PERIOD_SIZE ]\n" +" default 1024\n" +" }\n" +" buffer_size 4096\n" +" }\n" +"}\n" +"\n" +"# ------------------------------------------------------\n" +"# alsa_out -j alsa_out -dploop -q 1\n" +"pcm.ploop {\n" +" type plug\n" +" slave {\n" +" pcm \"hw:Loopback,1,1\"\n" +" }\n" +"}") + +asoundrc_jack = ("" +"pcm.!default {\n" +" type plug\n" +" slave { pcm \"jack\" }\n" +"}\n" +"\n" +"pcm.jack {\n" +" type jack\n" +" playback_ports {\n" +" 0 system:playback_1\n" +" 1 system:playback_2\n" +" }\n" +" capture_ports {\n" +" 0 system:capture_1\n" +" 1 system:capture_2\n" +" }\n" +"}\n" +"\n" +"ctl.mixer0 {\n" +" type hw\n" +" card 0\n" +"}") + +asoundrc_pulse = ("" +"pcm.!default {\n" +" type plug\n" +" slave { pcm \"pulse\" }\n" +"}\n" +"\n" +"pcm.pulse {\n" +" type pulse\n" +"}\n" +"\n" +"ctl.mixer0 {\n" +" type hw\n" +" card 0\n" +"}") + +# --------------------------------------------------------------------- + def get_architecture(): return architecture()[0] @@ -151,6 +300,17 @@ def get_windows_information(): # --------------------------------------------------------------------- +def isAlsaAudioBridged(): + global jackClientIdALSA + return bool(jackClientIdALSA != -1) + +def isPulseAudioStarted(): + return bool("pulseaudio" in getProcList()) + +def isPulseAudioBridged(): + global jackClientIdPulse + return bool(jackClientIdPulse != -1) + def isDesktopFileInstalled(desktop): for X_PATH in XDG_APPLICATIONS_PATH: if os.path.exists(os.path.join(X_PATH, desktop)): @@ -218,16 +378,6 @@ def smartHex(value, length): return hexStr -global PA_clientId -PA_clientId = -1 - -def PA_is_started(): - return bool("pulseaudio" in getProcList()) - -def PA_is_bridged(): - global PA_clientId - return bool(PA_clientId != -1) - # --------------------------------------------------------------------- cadenceSystemChecks = [] @@ -386,7 +536,7 @@ class ForceRestartThread(QThread): self.emit(SIGNAL("progressChanged(int)"), 96) - if GlobalSettings.value("Pulse2JACK/AutoStart", True, type=bool) and not PA_is_bridged(): + if GlobalSettings.value("Pulse2JACK/AutoStart", True, type=bool) and not isPulseAudioBridged(): if GlobalSettings.value("Pulse2JACK/PlaybackModeOnly", False, type=bool): os.system("cadence-pulse2jack -p") else: @@ -505,9 +655,6 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): QMainWindow.__init__(self, parent) self.setupUi(self) - # TODO - uninplemented - self.toolBox_alsaaudio.setEnabled(False) - self.settings = QSettings("Cadence", "Cadence") self.loadSettings(True) @@ -751,6 +898,9 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.addAction("jack_stop", self.tr("Stop JACK")) self.systray.addAction("jack_configure", self.tr("Configure JACK")) self.systray.addSeparator("sep1") + self.systray.addMenu("alsa", self.tr("ALSA Audio Bridge")) + self.systray.addMenuAction("alsa", "alsa_start", self.tr("Start")) + self.systray.addMenuAction("alsa", "alsa_stop", self.tr("Stop")) self.systray.addMenu("a2j", self.tr("ALSA MIDI Bridge")) self.systray.addMenuAction("a2j", "a2j_start", self.tr("Start")) self.systray.addMenuAction("a2j", "a2j_stop", self.tr("Stop")) @@ -773,6 +923,8 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.setActionIcon("jack_start", "media-playback-start") self.systray.setActionIcon("jack_stop", "media-playback-stop") self.systray.setActionIcon("jack_configure", "configure") + self.systray.setActionIcon("alsa_start", "media-playback-start") + self.systray.setActionIcon("alsa_stop", "media-playback-stop") self.systray.setActionIcon("a2j_start", "media-playback-start") self.systray.setActionIcon("a2j_stop", "media-playback-stop") self.systray.setActionIcon("pulse_start", "media-playback-start") @@ -781,6 +933,8 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.connect("jack_start", self.slot_JackServerStart) self.systray.connect("jack_stop", self.slot_JackServerStop) self.systray.connect("jack_configure", self.slot_JackServerConfigure) + self.systray.connect("alsa_start", self.slot_AlsaBridgeStart) + self.systray.connect("alsa_stop", self.slot_AlsaBridgeStop) self.systray.connect("a2j_start", self.slot_A2JBridgeStart) self.systray.connect("a2j_stop", self.slot_A2JBridgeStop) self.systray.connect("a2j_export_hw", self.slot_A2JBridgeExportHW) @@ -807,6 +961,9 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.connect(self.b_jack_configure, SIGNAL("clicked()"), SLOT("slot_JackServerConfigure()")) self.connect(self.tb_jack_options, SIGNAL("clicked()"), SLOT("slot_JackOptions()")) + self.connect(self.b_alsa_start, SIGNAL("clicked()"), SLOT("slot_AlsaBridgeStart()")) + self.connect(self.b_alsa_stop, SIGNAL("clicked()"), SLOT("slot_AlsaBridgeStop()")) + self.connect(self.b_a2j_start, SIGNAL("clicked()"), SLOT("slot_A2JBridgeStart()")) self.connect(self.b_a2j_stop, SIGNAL("clicked()"), SLOT("slot_A2JBridgeStop()")) self.connect(self.b_a2j_export_hw, SIGNAL("clicked()"), SLOT("slot_A2JBridgeExportHW()")) @@ -906,10 +1063,12 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): version, groups, conns = DBus.patchbay.GetGraph(0) for group_id, group_name, ports in groups: - if group_name == "PulseAudio JACK Sink": - global PA_clientId - PA_clientId = group_id - break + if group_name == "alsa2jack": + global jackClientIdALSA + jackClientIdALSA = group_id + elif group_name == "PulseAudio JACK Sink": + global jackClientIdPulse + jackClientIdPulse = group_id self.jackStarted() @@ -931,6 +1090,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.setActionEnabled("jack_start", False) self.systray.setActionEnabled("jack_stop", False) self.systray.setActionEnabled("jack_configure", False) + self.groupBox_bridges.setEnabled(False) if DBus.a2j: if DBus.a2j.is_started(): @@ -1010,6 +1170,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.b_a2j_start.setEnabled(True) self.systray.setActionEnabled("a2j_start", True) + self.checkAlsaAudio() self.checkPulseAudio() def jackStopped(self): @@ -1039,8 +1200,10 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.b_a2j_start.setEnabled(False) self.systray.setActionEnabled("a2j_start", False) - global PA_clientId - PA_clientId = -1 + global jackClientIdALSA, jackClientIdPulse + jackClientIdALSA = -1 + jackClientIdPulse = -1 + self.checkAlsaAudio() self.checkPulseAudio() def a2jStarted(self): @@ -1062,10 +1225,60 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.setActionEnabled("a2j_export_hw", True) self.label_bridge_a2j.setText(self.tr("ALSA MIDI Bridge is stopped")) + def checkAlsaAudio(self): + asoundrcFile = os.path.join(HOME, ".asoundrc") + + if not os.path.exists(asoundrcFile): + self.b_alsa_start.setEnabled(False) + self.b_alsa_stop.setEnabled(False) + self.cb_alsa_type.setCurrentIndex(iAlsaFileNone) + self.tb_alsa_options.setEnabled(False) + self.label_bridge_alsa.setText(self.tr("No bridge in use")) + return + + asoundrcFd = open(asoundrcFile, "r") + asoundrcRead = asoundrcFd.read().strip() + asoundrcFd.close() + + if asoundrcRead == asoundrc_aloop: + if isAlsaAudioBridged(): + self.b_alsa_start.setEnabled(False) + self.b_alsa_stop.setEnabled(True) + self.label_bridge_alsa.setText(self.tr("Using Cadence snd-aloop daemon, started")) + else: + jackRunning = bool(DBus.jack and DBus.jack.IsStarted()) + self.b_alsa_start.setEnabled(jackRunning) + self.b_alsa_stop.setEnabled(False) + self.label_bridge_alsa.setText(self.tr("Using Cadence snd-aloop daemon, stopped")) + + self.cb_alsa_type.setCurrentIndex(iAlsaFileLoop) + self.tb_alsa_options.setEnabled(False) + + elif asoundrcRead == asoundrc_jack: + self.b_alsa_start.setEnabled(False) + self.b_alsa_stop.setEnabled(False) + self.cb_alsa_type.setCurrentIndex(iAlsaFileJACK) + self.tb_alsa_options.setEnabled(False) + self.label_bridge_alsa.setText(self.tr("Using JACK plugin bridge")) + + elif asoundrcRead == asoundrc_pulse: + self.b_alsa_start.setEnabled(False) + self.b_alsa_stop.setEnabled(False) + self.cb_alsa_type.setCurrentIndex(iAlsaFilePulse) + self.tb_alsa_options.setEnabled(False) + self.label_bridge_alsa.setText(self.tr("Using PulseAudio plugin bridge")) + + else: + self.b_alsa_start.setEnabled(False) + self.b_alsa_stop.setEnabled(False) + self.cb_alsa_type.addItem(self.tr("Custom")) + self.cb_alsa_type.setCurrentIndex(iAlsaFileMax) + self.tb_alsa_options.setEnabled(True) + self.label_bridge_alsa.setText(self.tr("Using custom asoundrc, not managed by Cadence")) + def checkPulseAudio(self): - if PA_is_started(): - global PA_clientId - if PA_clientId != -1: + if isPulseAudioStarted(): + if isPulseAudioBridged(): self.b_pulse_start.setEnabled(False) self.b_pulse_stop.setEnabled(True) self.systray.setActionEnabled("pulse_start", False) @@ -1136,16 +1349,23 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): @pyqtSlot(int, str) def slot_DBusJackClientAppearedCallback(self, group_id, group_name): - if group_name == "PulseAudio JACK Sink": - global PA_clientId - PA_clientId = group_id + if group_name == "alsa2jack": + global jackClientIdALSA + jackClientIdALSA = group_id + self.checkAlsaAudio() + elif group_name == "PulseAudio JACK Sink": + global jackClientIdPulse + jackClientIdPulse = group_id self.checkPulseAudio() @pyqtSlot(int) def slot_DBusJackClientDisappearedCallback(self, group_id): - global PA_clientId - if group_id == PA_clientId: - PA_clientId = -1 + global jackClientIdALSA, jackClientIdPulse + if group_id == jackClientIdALSA: + jackClientIdALSA = -1 + self.checkAlsaAudio() + elif group_id == jackClientIdPulse: + jackClientIdPulse = -1 self.checkPulseAudio() @pyqtSlot() @@ -1203,6 +1423,16 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): if DBus.jack: DBus.jack.ResetXruns() + @pyqtSlot() + def slot_AlsaBridgeStart(self): + self.func_start_tool("cadence-aloop-daemon") + + @pyqtSlot() + def slot_AlsaBridgeStop(self): + checkFile = "/tmp/.cadence-aloop-daemon.x" + if os.path.exists(checkFile): + os.remove(checkFile) + @pyqtSlot() def slot_A2JBridgeStart(self): DBus.a2j.start() diff --git a/src/cadence_aloop_daemon.py b/src/cadence_aloop_daemon.py index 51b7336..19005e5 100755 --- a/src/cadence_aloop_daemon.py +++ b/src/cadence_aloop_daemon.py @@ -2,11 +2,10 @@ # -*- coding: utf-8 -*- # Imports (Global) -from ctypes import * -from os import path, system -from sys import version_info +import os from signal import signal, SIGINT, SIGTERM from time import sleep +from PyQt4.QtCore import QProcess # Imports (Custom Stuff) import jacklib @@ -14,9 +13,12 @@ import jacklib # -------------------------------------------------- # Global loop check -global doLoop, doRunNow -doLoop = True -doRunNow = True +global doLoop, doRunNow, procIn, procOut +doLoop = True +doRunNow = True +procIn = QProcess() +procOut = QProcess() +checkFile = "/tmp/.cadence-aloop-daemon.x" # -------------------------------------------------- # Global JACK variables @@ -51,24 +53,22 @@ def shutdown_callback(arg): # -------------------------------------------------- # run alsa_in and alsa_out def run_alsa_bridge(): - global sample_rate, buffer_size - killList = "alsa_in alsa_out zita-a2j zita-j2a" + global bufferSize, sampleRate + global procIn, procOut - # On KXStudio, pulseaudio is nicely bridged & configured to work with JACK - if not path.exists("/usr/share/kxstudio/config/pulse/daemon.conf"): - killList += " pulseaudio" + if procIn.state() != QProcess.NotRunning: + procIn.terminate() + if procOut.state() != QProcess.NotRunning: + procOut.terminate() - system("killall %s" % killList) + procIn.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "alsa_in", "-j", "alsa2jack", "-d", "cloop", "-q", "1"]) + procOut.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "alsa_out", "-j", "jack2alsa", "-d", "ploop", "-q", "1"]) - #if (False): - system("env JACK_SAMPLE_RATE=%i JACK_PERIOD_SIZE=%i alsa_in -j alsa2jack -d cloop -q 1 2>&1 1> /dev/null &" % (sampleRate, bufferSize)) - system("env JACK_SAMPLE_RATE=%i JACK_PERIOD_SIZE=%i alsa_out -j jack2alsa -d ploop -q 1 2>&1 1> /dev/null &" % (sampleRate, bufferSize)) - #else: - #system("env JACK_SAMPLE_RATE=%i JACK_PERIOD_SIZE=%i zita-a2j -j alsa2jack -d hw:Loopback,1,0 -r 44100 &" % (sample_rate, buffer_size)) - #system("env JACK_SAMPLE_RATE=%i JACK_PERIOD_SIZE=%i zita-j2a -j jack2alsa -d hw:Loopback,1,1 -r 44100 &" % (sample_rate, buffer_size)) + procIn.waitForStarted() + procOut.waitForStarted() # Pause it for a bit, and connect to the system ports - sleep(2) + sleep(1) jacklib.connect(client, "alsa2jack:capture_1", "system:playback_1") jacklib.connect(client, "alsa2jack:capture_2", "system:playback_2") jacklib.connect(client, "system:capture_1", "jack2alsa:playback_1") @@ -78,7 +78,7 @@ def run_alsa_bridge(): if __name__ == '__main__': # Init JACK client - client = jacklib.client_open("cadence-aloop-daemon", 0, None) + client = jacklib.client_open("cadence-aloop-daemon", jacklib.JackUseExactName, None) if not client: quit() @@ -95,8 +95,12 @@ if __name__ == '__main__': sampleRate = jacklib.get_sample_rate(client) bufferSize = jacklib.get_buffer_size(client) + # Create check file + if not os.path.exists(checkFile): + os.mknod(checkFile) + # Keep running until told otherwise - while doLoop: + while doLoop and os.path.exists(checkFile): if doRunNow: doRunNow = False run_alsa_bridge() @@ -105,3 +109,11 @@ if __name__ == '__main__': # Close JACK client jacklib.deactivate(client) jacklib.client_close(client) + + if os.path.exists(checkFile): + os.remove(checkFile) + + if procIn.state() != QProcess.NotRunning: + procIn.terminate() + if procOut.state() != QProcess.NotRunning: + procOut.terminate() diff --git a/src/ui/cadence.ui b/src/ui/cadence.ui index 0fed4e4..5b01cba 100644 --- a/src/ui/cadence.ui +++ b/src/ui/cadence.ui @@ -531,14 +531,14 @@ - + Start - + Stop @@ -575,7 +575,7 @@ - + (None) @@ -583,23 +583,23 @@ - ALSA -> JACK (Plugin) + ALSA -> Loop -> JACK - ALSA -> PulseAudio -> JACK (Plugin) + ALSA -> JACK (Plugin) - ALSA -> Loop -> JACK + ALSA -> PulseAudio -> JACK (Plugin) - + ...