Collection of tools useful for audio production
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
7.2KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Cadence ALSA-Loop daemon
  4. # Copyright (C) 2012-2018 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the COPYING file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import os
  20. import sys
  21. from signal import signal, SIGINT, SIGTERM
  22. from time import sleep
  23. if True:
  24. from PyQt5.QtCore import QProcess
  25. else:
  26. from PyQt4.QtCore import QProcess
  27. # ------------------------------------------------------------------------------------------------------------
  28. # Imports (Custom Stuff)
  29. import jacklib
  30. # --------------------------------------------------
  31. # Auto re-activate if on good kernel
  32. global reactivateCounter
  33. reactivateCounter = -1
  34. isKernelGood = [int(i) for i in os.uname()[2].split("-", 1)[0].split(".", 2)[:2]] >= [3,8]
  35. # --------------------------------------------------
  36. # Global loop check
  37. global doLoop, doRunNow, useZita, procIn, procOut
  38. doLoop = True
  39. doRunNow = True
  40. useZita = False
  41. procIn = QProcess()
  42. procOut = QProcess()
  43. checkFile = "/tmp/.cadence-aloop-daemon.x"
  44. # --------------------------------------------------
  45. # Global JACK variables
  46. global bufferSize, sampleRate, channels
  47. bufferSize = 1024
  48. sampleRate = 44100
  49. channels = 2
  50. # --------------------------------------------------
  51. # quit on SIGINT or SIGTERM
  52. def signal_handler(sig, frame=0):
  53. global doLoop
  54. doLoop = False
  55. # --------------------------------------------------
  56. # listen to jack buffer-size and sample-rate changes
  57. def buffer_size_callback(newBufferSize, arg):
  58. global doRunNow, bufferSize
  59. bufferSize = newBufferSize
  60. doRunNow = True
  61. return 0
  62. def sample_rate_callback(newSampleRate, arg):
  63. global doRunNow, sampleRate
  64. sampleRate = newSampleRate
  65. doRunNow = True
  66. return 0
  67. # --------------------------------------------------
  68. # listen to jack2 master switch
  69. def client_registration_callback(clientName, register, arg):
  70. global doLoop, doRunNow, reactivateCounter, useZita
  71. if clientName in (b"alsa2jack", b"jack2alsa") and not register:
  72. if doRunNow or not doLoop:
  73. return
  74. if isKernelGood:
  75. if reactivateCounter == -1:
  76. reactivateCounter = 0
  77. print("NOTICE: %s has been stopped, waiting 5 secs to reactivate" % ("zita-a2j/j2a" if useZita else "alsa_in/out"))
  78. elif doLoop:
  79. doLoop = False
  80. print("NOTICE: %s has been stopped, quitting now..." % ("zita-a2j/j2a" if useZita else "alsa_in/out"))
  81. # --------------------------------------------------
  82. # listen to jack shutdown
  83. def shutdown_callback(arg):
  84. global doLoop
  85. doLoop = False
  86. # --------------------------------------------------
  87. # run alsa_in and alsa_out
  88. def run_alsa_bridge():
  89. global reactivateCounter
  90. global bufferSize, sampleRate, channels
  91. global procIn, procOut
  92. global useZita
  93. if procIn.state() != QProcess.NotRunning:
  94. procIn.terminate()
  95. procIn.waitForFinished(1000)
  96. if procOut.state() != QProcess.NotRunning:
  97. procOut.terminate()
  98. procOut.waitForFinished(1000)
  99. reactivateCounter = -1
  100. if useZita:
  101. 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])
  102. 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])
  103. else:
  104. 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])
  105. 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])
  106. if procIn.waitForStarted():
  107. sleep(1)
  108. jacklib.connect(client, "alsa2jack:capture_1", "system:playback_1")
  109. jacklib.connect(client, "alsa2jack:capture_2", "system:playback_2")
  110. if procOut.waitForStarted():
  111. sleep(1)
  112. jacklib.connect(client, "system:capture_1", "jack2alsa:playback_1")
  113. jacklib.connect(client, "system:capture_2", "jack2alsa:playback_2")
  114. #--------------- main ------------------
  115. if __name__ == '__main__':
  116. for i in range(len(sys.argv)):
  117. if i == 0: continue
  118. argv = sys.argv[i]
  119. if argv == "--zita":
  120. useZita = True
  121. elif argv.startswith("--channels="):
  122. chStr = argv.replace("--channels=", "")
  123. if chStr.isdigit():
  124. channels = int(chStr)
  125. # Init JACK client
  126. client = jacklib.client_open("cadence-aloop-daemon", jacklib.JackUseExactName, None)
  127. if not client:
  128. print("cadence-aloop-daemon is already running, delete \"/tmp/.cadence-aloop-daemon.x\" to close it")
  129. quit()
  130. if jacklib.JACK2:
  131. jacklib.set_client_registration_callback(client, client_registration_callback, None)
  132. jacklib.set_buffer_size_callback(client, buffer_size_callback, None)
  133. jacklib.set_sample_rate_callback(client, sample_rate_callback, None)
  134. jacklib.on_shutdown(client, shutdown_callback, None)
  135. jacklib.activate(client)
  136. # Quit when requested
  137. signal(SIGINT, signal_handler)
  138. signal(SIGTERM, signal_handler)
  139. # Get initial values
  140. sampleRate = jacklib.get_sample_rate(client)
  141. bufferSize = jacklib.get_buffer_size(client)
  142. # Create check file
  143. if not os.path.exists(checkFile):
  144. os.mknod(checkFile)
  145. # Keep running until told otherwise
  146. firstStart = True
  147. while doLoop and os.path.exists(checkFile):
  148. if doRunNow:
  149. if firstStart:
  150. firstStart = False
  151. print("cadence-aloop-daemon started, using %s and %i channels" % ("zita-a2j/j2a" if useZita else "alsa_in/out", channels))
  152. run_alsa_bridge()
  153. doRunNow = False
  154. elif isKernelGood and reactivateCounter >= 0:
  155. if reactivateCounter == 5:
  156. reactivateCounter = -1
  157. doRunNow = True
  158. else:
  159. reactivateCounter += 1
  160. sleep(1)
  161. # Close JACK client
  162. jacklib.deactivate(client)
  163. jacklib.client_close(client)
  164. if os.path.exists(checkFile):
  165. os.remove(checkFile)
  166. if procIn.state() != QProcess.NotRunning:
  167. procIn.terminate()
  168. procIn.waitForFinished(1000)
  169. if procOut.state() != QProcess.NotRunning:
  170. procOut.terminate()
  171. procOut.waitForFinished(1000)