Audio plugin host https://kx.studio/carla
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.

331 lines
13KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla plugin host (plugin UI)
  4. # Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or 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 GPL.txt file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Custom Stuff)
  19. from carla_host import *
  20. from externalui import ExternalUI
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Host Plugin object
  23. class PluginHost(CarlaHostPlugin):
  24. def __init__(self, sampleRate):
  25. CarlaHostPlugin.__init__(self)
  26. self.fIsRunning = True
  27. self.fSampleRate = sampleRate
  28. # -------------------------------------------------------------------
  29. def sendMsg(self, lines):
  30. # FIXME
  31. gCarla.gui.send(lines)
  32. return True
  33. # -------------------------------------------------------------------
  34. def engine_init(self, driverName, clientName):
  35. return True
  36. def engine_close(self):
  37. return True
  38. def engine_idle(self):
  39. if gCarla.gui.idleExternalUI():
  40. return
  41. self.fIsRunning = False
  42. gCarla.gui.d_uiQuit()
  43. def is_engine_running(self):
  44. return self.fIsRunning
  45. def set_engine_about_to_close(self):
  46. return
  47. # ------------------------------------------------------------------------------------------------------------
  48. # Main Window
  49. class CarlaMiniW(ExternalUI, HostWindow):
  50. def __init__(self):
  51. ExternalUI.__init__(self)
  52. HostWindow.__init__(self, None)
  53. if sys.argv[0].lower().endswith("/carla-plugin-patchbay"):
  54. from carla_patchbay import CarlaPatchbayW
  55. self.fContainer = CarlaPatchbayW(self)
  56. self.setupContainer(True, self.fContainer.themeData)
  57. else:
  58. from carla_rack import CarlaRackW
  59. self.fContainer = CarlaRackW(self)
  60. self.setupContainer(False)
  61. self.setWindowTitle(self.fUiName)
  62. self.showUiIfTesting()
  63. # -------------------------------------------------------------------
  64. # ExternalUI Callbacks
  65. def d_uiShow(self):
  66. self.show()
  67. def d_uiHide(self):
  68. self.hide()
  69. def d_uiQuit(self):
  70. self.close()
  71. app.quit()
  72. def d_uiTitleChanged(self, uiTitle):
  73. self.setWindowTitle(uiTitle)
  74. # -------------------------------------------------------------------
  75. # Qt events
  76. def closeEvent(self, event):
  77. self.closeExternalUI()
  78. HostWindow.closeEvent(self, event)
  79. # -------------------------------------------------------------------
  80. # Custom idler
  81. def idleExternalUI(self):
  82. while True:
  83. if self.fPipeRecv is None:
  84. return True
  85. try:
  86. msg = self.fPipeRecv.readline().strip()
  87. except IOError:
  88. return False
  89. if not msg:
  90. return True
  91. elif msg.startswith("PEAKS_"):
  92. pluginId = int(msg.replace("PEAKS_", ""))
  93. in1, in2, out1, out2 = [float(i) for i in self.readlineblock().split(":")]
  94. gCarla.host._set_peaks(pluginId, in1, in2, out1, out2)
  95. elif msg.startswith("PARAMVAL_"):
  96. pluginId, paramId = [int(i) for i in msg.replace("PARAMVAL_", "").split(":")]
  97. paramValue = float(self.readlineblock())
  98. gCarla.host._set_parameterValueS(pluginId, paramId, paramValue)
  99. elif msg.startswith("ENGINE_CALLBACK_"):
  100. action = int(msg.replace("ENGINE_CALLBACK_", ""))
  101. pluginId = int(self.readlineblock())
  102. value1 = int(self.readlineblock())
  103. value2 = int(self.readlineblock())
  104. value3 = float(self.readlineblock())
  105. valueStr = self.readlineblock().replace("\r", "\n")
  106. if action == ENGINE_CALLBACK_PLUGIN_RENAMED:
  107. gCarla.host._set_pluginName(pluginId, valueStr)
  108. elif action == ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED:
  109. if value1 < 0:
  110. gCarla.host._set_parameterValueInt(pluginId, value1, value3)
  111. else:
  112. gCarla.host._set_parameterValueS(pluginId, value1, value3)
  113. elif action == ENGINE_CALLBACK_PARAMETER_DEFAULT_CHANGED:
  114. gCarla.host._set_parameterDefault(pluginId, value1, value3)
  115. elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CC_CHANGED:
  116. gCarla.host._set_parameterMidiCC(pluginId, value1, value2)
  117. elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED:
  118. gCarla.host._set_parameterMidiChannel(pluginId, value1, value2)
  119. elif action == ENGINE_CALLBACK_PROGRAM_CHANGED:
  120. gCarla.host._set_currentProgram(pluginId, value1)
  121. elif action == ENGINE_CALLBACK_MIDI_PROGRAM_CHANGED:
  122. gCarla.host._set_currentMidiProgram(pluginId, value1)
  123. engineCallback(None, action, pluginId, value1, value2, value3, valueStr)
  124. elif msg.startswith("PLUGIN_INFO_"):
  125. pluginId = int(msg.replace("PLUGIN_INFO_", ""))
  126. gCarla.host._add(pluginId)
  127. type_, category, hints, uniqueId, optsAvail, optsEnabled = [int(i) for i in self.readlineblock().split(":")]
  128. filename = self.readlineblock().replace("\r", "\n")
  129. name = self.readlineblock().replace("\r", "\n")
  130. iconName = self.readlineblock().replace("\r", "\n")
  131. realName = self.readlineblock().replace("\r", "\n")
  132. label = self.readlineblock().replace("\r", "\n")
  133. maker = self.readlineblock().replace("\r", "\n")
  134. copyright = self.readlineblock().replace("\r", "\n")
  135. pinfo = {
  136. 'type': type_,
  137. 'category': category,
  138. 'hints': hints,
  139. 'optionsAvailable': optsAvail,
  140. 'optionsEnabled': optsEnabled,
  141. 'filename': filename,
  142. 'name': name,
  143. 'label': label,
  144. 'maker': maker,
  145. 'copyright': copyright,
  146. 'iconName': iconName,
  147. 'patchbayClientId': 0,
  148. 'uniqueId': uniqueId
  149. }
  150. gCarla.host._set_pluginInfo(pluginId, pinfo)
  151. gCarla.host._set_pluginRealName(pluginId, realName)
  152. elif msg.startswith("AUDIO_COUNT_"):
  153. pluginId, ins, outs = [int(i) for i in msg.replace("AUDIO_COUNT_", "").split(":")]
  154. gCarla.host._set_audioCountInfo(pluginId, {'ins': ins, 'outs': outs})
  155. elif msg.startswith("MIDI_COUNT_"):
  156. pluginId, ins, outs = [int(i) for i in msg.replace("MIDI_COUNT_", "").split(":")]
  157. gCarla.host._set_midiCountInfo(pluginId, {'ins': ins, 'outs': outs})
  158. elif msg.startswith("PARAMETER_COUNT_"):
  159. pluginId, ins, outs, count = [int(i) for i in msg.replace("PARAMETER_COUNT_", "").split(":")]
  160. gCarla.host._set_parameterCountInfo(pluginId, count, {'ins': ins, 'outs': outs})
  161. elif msg.startswith("PARAMETER_DATA_"):
  162. pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_DATA_", "").split(":")]
  163. paramType, paramHints, midiChannel, midiCC = [int(i) for i in self.readlineblock().split(":")]
  164. paramName = self.readlineblock().replace("\r", "\n")
  165. paramUnit = self.readlineblock().replace("\r", "\n")
  166. paramInfo = {
  167. 'name': paramName,
  168. 'symbol': "",
  169. 'unit': paramUnit,
  170. 'scalePointCount': 0,
  171. }
  172. gCarla.host._set_parameterInfoS(pluginId, paramId, paramInfo)
  173. paramData = {
  174. 'type': paramType,
  175. 'hints': paramHints,
  176. 'index': paramId,
  177. 'rindex': -1,
  178. 'midiCC': midiCC,
  179. 'midiChannel': midiChannel
  180. }
  181. gCarla.host._set_parameterDataS(pluginId, paramId, paramData)
  182. elif msg.startswith("PARAMETER_RANGES_"):
  183. pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_RANGES_", "").split(":")]
  184. def_, min_, max_, step, stepSmall, stepLarge = [float(i) for i in self.readlineblock().split(":")]
  185. paramRanges = {
  186. 'def': def_,
  187. 'min': min_,
  188. 'max': max_,
  189. 'step': step,
  190. 'stepSmall': stepSmall,
  191. 'stepLarge': stepLarge
  192. }
  193. gCarla.host._set_parameterRangeS(pluginId, paramId, paramRanges)
  194. elif msg.startswith("PROGRAM_COUNT_"):
  195. pluginId, count, current = [int(i) for i in msg.replace("PROGRAM_COUNT_", "").split(":")]
  196. gCarla.host._set_programCount(pluginId, count)
  197. gCarla.host._set_currentProgram(pluginId, current)
  198. elif msg.startswith("PROGRAM_NAME_"):
  199. pluginId, progId = [int(i) for i in msg.replace("PROGRAM_NAME_", "").split(":")]
  200. progName = self.readlineblock().replace("\r", "\n")
  201. gCarla.host._set_programNameS(pluginId, progId, progName)
  202. elif msg.startswith("MIDI_PROGRAM_COUNT_"):
  203. pluginId, count, current = [int(i) for i in msg.replace("MIDI_PROGRAM_COUNT_", "").split(":")]
  204. gCarla.host._set_midiProgramCount(pluginId, count)
  205. gCarla.host._set_currentMidiProgram(pluginId, current)
  206. elif msg.startswith("MIDI_PROGRAM_DATA_"):
  207. pluginId, midiProgId = [int(i) for i in msg.replace("MIDI_PROGRAM_DATA_", "").split(":")]
  208. bank, program = [int(i) for i in self.readlineblock().split(":")]
  209. name = self.readlineblock().replace("\r", "\n")
  210. gCarla.host._set_midiProgramDataS(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})
  211. elif msg == "error":
  212. error = self.readlineblock().replace("\r", "\n")
  213. engineCallback(None, ENGINE_CALLBACK_ERROR, 0, 0, 0, 0.0, error)
  214. elif msg == "show":
  215. self.d_uiShow()
  216. elif msg == "hide":
  217. self.d_uiHide()
  218. elif msg == "quit":
  219. self.fQuitReceived = True
  220. self.d_uiQuit()
  221. elif msg == "uiTitle":
  222. uiTitle = self.readlineblock().replace("\r", "\n")
  223. self.d_uiTitleChanged(uiTitle)
  224. else:
  225. print("unknown message: \"" + msg + "\"")
  226. return True
  227. # ------------------------------------------------------------------------------------------------------------
  228. # Main
  229. if __name__ == '__main__':
  230. # -------------------------------------------------------------
  231. # App initialization
  232. app = CarlaApplication("Carla2-Plugin")
  233. # -------------------------------------------------------------
  234. # Set-up custom signal handling
  235. setUpSignals()
  236. # -------------------------------------------------------------
  237. # Init plugin host data
  238. gCarla.isControl = False
  239. gCarla.isLocal = True
  240. gCarla.isPlugin = True
  241. gCarla.processMode = ENGINE_PROCESS_MODE_CONTINUOUS_RACK
  242. # -------------------------------------------------------------
  243. # Create GUI first
  244. gCarla.gui = CarlaMiniW()
  245. # -------------------------------------------------------------
  246. # Init plugin host now
  247. gCarla.host = PluginHost(gCarla.gui.d_getSampleRate())
  248. initHost("Carla-Plugin")
  249. # simulate an engire started callback
  250. engineCallback(None, ENGINE_CALLBACK_ENGINE_STARTED, 0, ENGINE_PROCESS_MODE_CONTINUOUS_RACK, ENGINE_TRANSPORT_MODE_PLUGIN, 0.0, "Plugin")
  251. # -------------------------------------------------------------
  252. # App-Loop
  253. ret = app.exec_()
  254. # disable parenting
  255. gCarla.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0")
  256. sys.exit(ret)