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.

411 lines
17KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla Backend code (Web stuff)
  4. # Copyright (C) 2018 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 doc/GPL.txt file.
  17. # ---------------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import requests
  20. # ---------------------------------------------------------------------------------------------------------------------
  21. # Imports (Custom)
  22. from carla_backend_qt import *
  23. import os
  24. from time import sleep
  25. # ---------------------------------------------------------------------------------------------------------------------
  26. # Iterates over the content of a file-like object line-by-line.
  27. # Based on code by Lars Kellogg-Stedman, see https://github.com/requests/requests/issues/2433
  28. def iterate_stream_nonblock(stream, chunk_size=128):
  29. pending = None
  30. while True:
  31. try:
  32. chunk = os.read(stream.raw.fileno(), chunk_size)
  33. except BlockingIOError:
  34. break
  35. if not chunk:
  36. break
  37. if pending is not None:
  38. chunk = pending + chunk
  39. pending = None
  40. lines = chunk.splitlines()
  41. if lines and lines[-1]:
  42. pending = lines.pop()
  43. for line in lines:
  44. yield line
  45. if not pending:
  46. break
  47. if pending:
  48. yield pending
  49. # ---------------------------------------------------------------------------------------------------------------------
  50. def create_stream(baseurl):
  51. stream = requests.get("{}/stream".format(baseurl), stream=True, timeout=0.1)
  52. if stream.encoding is None:
  53. stream.encoding = 'utf-8'
  54. return stream
  55. # ---------------------------------------------------------------------------------------------------------------------
  56. # Carla Host object for connecting to the REST API backend
  57. class CarlaHostQtWeb(CarlaHostQtNull):
  58. def __init__(self):
  59. CarlaHostQtNull.__init__(self)
  60. self.baseurl = "http://localhost:2228"
  61. self.stream = create_stream(self.baseurl)
  62. def get_engine_driver_count(self):
  63. # FIXME
  64. return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) - 1
  65. def get_engine_driver_name(self, index):
  66. return requests.get("{}/get_engine_driver_name/{}".format(self.baseurl, index)).text
  67. def get_engine_driver_device_names(self, index):
  68. return requests.get("{}/get_engine_driver_device_names/{}".format(self.baseurl, index)).text.split("\n")
  69. def get_engine_driver_device_info(self, index, name):
  70. return requests.get("{}/get_engine_driver_device_info/{}/{}".format(self.baseurl, index, name)).json()
  71. def engine_init(self, driverName, clientName):
  72. return requests.get("{}/engine_init/{}/{}".format(self.baseurl, driverName, clientName)).status_code == 200
  73. def engine_close(self):
  74. return requests.get("{}/engine_close".format(self.baseurl)).status_code == 200
  75. def engine_idle(self):
  76. closed = False
  77. stream = self.stream
  78. for line in iterate_stream_nonblock(stream):
  79. line = line.decode('utf-8', errors='ignore')
  80. if line.startswith("Carla: "):
  81. if self.fEngineCallback is None:
  82. continue
  83. # split values from line
  84. action, pluginId, value1, value2, value3, valueStr = line[7:].split(" ",5)
  85. # convert to proper types
  86. action = int(action)
  87. pluginId = int(pluginId)
  88. value1 = int(value1)
  89. value2 = int(value2)
  90. value3 = float(value3)
  91. # pass to callback
  92. self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr)
  93. elif line == "Connection: close":
  94. if not closed:
  95. self.stream = create_stream(self.baseurl)
  96. closed = True
  97. if closed:
  98. stream.close()
  99. def is_engine_running(self):
  100. try:
  101. return requests.get("{}/is_engine_running".format(self.baseurl)).status_code == 200
  102. except requests.exceptions.ConnectionError:
  103. if self.fEngineCallback is None:
  104. self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "")
  105. def set_engine_about_to_close(self):
  106. return requests.get("{}/set_engine_about_to_close".format(self.baseurl)).status_code == 200
  107. def set_engine_option(self, option, value, valueStr):
  108. requests.get("{}/set_engine_option/{}/{}/{}".format(self.baseurl, option, value, valueStr))
  109. def load_file(self, filename):
  110. return requests.get("{}/load_file/{}".format(self.baseurl, filename)).status_code == 200
  111. def load_project(self, filename):
  112. return requests.get("{}/load_project/{}".format(self.baseurl, filename)).status_code == 200
  113. def save_project(self, filename):
  114. return requests.get("{}/save_project/{}".format(self.baseurl, filename)).status_code == 200
  115. def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB):
  116. return requests.get("{}/patchbay_connect/{}/{}/{}/{}".format(self.baseurl, groupIdA, portIdA, groupIdB, portIdB)).status_code == 200
  117. def patchbay_disconnect(self, connectionId):
  118. return requests.get("{}/patchbay_disconnect/{}".format(self.baseurl, connectionId)).status_code == 200
  119. def patchbay_refresh(self, external):
  120. return requests.get("{}/patchbay_refresh/{}".format(self.baseurl, external)).status_code == 200
  121. def transport_play(self):
  122. requests.get("{}/transport_play".format(self.baseurl))
  123. def transport_pause(self):
  124. requests.get("{}/transport_pause".format(self.baseurl))
  125. def transport_bpm(self, bpm):
  126. requests.get("{}/transport_bpm/{}".format(self.baseurl, bpm))
  127. def transport_relocate(self, frame):
  128. requests.get("{}/transport_relocate/{}".format(self.baseurl, frame))
  129. def get_current_transport_frame(self):
  130. return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text)
  131. def get_transport_info(self):
  132. return requests.get("{}/get_transport_info".format(self.baseurl)).json()
  133. def get_current_plugin_count(self):
  134. return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text)
  135. def get_max_plugin_number(self):
  136. return int(requests.get("{}/get_max_plugin_number".format(self.baseurl)).text)
  137. def add_plugin(self, btype, ptype, filename, name, label, uniqueId, extraPtr, options):
  138. return requests.get("{}/add_plugin/{}/{}/{}/{}/{}/{}/{}/{}".format(self.baseurl, btype, ptype, filename, name, label, uniqueId, extraPtr, options)).status_code == 200
  139. def remove_plugin(self, pluginId):
  140. return requests.get("{}/remove_plugin/{}".format(self.baseurl, pluginId)).status_code == 200
  141. def remove_all_plugins(self):
  142. return requests.get("{}/remove_all_plugins".format(self.baseurl)).status_code == 200
  143. def rename_plugin(self, pluginId, newName):
  144. return requests.get("{}/rename_plugin/{}/{}".format(self.baseurl, pluginId, newName)).text
  145. def clone_plugin(self, pluginId):
  146. return requests.get("{}/clone_plugin/{}".format(self.baseurl, pluginId)).status_code == 200
  147. def replace_plugin(self, pluginId):
  148. return requests.get("{}/replace_plugin/{}".format(self.baseurl, pluginId)).status_code == 200
  149. def switch_plugins(self, pluginIdA, pluginIdB):
  150. return requests.get("{}/switch_plugins/{}/{}".format(self.baseurl, pluginIdA, pluginIdB)).status_code == 200
  151. def load_plugin_state(self, pluginId, filename):
  152. return requests.get("{}/load_plugin_state/{}/{}".format(self.baseurl, pluginId, filename)).status_code == 200
  153. def save_plugin_state(self, pluginId, filename):
  154. return requests.get("{}/save_plugin_state/{}/{}".format(self.baseurl, pluginId, filename)).status_code == 200
  155. def export_plugin_lv2(self, pluginId, lv2path):
  156. return requests.get("{}/export_plugin_lv2/{}/{}".format(self.baseurl, pluginId, lv2path)).status_code == 200
  157. def get_plugin_info(self, pluginId):
  158. return requests.get("{}/get_plugin_info/{}".format(self.baseurl, pluginId)).json()
  159. def get_audio_port_count_info(self, pluginId):
  160. return requests.get("{}/get_audio_port_count_info/{}".format(self.baseurl, pluginId)).json()
  161. def get_midi_port_count_info(self, pluginId):
  162. return requests.get("{}/get_midi_port_count_info/{}".format(self.baseurl, pluginId)).json()
  163. def get_parameter_count_info(self, pluginId):
  164. return requests.get("{}/get_parameter_count_info/{}".format(self.baseurl, pluginId)).json()
  165. def get_parameter_info(self, pluginId, parameterId):
  166. return requests.get("{}/get_parameter_info/{}/{}".format(self.baseurl, pluginId, parameterId)).json()
  167. def get_parameter_scalepoint_info(self, pluginId, parameterId, scalePointId):
  168. return requests.get("{}/get_parameter_scalepoint_info/{}/{}/{}".format(self.baseurl, pluginId, parameterId, scalePointId)).json()
  169. def get_parameter_data(self, pluginId, parameterId):
  170. return requests.get("{}/get_parameter_data/{}/{}".format(self.baseurl, pluginId, parameterId)).json()
  171. def get_parameter_ranges(self, pluginId, parameterId):
  172. return requests.get("{}/get_parameter_ranges/{}/{}".format(self.baseurl, pluginId, parameterId)).json()
  173. def get_midi_program_data(self, pluginId, midiProgramId):
  174. return requests.get("{}/get_midi_program_data/{}/{}".format(self.baseurl, pluginId, midiProgramId)).json()
  175. def get_custom_data(self, pluginId, customDataId):
  176. return requests.get("{}/get_custom_data/{}/{}".format(self.baseurl, pluginId, customDataId)).json()
  177. def get_chunk_data(self, pluginId):
  178. return requests.get("{}/get_chunk_data/{}".format(self.baseurl, pluginId)).text
  179. def get_parameter_count(self, pluginId):
  180. return int(requests.get("{}/get_parameter_count/{}".format(self.baseurl, pluginId)).text)
  181. def get_program_count(self, pluginId):
  182. return int(requests.get("{}/get_program_count/{}".format(self.baseurl, pluginId)).text)
  183. def get_midi_program_count(self, pluginId):
  184. return int(requests.get("{}/get_midi_program_count/{}".format(self.baseurl, pluginId)).text)
  185. def get_custom_data_count(self, pluginId):
  186. return int(requests.get("{}/get_custom_data_count/{}".format(self.baseurl, pluginId)).text)
  187. def get_parameter_text(self, pluginId, parameterId):
  188. return requests.get("{}/get_parameter_text/{}/{}".format(self.baseurl, pluginId, parameterId)).text
  189. def get_program_name(self, pluginId, programId):
  190. return requests.get("{}/get_program_name/{}/{}".format(self.baseurl, pluginId, programId)).text
  191. def get_midi_program_name(self, pluginId, midiProgramId):
  192. return requests.get("{}/get_midi_program_name/{}/{}".format(self.baseurl, pluginId, midiProgramId)).text
  193. def get_real_plugin_name(self, pluginId):
  194. return requests.get("{}/get_real_plugin_name/{}".format(self.baseurl, pluginId)).text
  195. def get_current_program_index(self, pluginId):
  196. return int(requests.get("{}/get_custom_data_count/{}".format(self.baseurl, pluginId)).text)
  197. def get_current_midi_program_index(self, pluginId):
  198. return int(requests.get("{}/get_custom_data_count/{}".format(self.baseurl, pluginId)).text)
  199. def get_default_parameter_value(self, pluginId, parameterId):
  200. return float(requests.get("{}/get_default_parameter_value/{}/{}".format(self.baseurl, pluginId, parameterId)).text)
  201. def get_current_parameter_value(self, pluginId, parameterId):
  202. return float(requests.get("{}/get_current_parameter_value/{}/{}".format(self.baseurl, pluginId, parameterId)).text)
  203. def get_internal_parameter_value(self, pluginId, parameterId):
  204. return float(requests.get("{}/get_internal_parameter_value/{}/{}".format(self.baseurl, pluginId, parameterId)).text)
  205. def get_input_peak_value(self, pluginId, isLeft):
  206. return float(requests.get("{}/get_input_peak_value/{}/{}".format(self.baseurl, pluginId, isLeft)).text)
  207. def get_output_peak_value(self, pluginId, isLeft):
  208. return float(requests.get("{}/get_output_peak_value/{}/{}".format(self.baseurl, pluginId, isLeft)).text)
  209. def set_option(self, pluginId, option, yesNo):
  210. requests.get("{}/set_option/{}/{}/{}".format(self.baseurl, pluginId, option, yesNo))
  211. def set_active(self, pluginId, onOff):
  212. requests.get("{}/set_active/{}/{}".format(self.baseurl, pluginId, onOff))
  213. def set_drywet(self, pluginId, value):
  214. requests.get("{}/set_drywet/{}/{}".format(self.baseurl, pluginId, value))
  215. def set_volume(self, pluginId, value):
  216. requests.get("{}/set_volume/{}/{}".format(self.baseurl, pluginId, value))
  217. def set_balance_left(self, pluginId, value):
  218. requests.get("{}/set_balance_left/{}/{}".format(self.baseurl, pluginId, value))
  219. def set_balance_right(self, pluginId, value):
  220. requests.get("{}/set_balance_right/{}/{}".format(self.baseurl, pluginId, value))
  221. def set_panning(self, pluginId, value):
  222. requests.get("{}/set_panning/{}/{}".format(self.baseurl, pluginId, value))
  223. def set_ctrl_channel(self, pluginId, channel):
  224. requests.get("{}/set_ctrl_channel/{}/{}".format(self.baseurl, pluginId, channel))
  225. def set_parameter_value(self, pluginId, parameterId, value):
  226. requests.get("{}/set_parameter_value/{}/{}/{}".format(self.baseurl, pluginId, parameterId, value))
  227. def set_parameter_midi_channel(self, pluginId, parameterId, channel):
  228. requests.get("{}/set_parameter_midi_channel/{}/{}/{}".format(self.baseurl, pluginId, parameterId, channel))
  229. def set_parameter_midi_cc(self, pluginId, parameterId, cc):
  230. requests.get("{}/set_parameter_midi_cc/{}/{}/{}".format(self.baseurl, pluginId, parameterId, cc))
  231. def set_program(self, pluginId, programId):
  232. requests.get("{}/set_program/{}/{}".format(self.baseurl, pluginId, programId))
  233. def set_midi_program(self, pluginId, midiProgramId):
  234. requests.get("{}/set_midi_program/{}/{}".format(self.baseurl, pluginId, midiProgramId))
  235. def set_custom_data(self, pluginId, type_, key, value):
  236. requests.get("{}/set_custom_data/{}/{}/{}/{}".format(self.baseurl, pluginId, type_, key, value))
  237. def set_chunk_data(self, pluginId, chunkData):
  238. requests.get("{}/set_chunk_data/{}/{}".format(self.baseurl, pluginId, chunkData))
  239. def prepare_for_save(self, pluginId):
  240. requests.get("{}/prepare_for_save/{}".format(self.baseurl, pluginId))
  241. def reset_parameters(self, pluginId):
  242. requests.get("{}/reset_parameters/{}".format(self.baseurl, pluginId))
  243. def randomize_parameters(self, pluginId):
  244. requests.get("{}/randomize_parameters/{}".format(self.baseurl, pluginId))
  245. def send_midi_note(self, pluginId, channel, note, velocity):
  246. requests.get("{}/send_midi_note/{}/{}/{}/{}".format(self.baseurl, pluginId, channel, note, velocity))
  247. def get_buffer_size(self):
  248. return int(requests.get("{}/get_buffer_size".format(self.baseurl)).text)
  249. def get_sample_rate(self):
  250. return float(requests.get("{}/get_sample_rate".format(self.baseurl)).text)
  251. def get_last_error(self):
  252. return requests.get("{}/get_last_error".format(self.baseurl)).text
  253. def get_host_osc_url_tcp(self):
  254. return requests.get("{}/get_host_osc_url_tcp".format(self.baseurl)).text
  255. def get_host_osc_url_udp(self):
  256. return requests.get("{}/get_host_osc_url_udp".format(self.baseurl)).text
  257. # ---------------------------------------------------------------------------------------------------------------------
  258. # TESTING
  259. if __name__ == '__main__':
  260. baseurl = "http://localhost:2228"
  261. #driver_count = int(requests.get("{}/get_engine_driver_count".format(baseurl)).text)
  262. ## FIXME
  263. #driver_count -= 1
  264. #print("Driver names:")
  265. #for index in range(driver_count):
  266. #print("\t -", requests.get("{}/get_engine_driver_name/{}".format(baseurl, index)).text)
  267. #print("Driver device names:")
  268. #for index in range(driver_count):
  269. #for name in requests.get("{}/get_engine_driver_device_names/{}".format(baseurl, index)).text.split("\n"):
  270. #print("\t {}:".format(name), requests.get("{}/get_engine_driver_device_info/{}/{}".format(baseurl, index, name)).json())
  271. requests.get("{}/engine_close".format(baseurl)).status_code
  272. if requests.get("{}/engine_init/{}/{}".format(baseurl, "JACK", "test")).status_code == 200:
  273. stream = requests.get("{}/stream".format(baseurl), stream=True, timeout=0.25)
  274. #stream.add_done_callback(cb)
  275. #print(stream, dir(stream))
  276. #for line in stream.iter_lines():
  277. #print("line", stream, line)
  278. while True:
  279. print("idle")
  280. for line in iterate_stream_nonblock(stream):
  281. print("line", line)
  282. sleep(0.5)
  283. requests.get("{}/engine_close".format(baseurl)).status_code
  284. # ---------------------------------------------------------------------------------------------------------------------