221 lines
7.3 KiB
Python
Executable File
221 lines
7.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Cadence ALSA-Loop daemon
|
|
# Copyright (C) 2012-2018 Filipe Coelho <falktx@falktx.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# For a full copy of the GNU General Public License see the COPYING file
|
|
|
|
# ------------------------------------------------------------------------------------------------------------
|
|
# Imports (Global)
|
|
|
|
import os
|
|
import sys
|
|
from signal import signal, SIGINT, SIGTERM
|
|
from time import sleep
|
|
|
|
if True:
|
|
from PyQt5.QtCore import QProcess
|
|
else:
|
|
from PyQt4.QtCore import QProcess
|
|
|
|
# ------------------------------------------------------------------------------------------------------------
|
|
# Imports (Custom Stuff)
|
|
|
|
import jacklib
|
|
from shared import getDaemonLockfile
|
|
|
|
# --------------------------------------------------
|
|
# Auto re-activate if on good kernel
|
|
|
|
global reactivateCounter
|
|
reactivateCounter = -1
|
|
isKernelGood = [int(i) for i in os.uname()[2].split("-", 1)[0].split(".", 2)[:2]] >= [3,8]
|
|
|
|
# --------------------------------------------------
|
|
# Global loop check
|
|
|
|
global doLoop, doRunNow, useZita, procIn, procOut
|
|
doLoop = True
|
|
doRunNow = True
|
|
useZita = False
|
|
procIn = QProcess()
|
|
procOut = QProcess()
|
|
checkFile = getDaemonLockfile("cadence-aloop-daemon")
|
|
|
|
# --------------------------------------------------
|
|
# Global JACK variables
|
|
|
|
global bufferSize, sampleRate, channels
|
|
bufferSize = 1024
|
|
sampleRate = 44100
|
|
channels = 2
|
|
|
|
# --------------------------------------------------
|
|
# quit on SIGINT or SIGTERM
|
|
|
|
def signal_handler(sig, frame=0):
|
|
global doLoop
|
|
doLoop = False
|
|
|
|
# --------------------------------------------------
|
|
# listen to jack buffer-size and sample-rate changes
|
|
|
|
def buffer_size_callback(newBufferSize, arg):
|
|
global doRunNow, bufferSize
|
|
bufferSize = newBufferSize
|
|
doRunNow = True
|
|
return 0
|
|
|
|
def sample_rate_callback(newSampleRate, arg):
|
|
global doRunNow, sampleRate
|
|
sampleRate = newSampleRate
|
|
doRunNow = True
|
|
return 0
|
|
|
|
# --------------------------------------------------
|
|
# listen to jack2 master switch
|
|
|
|
def client_registration_callback(clientName, register, arg):
|
|
global doLoop, doRunNow, reactivateCounter, useZita
|
|
|
|
if clientName in (b"alsa2jack", b"jack2alsa") and not register:
|
|
if doRunNow or not doLoop:
|
|
return
|
|
|
|
if isKernelGood:
|
|
if reactivateCounter == -1:
|
|
reactivateCounter = 0
|
|
print("NOTICE: %s has been stopped, waiting 5 secs to reactivate" % ("zita-a2j/j2a" if useZita else "alsa_in/out"))
|
|
elif doLoop:
|
|
doLoop = False
|
|
print("NOTICE: %s has been stopped, quitting now..." % ("zita-a2j/j2a" if useZita else "alsa_in/out"))
|
|
|
|
# --------------------------------------------------
|
|
# listen to jack shutdown
|
|
|
|
def shutdown_callback(arg):
|
|
global doLoop
|
|
doLoop = False
|
|
|
|
# --------------------------------------------------
|
|
# run alsa_in and alsa_out
|
|
def run_alsa_bridge():
|
|
global reactivateCounter
|
|
global bufferSize, sampleRate, channels
|
|
global procIn, procOut
|
|
global useZita
|
|
|
|
if procIn.state() != QProcess.NotRunning:
|
|
procIn.terminate()
|
|
procIn.waitForFinished(1000)
|
|
if procOut.state() != QProcess.NotRunning:
|
|
procOut.terminate()
|
|
procOut.waitForFinished(1000)
|
|
|
|
reactivateCounter = -1
|
|
|
|
if useZita:
|
|
procIn.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "zita-a2j", "-d", "hw:Loopback,1,0", "-r", "%i" % sampleRate, "-p", "%i" % bufferSize, "-j", "alsa2jack", "-c", "%i" % channels])
|
|
procOut.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "zita-j2a", "-d", "hw:Loopback,1,1", "-r", "%i" % sampleRate, "-p", "%i" % bufferSize, "-j", "jack2alsa", "-c", "%i" % channels])
|
|
else:
|
|
procIn.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "alsa_in", "-d", "cloop", "%i" % sampleRate, "-p", "%i" % bufferSize, "-j", "alsa2jack", "-q", "1", "-c", "%i" % channels])
|
|
procOut.start("env", ["JACK_SAMPLE_RATE=%i" % sampleRate, "JACK_PERIOD_SIZE=%i" % bufferSize, "alsa_out", "-d", "ploop", "%i" % sampleRate, "-p", "%i" % bufferSize, "-j", "jack2alsa", "-q", "1", "-c", "%i" % channels])
|
|
|
|
if procIn.waitForStarted():
|
|
sleep(1)
|
|
jacklib.connect(client, "alsa2jack:capture_1", "system:playback_1")
|
|
jacklib.connect(client, "alsa2jack:capture_2", "system:playback_2")
|
|
|
|
if procOut.waitForStarted():
|
|
sleep(1)
|
|
jacklib.connect(client, "system:capture_1", "jack2alsa:playback_1")
|
|
jacklib.connect(client, "system:capture_2", "jack2alsa:playback_2")
|
|
|
|
#--------------- main ------------------
|
|
if __name__ == '__main__':
|
|
|
|
for i in range(len(sys.argv)):
|
|
if i == 0: continue
|
|
|
|
argv = sys.argv[i]
|
|
|
|
if argv == "--zita":
|
|
useZita = True
|
|
elif argv.startswith("--channels="):
|
|
chStr = argv.replace("--channels=", "")
|
|
|
|
if chStr.isdigit():
|
|
channels = int(chStr)
|
|
|
|
# Init JACK client
|
|
client = jacklib.client_open("cadence-aloop-daemon", jacklib.JackUseExactName, None)
|
|
|
|
if not client:
|
|
print("cadence-aloop-daemon is already running, delete \"{}\" to close it".format(checkFile))
|
|
quit()
|
|
|
|
if jacklib.JACK2:
|
|
jacklib.set_client_registration_callback(client, client_registration_callback, None)
|
|
|
|
jacklib.set_buffer_size_callback(client, buffer_size_callback, None)
|
|
jacklib.set_sample_rate_callback(client, sample_rate_callback, None)
|
|
jacklib.on_shutdown(client, shutdown_callback, None)
|
|
jacklib.activate(client)
|
|
|
|
# Quit when requested
|
|
signal(SIGINT, signal_handler)
|
|
signal(SIGTERM, signal_handler)
|
|
|
|
# Get initial values
|
|
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
|
|
firstStart = True
|
|
while doLoop and os.path.exists(checkFile):
|
|
if doRunNow:
|
|
if firstStart:
|
|
firstStart = False
|
|
print("cadence-aloop-daemon started, using %s and %i channels" % ("zita-a2j/j2a" if useZita else "alsa_in/out", channels))
|
|
|
|
run_alsa_bridge()
|
|
doRunNow = False
|
|
|
|
elif isKernelGood and reactivateCounter >= 0:
|
|
if reactivateCounter == 5:
|
|
reactivateCounter = -1
|
|
doRunNow = True
|
|
else:
|
|
reactivateCounter += 1
|
|
|
|
sleep(1)
|
|
|
|
# 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()
|
|
procIn.waitForFinished(1000)
|
|
if procOut.state() != QProcess.NotRunning:
|
|
procOut.terminate()
|
|
procOut.waitForFinished(1000)
|