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.

carla_control.py 34KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla OSC controller
  4. # Copyright (C) 2011-2013 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 (Global)
  19. from PyQt4.QtGui import QApplication, QInputDialog, QMainWindow
  20. from liblo import make_method, Address, ServerError, ServerThread
  21. from liblo import send as lo_send
  22. from liblo import TCP as LO_TCP
  23. # ------------------------------------------------------------------------------------------------------------
  24. # Imports (Custom)
  25. import ui_carla_control
  26. from carla_shared import *
  27. global lo_target, lo_targetName
  28. lo_target = None
  29. lo_targetName = ""
  30. Carla.isControl = True
  31. # ------------------------------------------------------------------------------------------------------------
  32. # Python Object dicts compatible to carla-backend struct ctypes
  33. MidiProgramData = {
  34. 'bank': 0,
  35. 'program': 0,
  36. 'label': None
  37. }
  38. ParameterData = {
  39. 'type': PARAMETER_NULL,
  40. 'index': 0,
  41. 'rindex': -1,
  42. 'hints': 0x0,
  43. 'midiChannel': 0,
  44. 'midiCC': -1
  45. }
  46. ParameterRanges = {
  47. 'def': 0.0,
  48. 'min': 0.0,
  49. 'max': 1.0,
  50. 'step': 0.0,
  51. 'stepSmall': 0.0,
  52. 'stepLarge': 0.0
  53. }
  54. CustomData = {
  55. 'type': CUSTOM_DATA_INVALID,
  56. 'key': None,
  57. 'value': None
  58. }
  59. PluginInfo = {
  60. 'type': PLUGIN_NONE,
  61. 'category': PLUGIN_CATEGORY_NONE,
  62. 'hints': 0x0,
  63. 'binary': None,
  64. 'name': None,
  65. 'label': None,
  66. 'maker': None,
  67. 'copyright': None,
  68. 'uniqueId': 0
  69. }
  70. PortCountInfo = {
  71. 'ins': 0,
  72. 'outs': 0,
  73. 'total': 0
  74. }
  75. ParameterInfo = {
  76. 'name': None,
  77. 'symbol': None,
  78. 'unit': None,
  79. 'scalePointCount': 0,
  80. }
  81. ScalePointInfo = {
  82. 'value': 0.0,
  83. 'label': None
  84. }
  85. class ControlPluginInfo(object):
  86. __slots__ = [
  87. 'pluginInfo',
  88. 'pluginRealName',
  89. 'audioCountInfo',
  90. 'midiCountInfo',
  91. 'parameterCountInfo',
  92. 'parameterInfoS',
  93. 'parameterDataS',
  94. 'parameterRangeS',
  95. 'parameterValueS',
  96. 'programCount',
  97. 'programCurrent',
  98. 'programNameS',
  99. 'midiProgramCount',
  100. 'midiProgramCurrent',
  101. 'midiProgramDataS',
  102. 'inPeak',
  103. 'outPeak'
  104. ]
  105. # ------------------------------------------------------------------------------------------------------------
  106. # Python Object class compatible to 'Host' on the Carla Backend code
  107. class Host(object):
  108. def __init__(self):
  109. object.__init__(self)
  110. self.pluginInfo = []
  111. def _clear(self, index):
  112. self.pluginInfo[index].pluginInfo = PluginInfo
  113. self.pluginInfo[index].pluginRealName = None
  114. self.pluginInfo[index].audioCountInfo = PortCountInfo
  115. self.pluginInfo[index].midiCountInfo = PortCountInfo
  116. self.pluginInfo[index].parameterCountInfo = PortCountInfo
  117. self.pluginInfo[index].parameterInfoS = []
  118. self.pluginInfo[index].parameterDataS = []
  119. self.pluginInfo[index].parameterRangeS = []
  120. self.pluginInfo[index].parameterValueS = []
  121. self.pluginInfo[index].programCount = 0
  122. self.pluginInfo[index].programCurrent = -1
  123. self.pluginInfo[index].programNameS = []
  124. self.pluginInfo[index].midiProgramCount = 0
  125. self.pluginInfo[index].midiProgramCurrent = -1
  126. self.pluginInfo[index].midiProgramDataS = []
  127. self.pluginInfo[index].inPeak = [0.0, 0.0]
  128. self.pluginInfo[index].outPeak = [0.0, 0.0]
  129. def _set_pluginInfo(self, index, info):
  130. self.pluginInfo[index].pluginInfo = info
  131. def _set_pluginRealName(self, index, realName):
  132. self.pluginInfo[index].pluginRealName = realName
  133. def _set_audioCountInfo(self, index, info):
  134. self.pluginInfo[index].audioCountInfo = info
  135. def _set_midiCountInfo(self, index, info):
  136. self.pluginInfo[index].midiCountInfo = info
  137. def _set_parameterCountInfo(self, index, info):
  138. self.pluginInfo[index].parameterCountInfo = info
  139. # clear
  140. self.pluginInfo[index].parameterInfoS = []
  141. self.pluginInfo[index].parameterDataS = []
  142. self.pluginInfo[index].parameterRangeS = []
  143. self.pluginInfo[index].parameterValueS = []
  144. # add placeholders
  145. for x in range(info['total']):
  146. self.pluginInfo[index].parameterInfoS.append(ParameterInfo)
  147. self.pluginInfo[index].parameterDataS.append(ParameterData)
  148. self.pluginInfo[index].parameterRangeS.append(ParameterRanges)
  149. self.pluginInfo[index].parameterValueS.append(0.0)
  150. def _set_programCount(self, index, count):
  151. self.pluginInfo[index].programCount = count
  152. # clear
  153. self.pluginInfo[index].programNameS = []
  154. # add placeholders
  155. for x in range(count):
  156. self.pluginInfo[index].programNameS.append(None)
  157. def _set_midiProgramCount(self, index, count):
  158. self.pluginInfo[index].midiProgramCount = count
  159. # clear
  160. self.pluginInfo[index].midiProgramDataS = []
  161. # add placeholders
  162. for x in range(count):
  163. self.pluginInfo[index].midiProgramDataS.append(MidiProgramData)
  164. def _set_parameterInfoS(self, index, paramIndex, data):
  165. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  166. self.pluginInfo[index].parameterInfoS[paramIndex] = data
  167. def _set_parameterDataS(self, index, paramIndex, data):
  168. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  169. self.pluginInfo[index].parameterDataS[paramIndex] = data
  170. def _set_parameterRangeS(self, index, paramIndex, data):
  171. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  172. self.pluginInfo[index].parameterRangeS[paramIndex] = data
  173. def _set_parameterValueS(self, index, paramIndex, value):
  174. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  175. self.pluginInfo[index].parameterValueS[paramIndex] = value
  176. def _set_parameterDefaultValue(self, index, paramIndex, value):
  177. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  178. self.pluginInfo[index].parameterRangeS[paramIndex]['def'] = value
  179. def _set_parameterMidiCC(self, index, paramIndex, cc):
  180. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  181. self.pluginInfo[index].parameterDataS[paramIndex]['midiCC'] = cc
  182. def _set_parameterMidiChannel(self, index, paramIndex, channel):
  183. if paramIndex < self.pluginInfo[index].parameterCountInfo['total']:
  184. self.pluginInfo[index].parameterDataS[paramIndex]['midiChannel'] = channel
  185. def _set_currentProgram(self, index, pIndex):
  186. self.pluginInfo[index].programCurrent = pIndex
  187. def _set_currentMidiProgram(self, index, mpIndex):
  188. self.pluginInfo[index].midiProgramCurrent = mpIndex
  189. def _set_programNameS(self, index, pIndex, data):
  190. if pIndex < self.pluginInfo[index].programCount:
  191. self.pluginInfo[index].programNameS[pIndex] = data
  192. def _set_midiProgramDataS(self, index, mpIndex, data):
  193. if mpIndex < self.pluginInfo[index].midiProgramCount:
  194. self.pluginInfo[index].midiProgramDataS[mpIndex] = data
  195. def _set_inPeak(self, index, port, value):
  196. self.pluginInfo[index].inPeak[port] = value
  197. def _set_outPeak(self, index, port, value):
  198. self.pluginInfo[index].outPeak[port] = value
  199. def get_plugin_info(self, plugin_id):
  200. return self.pluginInfo[plugin_id].pluginInfo
  201. def get_audio_port_count_info(self, plugin_id):
  202. return self.pluginInfo[plugin_id].audioCountInfo
  203. def get_midi_port_count_info(self, plugin_id):
  204. return self.pluginInfo[plugin_id].midiCountInfo
  205. def get_parameter_count_info(self, plugin_id):
  206. return self.pluginInfo[plugin_id].parameterCountInfo
  207. def get_parameter_info(self, plugin_id, parameter_id):
  208. return self.pluginInfo[plugin_id].parameterInfoS[parameter_id]
  209. def get_parameter_scalepoint_info(self, plugin_id, parameter_id, scalepoint_id):
  210. return ScalePointInfo
  211. def get_parameter_data(self, plugin_id, parameter_id):
  212. return self.pluginInfo[plugin_id].parameterDataS[parameter_id]
  213. def get_parameter_ranges(self, plugin_id, parameter_id):
  214. return self.pluginInfo[plugin_id].parameterRangeS[parameter_id]
  215. def get_midi_program_data(self, plugin_id, midi_program_id):
  216. return self.pluginInfo[plugin_id].midiProgramDataS[midi_program_id]
  217. def get_custom_data(self, plugin_id, custom_data_id):
  218. return CustomData
  219. def get_chunk_data(self, plugin_id):
  220. return None
  221. def get_parameter_count(self, plugin_id):
  222. return self.pluginInfo[plugin_id].parameterCountInfo['total']
  223. def get_program_count(self, plugin_id):
  224. return self.pluginInfo[plugin_id].programCount
  225. def get_midi_program_count(self, plugin_id):
  226. return self.pluginInfo[plugin_id].midiProgramCount
  227. def get_custom_data_count(self, plugin_id):
  228. return 0
  229. def get_parameter_text(self, plugin_id, program_id):
  230. return None
  231. def get_program_name(self, plugin_id, program_id):
  232. return self.pluginInfo[plugin_id].programNameS[program_id]
  233. def get_midi_program_name(self, plugin_id, midi_program_id):
  234. return self.pluginInfo[plugin_id].midiProgramDataS[midi_program_id]['label']
  235. def get_real_plugin_name(self, plugin_id):
  236. return self.pluginInfo[plugin_id].pluginRealName
  237. def get_current_program_index(self, plugin_id):
  238. return self.pluginInfo[plugin_id].programCurrent
  239. def get_current_midi_program_index(self, plugin_id):
  240. return self.pluginInfo[plugin_id].midiProgramCurrent
  241. def get_default_parameter_value(self, plugin_id, parameter_id):
  242. return self.pluginInfo[plugin_id].parameterRangeS[parameter_id]['def']
  243. def get_current_parameter_value(self, plugin_id, parameter_id):
  244. return self.pluginInfo[plugin_id].parameterValueS[parameter_id]
  245. def get_input_peak_value(self, plugin_id, port_id):
  246. return self.pluginInfo[plugin_id].inPeak[port_id-1]
  247. def get_output_peak_value(self, plugin_id, port_id):
  248. return self.pluginInfo[plugin_id].outPeak[port_id-1]
  249. def set_active(self, plugin_id, onoff):
  250. global to_target, lo_targetName
  251. lo_path = "/%s/%i/set_active" % (lo_targetName, plugin_id)
  252. lo_send(lo_target, lo_path, 1 if onoff else 0)
  253. def set_drywet(self, plugin_id, value):
  254. global to_target, lo_targetName
  255. lo_path = "/%s/%i/set_drywet" % (lo_targetName, plugin_id)
  256. lo_send(lo_target, lo_path, value)
  257. def set_volume(self, plugin_id, value):
  258. global to_target, lo_targetName
  259. lo_path = "/%s/%i/set_volume" % (lo_targetName, plugin_id)
  260. lo_send(lo_target, lo_path, value)
  261. def set_balance_left(self, plugin_id, value):
  262. global to_target, lo_targetName
  263. lo_path = "/%s/%i/set_balance_left" % (lo_targetName, plugin_id)
  264. lo_send(lo_target, lo_path, value)
  265. def set_balance_right(self, plugin_id, value):
  266. global to_target, lo_targetName
  267. lo_path = "/%s/%i/set_balance_right" % (lo_targetName, plugin_id)
  268. lo_send(lo_target, lo_path, value)
  269. def set_parameter_value(self, plugin_id, parameter_id, value):
  270. global to_target, lo_targetName
  271. lo_path = "/%s/%i/set_parameter_value" % (lo_targetName, plugin_id)
  272. lo_send(lo_target, lo_path, parameter_id, value)
  273. def set_parameter_midi_channel(self, plugin_id, parameter_id, channel):
  274. global to_target, lo_targetName
  275. lo_path = "/%s/%i/set_parameter_midi_channel" % (lo_targetName, plugin_id)
  276. lo_send(lo_target, lo_path, parameter_id, channel)
  277. def set_parameter_midi_cc(self, plugin_id, parameter_id, midi_cc):
  278. global to_target, lo_targetName
  279. lo_path = "/%s/%i/set_parameter_midi_cc" % (lo_targetName, plugin_id)
  280. lo_send(lo_target, lo_path, parameter_id, midi_cc)
  281. def set_program(self, plugin_id, program_id):
  282. global to_target, lo_targetName
  283. lo_path = "/%s/%i/set_program" % (lo_targetName, plugin_id)
  284. lo_send(lo_target, lo_path, program_id)
  285. def set_midi_program(self, plugin_id, midi_program_id):
  286. global to_target, lo_targetName
  287. lo_path = "/%s/%i/set_midi_program" % (lo_targetName, plugin_id)
  288. lo_send(lo_target, lo_path, midi_program_id)
  289. def send_midi_note(self, plugin_id, channel, note, velocity):
  290. global to_target, lo_targetName
  291. if velocity:
  292. lo_path = "/%s/%i/note_on" % (lo_targetName, plugin_id)
  293. lo_send(lo_target, lo_path, channel, note, velocity)
  294. else:
  295. lo_path = "/%s/%i/note_off" % (lo_targetName, plugin_id)
  296. lo_send(lo_target, lo_path, channel, note)
  297. # ------------------------------------------------------------------------------------------------------------
  298. # OSC Control server
  299. class ControlServer(ServerThread):
  300. def __init__(self, parent):
  301. ServerThread.__init__(self, 8087, LO_TCP)
  302. self.parent = parent
  303. def getFullURL(self):
  304. return "%scarla-control" % self.get_url()
  305. @make_method('/carla-control/add_plugin_start', 'is')
  306. def add_plugin_start_callback(self, path, args):
  307. pluginId, pluginName = args
  308. self.parent.emit(SIGNAL("AddPluginStart(int, QString)"), pluginId, pluginName)
  309. @make_method('/carla-control/add_plugin_end', 'i')
  310. def add_plugin_end_callback(self, path, args):
  311. pluginId, = args
  312. self.parent.emit(SIGNAL("AddPluginEnd(int)"), pluginId)
  313. @make_method('/carla-control/remove_plugin', 'i')
  314. def remove_plugin_callback(self, path, args):
  315. pluginId, = args
  316. self.parent.emit(SIGNAL("RemovePlugin(int)"), pluginId)
  317. @make_method('/carla-control/set_plugin_data', 'iiiissssh')
  318. def set_plugin_data_callback(self, path, args):
  319. pluginId, ptype, category, hints, realName, label, maker, copyright_, uniqueId = args
  320. self.parent.emit(SIGNAL("SetPluginData(int, int, int, int, QString, QString, QString, QString, int)"), pluginId, ptype, category, hints, realName, label, maker, copyright_, uniqueId)
  321. @make_method('/carla-control/set_plugin_ports', 'iiiiiiii')
  322. def set_plugin_ports_callback(self, path, args):
  323. pluginId, audioIns, audioOuts, midiIns, midiOuts, cIns, cOuts, cTotals = args
  324. self.parent.emit(SIGNAL("SetPluginPorts(int, int, int, int, int, int, int, int)"), pluginId, audioIns, audioOuts, midiIns, midiOuts, cIns, cOuts, cTotals)
  325. @make_method('/carla-control/set_parameter_data', 'iiiissd')
  326. def set_parameter_data_callback(self, path, args):
  327. pluginId, index, type_, hints, name, label, current = args
  328. self.parent.emit(SIGNAL("SetParameterData(int, int, int, int, QString, QString, double)"), pluginId, index, type_, hints, name, label, current)
  329. @make_method('/carla-control/set_parameter_ranges', 'iidddddd')
  330. def set_parameter_ranges_callback(self, path, args):
  331. pluginId, index, min_, max_, def_, step, stepSmall, stepLarge = args
  332. self.parent.emit(SIGNAL("SetParameterRanges(int, int, double, double, double, double, double, double)"), pluginId, index, min_, max_, def_, step, stepSmall, stepLarge)
  333. @make_method('/carla-control/set_parameter_midi_cc', 'iii')
  334. def set_parameter_midi_cc_callback(self, path, args):
  335. pluginId, index, cc = args
  336. self.parent.emit(SIGNAL("SetParameterMidiCC(int, int, int)"), pluginId, index, cc)
  337. @make_method('/carla-control/set_parameter_midi_channel', 'iii')
  338. def set_parameter_midi_channel_callback(self, path, args):
  339. pluginId, index, channel = args
  340. self.parent.emit(SIGNAL("SetParameterMidiChannel(int, int, int)"), pluginId, index, channel)
  341. @make_method('/carla-control/set_parameter_value', 'iid')
  342. def set_parameter_value_callback(self, path, args):
  343. pluginId, index, value = args
  344. self.parent.emit(SIGNAL("SetParameterValue(int, int, double)"), pluginId, index, value)
  345. @make_method('/carla-control/set_default_value', 'iid')
  346. def set_default_value_callback(self, path, args):
  347. pluginId, index, value = args
  348. self.parent.emit(SIGNAL("SetDefaultValue(int, int, double)"), pluginId, index, value)
  349. @make_method('/carla-control/set_program', 'ii')
  350. def set_program_callback(self, path, args):
  351. pluginId, index = args
  352. self.parent.emit(SIGNAL("SetProgram(int, int)"), pluginId, index)
  353. @make_method('/carla-control/set_program_count', 'ii')
  354. def set_program_count_callback(self, path, args):
  355. pluginId, count = args
  356. self.parent.emit(SIGNAL("SetProgramCount(int, int)"), pluginId, count)
  357. @make_method('/carla-control/set_program_name', 'iis')
  358. def set_program_name_callback(self, path, args):
  359. pluginId, index, name = args
  360. self.parent.emit(SIGNAL("SetProgramName(int, int, QString)"), pluginId, index, name)
  361. @make_method('/carla-control/set_midi_program', 'ii')
  362. def set_midi_program_callback(self, path, args):
  363. pluginId, index = args
  364. self.parent.emit(SIGNAL("SetMidiProgram(int, int)"), pluginId, index)
  365. @make_method('/carla-control/set_midi_program_count', 'ii')
  366. def set_midi_program_count_callback(self, path, args):
  367. pluginId, count = args
  368. self.parent.emit(SIGNAL("SetMidiProgramCount(int, int)"), pluginId, count)
  369. @make_method('/carla-control/set_midi_program_data', 'iiiis')
  370. def set_midi_program_data_callback(self, path, args):
  371. pluginId, index, bank, program, name = args
  372. self.parent.emit(SIGNAL("SetMidiProgramData(int, int, int, int, QString)"), pluginId, index, bank, program, name)
  373. @make_method('/carla-control/set_input_peak_value', 'iid')
  374. def set_input_peak_value_callback(self, path, args):
  375. pluginId, portId, value = args
  376. self.parent.emit(SIGNAL("SetInputPeakValue(int, int, double)"), pluginId, portId, value)
  377. @make_method('/carla-control/set_output_peak_value', 'iid')
  378. def set_output_peak_value_callback(self, path, args):
  379. pluginId, portId, value = args
  380. self.parent.emit(SIGNAL("SetOutputPeakValue(int, int, double)"), pluginId, portId, value)
  381. @make_method('/carla-control/note_on', 'iiii')
  382. def note_on_callback(self, path, args):
  383. pluginId, channel, note, velo = args
  384. self.parent.emit(SIGNAL("NoteOn(int, int, int, int)"), pluginId, channel, note, velo)
  385. @make_method('/carla-control/note_off', 'iii')
  386. def note_off_callback(self, path, args):
  387. pluginId, channel, note = args
  388. self.parent.emit(SIGNAL("NoteOff(int, int, int)"), pluginId, channel, note)
  389. @make_method('/carla-control/exit', '')
  390. def exit_callback(self, path, args):
  391. self.parent.emit(SIGNAL("Exit()"))
  392. @make_method(None, None)
  393. def fallback(self, path, args):
  394. print("ControlServer::fallback(\"%s\") - unknown message, args =" % path, args)
  395. # ------------------------------------------------------------------------------------------------------------
  396. # Main Window
  397. class CarlaControlW(QMainWindow):
  398. def __init__(self, parent=None):
  399. QMainWindow.__init__(self, parent)
  400. self.ui = ui_carla_control.Ui_CarlaControlW
  401. self.ui.setupUi(self)
  402. # -------------------------------------------------------------
  403. # Load Settings
  404. self.loadSettings()
  405. self.setStyleSheet("""
  406. QWidget#centralwidget {
  407. background-color: qlineargradient(spread:pad,
  408. x1:0.0, y1:0.0,
  409. x2:0.2, y2:1.0,
  410. stop:0 rgb( 7, 7, 7),
  411. stop:1 rgb(28, 28, 28)
  412. );
  413. }
  414. """)
  415. # -------------------------------------------------------------
  416. # Internal stuff
  417. self.lo_address = ""
  418. self.lo_server = None
  419. self.fLastPluginName = ""
  420. self.fPluginCount = 0
  421. self.fPluginList = []
  422. self.fIdleTimerFast = 0
  423. self.fIdleTimerSlow = 0
  424. # -------------------------------------------------------------
  425. # Set-up GUI stuff
  426. self.ui.act_file_refresh.setEnabled(False)
  427. self.resize(self.width(), 0)
  428. # -------------------------------------------------------------
  429. # Connect actions to functions
  430. self.connect(self.ui.act_file_connect, SIGNAL("triggered()"), SLOT("slot_fileConnect()"))
  431. self.connect(self.ui.act_file_refresh, SIGNAL("triggered()"), SLOT("slot_fileRefresh()"))
  432. self.connect(self.ui.act_help_about, SIGNAL("triggered()"), SLOT("slot_aboutCarlaControl()"))
  433. self.connect(self.ui.act_help_about_qt, SIGNAL("triggered()"), app, SLOT("aboutQt()"))
  434. self.connect(self, SIGNAL("AddPluginStart(int, QString)"), SLOT("slot_handleAddPluginStart(int, QString)"))
  435. self.connect(self, SIGNAL("AddPluginEnd(int)"), SLOT("slot_handleAddPluginEnd(int)"))
  436. self.connect(self, SIGNAL("RemovePlugin(int)"), SLOT("slot_handleRemovePlugin(int)"))
  437. self.connect(self, SIGNAL("SetPluginData(int, int, int, int, QString, QString, QString, QString, int)"), SLOT("slot_handleSetPluginData(int, int, int, int, QString, QString, QString, QString, int)"))
  438. self.connect(self, SIGNAL("SetPluginPorts(int, int, int, int, int, int, int, int)"), SLOT("slot_handleSetPluginPorts(int, int, int, int, int, int, int, int)"))
  439. self.connect(self, SIGNAL("SetParameterData(int, int, int, int, QString, QString, double)"), SLOT("slot_handleSetParameterData(int, int, int, int, QString, QString, double)"))
  440. self.connect(self, SIGNAL("SetParameterRanges(int, int, double, double, double, double, double, double)"), SLOT("slot_handleSetParameterRanges(int, int, double, double, double, double, double, double)"))
  441. self.connect(self, SIGNAL("SetParameterMidiCC(int, int, int)"), SLOT("slot_handleSetParameterMidiCC(int, int, int)"))
  442. self.connect(self, SIGNAL("SetParameterMidiChannel(int, int, int)"), SLOT("slot_handleSetParameterMidiChannel(int, int, int)"))
  443. self.connect(self, SIGNAL("SetParameterValue(int, int, double)"), SLOT("slot_handleSetParameterValue(int, int, double)"))
  444. self.connect(self, SIGNAL("SetDefaultValue(int, int, double)"), SLOT("slot_handleSetDefaultValue(int, int, double)"))
  445. self.connect(self, SIGNAL("SetProgram(int, int)"), SLOT("slot_handleSetProgram(int, int)"))
  446. self.connect(self, SIGNAL("SetProgramCount(int, int)"), SLOT("slot_handleSetProgramCount(int, int)"))
  447. self.connect(self, SIGNAL("SetProgramName(int, int, QString)"), SLOT("slot_handleSetProgramName(int, int, QString)"))
  448. self.connect(self, SIGNAL("SetMidiProgram(int, int)"), SLOT("slot_handleSetMidiProgram(int, int)"))
  449. self.connect(self, SIGNAL("SetMidiProgramCount(int, int)"), SLOT("slot_handleSetMidiProgramCount(int, int)"))
  450. self.connect(self, SIGNAL("SetMidiProgramData(int, int, int, int, QString)"), SLOT("slot_handleSetMidiProgramData(int, int, int, int, QString)"))
  451. self.connect(self, SIGNAL("SetInputPeakValue(int, int, double)"), SLOT("slot_handleSetInputPeakValue(int, int, double)"))
  452. self.connect(self, SIGNAL("SetOutputPeakValue(int, int, double)"), SLOT("slot_handleSetOutputPeakValue(int, int, double)"))
  453. self.connect(self, SIGNAL("NoteOn(int, int, int, int)"), SLOT("slot_handleNoteOn(int, int, int, int)"))
  454. self.connect(self, SIGNAL("NoteOff(int, int, int)"), SLOT("slot_handleNoteOff(int, int, int)"))
  455. self.connect(self, SIGNAL("Exit()"), SLOT("slot_handleExit()"))
  456. # Peaks
  457. self.fIdleTimerFast = self.startTimer(50)
  458. # LEDs and edit dialog parameters
  459. self.fIdleTimerSlow = self.startTimer(50*2)
  460. def removeAll(self):
  461. for i in range(self.fPluginCount):
  462. self.slot_handleRemovePlugin(i)
  463. @pyqtSlot()
  464. def slot_fileConnect(self):
  465. global lo_target, lo_targetName
  466. if lo_target and self.lo_server:
  467. urlText = self.lo_address
  468. else:
  469. urlText = "osc.tcp://127.0.0.1:19000/Carla"
  470. askValue = QInputDialog.getText(self, self.tr("Carla Control - Connect"), self.tr("Address"), text=urlText)
  471. if not askValue[1]:
  472. return
  473. self.slot_handleExit()
  474. self.lo_address = askValue[0]
  475. lo_target = Address(self.lo_address)
  476. lo_targetName = self.lo_address.rsplit("/", 1)[-1]
  477. print("Connecting to \"%s\" as '%s'..." % (self.lo_address, lo_targetName))
  478. try:
  479. self.lo_server = ControlServer(self)
  480. except: # ServerError, err:
  481. print("Connecting error!")
  482. #print str(err)
  483. QMessageBox.critical(self, self.tr("Error"), self.tr("Failed to connect, operation failed."))
  484. if self.lo_server:
  485. self.lo_server.start()
  486. self.ui.act_file_refresh.setEnabled(True)
  487. lo_send(lo_target, "/register", self.lo_server.getFullURL())
  488. @pyqtSlot()
  489. def slot_fileRefresh(self):
  490. global lo_target
  491. if lo_target and self.lo_server:
  492. self.removeAll()
  493. lo_send(lo_target, "/unregister")
  494. lo_send(lo_target, "/register", self.lo_server.getFullURL())
  495. @pyqtSlot()
  496. def slot_aboutCarlaControl(self):
  497. CarlaAboutW(self).exec_()
  498. @pyqtSlot(int, str)
  499. def slot_handleAddPluginStart(self, pluginId, pluginName):
  500. self.m_lastPluginName = pluginName
  501. @pyqtSlot(int)
  502. def slot_handleAddPluginEnd(self, pluginId):
  503. pwidget = PluginWidget(self, pluginId)
  504. pwidget.setRefreshRate(50)
  505. self.ui.w_plugins.layout().addWidget(pwidget)
  506. self.fPluginCount += 1
  507. self.fPluginList.append(pwidget)
  508. @pyqtSlot(int)
  509. def slot_handleRemovePlugin(self, pluginId):
  510. pwidget = self.fPluginList[pluginId]
  511. if pwidget is None:
  512. return
  513. self.fPluginList[pluginId] = None
  514. self.fPluginCount -= 1
  515. self.ui.w_plugins.layout().removeWidget(pwidget)
  516. pwidget.ui.edit_dialog.close()
  517. pwidget.close()
  518. pwidget.deleteLater()
  519. del pwidget
  520. # push all plugins 1 slot back
  521. for i in range(pluginId, self.fPluginCount):
  522. self.fPluginList[i] = self.fPluginList[i+1]
  523. self.fPluginList[i].setId(i)
  524. # TODO - move Carla.host.* stuff too
  525. @pyqtSlot(int, int, int, int, str, str, str, str, int)
  526. def slot_handleSetPluginData(self, pluginId, type_, category, hints, realName, label, maker, copyright, uniqueId):
  527. info = deepcopy(PluginInfo)
  528. info['type'] = type_
  529. info['category'] = category
  530. info['hints'] = hints
  531. info['name'] = self.m_lastPluginName
  532. info['label'] = label
  533. info['maker'] = maker
  534. info['copyright'] = copyright
  535. info['uniqueId'] = uniqueId
  536. Carla.host._set_pluginInfo(pluginId, info)
  537. Carla.host._set_pluginRealName(pluginId, realName)
  538. @pyqtSlot(int, int, int, int, int, int, int, int)
  539. def slot_handleSetPluginPorts(self, pluginId, audioIns, audioOuts, midiIns, midiOuts, cIns, cOuts, cTotals):
  540. audioInfo = deepcopy(PortCountInfo)
  541. midiInfo = deepcopy(PortCountInfo)
  542. paramInfo = deepcopy(PortCountInfo)
  543. audioInfo['ins'] = audioIns
  544. audioInfo['outs'] = audioOuts
  545. audioInfo['total'] = audioIns + audioOuts
  546. midiInfo['ins'] = midiIns
  547. midiInfo['outs'] = midiOuts
  548. midiInfo['total'] = midiIns + midiOuts
  549. paramInfo['ins'] = cIns
  550. paramInfo['outs'] = cOuts
  551. paramInfo['total'] = cTotals
  552. Carla.host._set_audioCountInfo(pluginId, audioInfo)
  553. Carla.host._set_midiCountInfo(pluginId, midiInfo)
  554. Carla.host._set_parameterCountInfo(pluginId, paramInfo)
  555. @pyqtSlot(int, int, int, int, str, str, float)
  556. def slot_handleSetParameterData(self, pluginId, index, type_, hints, name, label, current):
  557. # remove hints not possible in bridge mode
  558. hints &= ~(PARAMETER_USES_SCALEPOINTS | PARAMETER_USES_CUSTOM_TEXT)
  559. data = deepcopy(ParameterData)
  560. data['type'] = type_
  561. data['index'] = index
  562. data['rindex'] = index
  563. data['hints'] = hints
  564. info = deepcopy(ParameterInfo)
  565. info['name'] = name
  566. info['label'] = label
  567. Carla.host._set_parameterDataS(pluginId, index, data)
  568. Carla.host._set_parameterInfoS(pluginId, index, info)
  569. Carla.host._set_parameterValueS(pluginId, index, current)
  570. @pyqtSlot(int, int, float, float, float, float, float, float)
  571. def slot_handleSetParameterRanges(self, pluginId, index, min_, max_, def_, step, stepSmall, stepLarge):
  572. ranges = deepcopy(ParameterRanges)
  573. ranges['min'] = min_
  574. ranges['max'] = max_
  575. ranges['def'] = def_
  576. ranges['step'] = step
  577. ranges['stepSmall'] = stepSmall
  578. ranges['stepLarge'] = stepLarge
  579. Carla.host._set_parameterRangeS(pluginId, index, ranges)
  580. @pyqtSlot(int, int, float)
  581. def slot_handleSetParameterValue(self, pluginId, parameterId, value):
  582. if parameterId >= 0:
  583. Carla.host._set_parameterValueS(pluginId, parameterId, value)
  584. pwidget = self.fPluginList[pluginId]
  585. if pwidget is None:
  586. return
  587. pwidget.setParameterValue(parameterId, value)
  588. @pyqtSlot(int, int, float)
  589. def slot_handleSetDefaultValue(self, pluginId, parameterId, value):
  590. Carla.host._set_parameterDefaultValue(pluginId, parameterId, value)
  591. pwidget = self.fPluginList[pluginId]
  592. if pwidget is None:
  593. return
  594. pwidget.setParameterDefault(parameterId, value)
  595. @pyqtSlot(int, int, int)
  596. def slot_handleSetParameterMidiCC(self, pluginId, index, cc):
  597. Carla.host._set_parameterMidiCC(pluginId, index, cc)
  598. pwidget = self.fPluginList[pluginId]
  599. if pwidget is None:
  600. return
  601. pwidget.setParameterMidiControl(index, cc)
  602. @pyqtSlot(int, int, int)
  603. def slot_handleSetParameterMidiChannel(self, pluginId, index, channel):
  604. Carla.host._set_parameterMidiChannel(pluginId, index, channel)
  605. pwidget = self.fPluginList[pluginId]
  606. if pwidget is None:
  607. return
  608. pwidget.setParameterMidiChannel(index, channel)
  609. @pyqtSlot(int, int)
  610. def slot_handleSetProgram(self, pluginId, index):
  611. Carla.host._set_currentProgram(pluginId, index)
  612. pwidget = self.fPluginList[pluginId]
  613. if pwidget is None:
  614. return
  615. pwidget.setProgram(index)
  616. @pyqtSlot(int, int)
  617. def slot_handleSetProgramCount(self, pluginId, count):
  618. Carla.host._set_programCount(pluginId, count)
  619. @pyqtSlot(int, int, str)
  620. def slot_handleSetProgramName(self, pluginId, index, name):
  621. Carla.host._set_programNameS(pluginId, index, name)
  622. @pyqtSlot(int, int)
  623. def slot_handleSetMidiProgram(self, pluginId, index):
  624. Carla.host._set_currentMidiProgram(pluginId, index)
  625. pwidget = self.fPluginList[pluginId]
  626. if pwidget is None:
  627. return
  628. pwidget.setMidiProgram(index)
  629. @pyqtSlot(int, int)
  630. def slot_handleSetMidiProgramCount(self, pluginId, count):
  631. Carla.host._set_midiProgramCount(pluginId, count)
  632. @pyqtSlot(int, int, int, int, str)
  633. def slot_handleSetMidiProgramData(self, pluginId, index, bank, program, name):
  634. data = deepcopy(MidiProgramData)
  635. data['bank'] = bank
  636. data['program'] = program
  637. data['label'] = name
  638. Carla.host._set_midiProgramDataS(pluginId, index, data)
  639. @pyqtSlot(int, int, float)
  640. def slot_handleSetInputPeakValue(self, pluginId, portId, value):
  641. Carla.host._set_inPeak(pluginId, portId-1, value)
  642. @pyqtSlot(int, int, float)
  643. def slot_handleSetOutputPeakValue(self, pluginId, portId, value):
  644. Carla.host._set_outPeak(pluginId, portId-1, value)
  645. @pyqtSlot(int, int, int, int)
  646. def slot_handleNoteOn(self, pluginId, channel, note, velo):
  647. pwidget = self.fPluginList[pluginId]
  648. if pwidget is None:
  649. return
  650. pwidget.sendNoteOn(note)
  651. @pyqtSlot(int, int, int)
  652. def slot_handleNoteOff(self, pluginId, channel, note):
  653. pwidget = self.fPluginList[pluginId]
  654. if pwidget is None:
  655. return
  656. pwidget.sendNoteOff(note)
  657. @pyqtSlot()
  658. def slot_handleExit(self):
  659. self.removeAll()
  660. if self.lo_server:
  661. self.lo_server.stop()
  662. self.lo_server = None
  663. self.ui.act_file_refresh.setEnabled(False)
  664. global lo_target, lo_targetName
  665. lo_target = None
  666. lo_targetName = ""
  667. self.lo_address = ""
  668. def saveSettings(self):
  669. settings = QSettings()
  670. settings.setValue("Geometry", self.saveGeometry())
  671. #settings.setValue("ShowToolbar", self.ui.toolBar.isVisible())
  672. def loadSettings(self):
  673. settings = QSettings()
  674. self.restoreGeometry(settings.value("Geometry", ""))
  675. #showToolbar = settings.value("ShowToolbar", True, type=bool)
  676. #self.ui.act_settings_show_toolbar.setChecked(showToolbar)
  677. #self.ui.toolBar.setVisible(showToolbar)
  678. def timerEvent(self, event):
  679. if event.timerId() == self.fIdleTimerFast:
  680. for pwidget in self.fPluginList:
  681. if pwidget is None:
  682. break
  683. pwidget.idleFast()
  684. elif event.timerId() == self.fIdleTimerSlow:
  685. for pwidget in self.fPluginList:
  686. if pwidget is None:
  687. break
  688. pwidget.idleSlow()
  689. QMainWindow.timerEvent(self, event)
  690. def closeEvent(self, event):
  691. self.saveSettings()
  692. global lo_target
  693. if lo_target and self.lo_server:
  694. lo_send(lo_target, "/unregister")
  695. QMainWindow.closeEvent(self, event)
  696. #--------------- main ------------------
  697. if __name__ == '__main__':
  698. # App initialization
  699. app = QApplication(sys.argv)
  700. app.setApplicationName("Carla-Control")
  701. app.setApplicationVersion(VERSION)
  702. app.setOrganizationName("Cadence")
  703. app.setWindowIcon(QIcon(":/scalable/carla-control.svg"))
  704. Carla.host = Host()
  705. # Create GUI
  706. Carla.gui = CarlaControlW()
  707. # Set-up custom signal handling
  708. setUpSignals()
  709. # Show GUI
  710. Carla.gui.show()
  711. # App-Loop
  712. sys.exit(app.exec_())