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.

410 lines
12KB

  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
  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. closed = True
  95. if closed:
  96. self.stream = create_stream(self.baseurl)
  97. stream.close()
  98. def is_engine_running(self):
  99. try:
  100. return requests.get("{}/is_engine_running".format(self.baseurl)).status_code == 200
  101. except requests.exceptions.ConnectionError:
  102. if self.fEngineCallback is None:
  103. self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "")
  104. def set_engine_about_to_close(self):
  105. return requests.get("{}/set_engine_about_to_close".format(self.baseurl)).status_code == 200
  106. def set_engine_option(self, option, value, valueStr):
  107. return
  108. def load_file(self, filename):
  109. return False
  110. def load_project(self, filename):
  111. return False
  112. def save_project(self, filename):
  113. return False
  114. def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB):
  115. return False
  116. def patchbay_disconnect(self, connectionId):
  117. return False
  118. def patchbay_refresh(self, external):
  119. return False
  120. def transport_play(self):
  121. return
  122. def transport_pause(self):
  123. return
  124. def transport_bpm(self, bpm):
  125. return
  126. def transport_relocate(self, frame):
  127. return
  128. def get_current_transport_frame(self):
  129. return 0
  130. def get_transport_info(self):
  131. return PyCarlaTransportInfo
  132. def get_current_plugin_count(self):
  133. return 0
  134. def get_max_plugin_number(self):
  135. return 0
  136. def add_plugin(self, btype, ptype, filename, name, label, uniqueId, extraPtr, options):
  137. return False
  138. def remove_plugin(self, pluginId):
  139. return False
  140. def remove_all_plugins(self):
  141. return False
  142. def rename_plugin(self, pluginId, newName):
  143. return ""
  144. def clone_plugin(self, pluginId):
  145. return False
  146. def replace_plugin(self, pluginId):
  147. return False
  148. def switch_plugins(self, pluginIdA, pluginIdB):
  149. return False
  150. def load_plugin_state(self, pluginId, filename):
  151. return False
  152. def save_plugin_state(self, pluginId, filename):
  153. return False
  154. def export_plugin_lv2(self, pluginId, lv2path):
  155. return False
  156. def get_plugin_info(self, pluginId):
  157. return PyCarlaPluginInfo
  158. def get_audio_port_count_info(self, pluginId):
  159. return PyCarlaPortCountInfo
  160. def get_midi_port_count_info(self, pluginId):
  161. return PyCarlaPortCountInfo
  162. def get_parameter_count_info(self, pluginId):
  163. return PyCarlaPortCountInfo
  164. def get_parameter_info(self, pluginId, parameterId):
  165. return PyCarlaParameterInfo
  166. def get_parameter_scalepoint_info(self, pluginId, parameterId, scalePointId):
  167. return PyCarlaScalePointInfo
  168. def get_parameter_data(self, pluginId, parameterId):
  169. return PyParameterData
  170. def get_parameter_ranges(self, pluginId, parameterId):
  171. return PyParameterRanges
  172. def get_midi_program_data(self, pluginId, midiProgramId):
  173. return PyMidiProgramData
  174. def get_custom_data(self, pluginId, customDataId):
  175. return PyCustomData
  176. def get_chunk_data(self, pluginId):
  177. return ""
  178. def get_parameter_count(self, pluginId):
  179. return 0
  180. def get_program_count(self, pluginId):
  181. return 0
  182. def get_midi_program_count(self, pluginId):
  183. return 0
  184. def get_custom_data_count(self, pluginId):
  185. return 0
  186. def get_parameter_text(self, pluginId, parameterId):
  187. return ""
  188. def get_program_name(self, pluginId, programId):
  189. return ""
  190. def get_midi_program_name(self, pluginId, midiProgramId):
  191. return ""
  192. def get_real_plugin_name(self, pluginId):
  193. return ""
  194. def get_current_program_index(self, pluginId):
  195. return 0
  196. def get_current_midi_program_index(self, pluginId):
  197. return 0
  198. def get_default_parameter_value(self, pluginId, parameterId):
  199. return 0.0
  200. def get_current_parameter_value(self, pluginId, parameterId):
  201. return 0.0
  202. def get_internal_parameter_value(self, pluginId, parameterId):
  203. return 0.0
  204. def get_input_peak_value(self, pluginId, isLeft):
  205. return 0.0
  206. def get_output_peak_value(self, pluginId, isLeft):
  207. return 0.0
  208. def set_option(self, pluginId, option, yesNo):
  209. return
  210. def set_active(self, pluginId, onOff):
  211. return
  212. def set_drywet(self, pluginId, value):
  213. return
  214. def set_volume(self, pluginId, value):
  215. return
  216. def set_balance_left(self, pluginId, value):
  217. return
  218. def set_balance_right(self, pluginId, value):
  219. return
  220. def set_panning(self, pluginId, value):
  221. return
  222. def set_ctrl_channel(self, pluginId, channel):
  223. return
  224. def set_parameter_value(self, pluginId, parameterId, value):
  225. return
  226. def set_parameter_midi_channel(self, pluginId, parameterId, channel):
  227. return
  228. def set_parameter_midi_cc(self, pluginId, parameterId, cc):
  229. return
  230. def set_program(self, pluginId, programId):
  231. return
  232. def set_midi_program(self, pluginId, midiProgramId):
  233. return
  234. def set_custom_data(self, pluginId, type_, key, value):
  235. return
  236. def set_chunk_data(self, pluginId, chunkData):
  237. return
  238. def prepare_for_save(self, pluginId):
  239. return
  240. def reset_parameters(self, pluginId):
  241. return
  242. def randomize_parameters(self, pluginId):
  243. return
  244. def send_midi_note(self, pluginId, channel, note, velocity):
  245. return
  246. def get_buffer_size(self):
  247. return 0
  248. def get_sample_rate(self):
  249. return 0.0
  250. def get_last_error(self):
  251. return requests.get("{}/get_last_error".format(self.baseurl)).text
  252. def get_host_osc_url_tcp(self):
  253. return ""
  254. def get_host_osc_url_udp(self):
  255. return ""
  256. # ---------------------------------------------------------------------------------------------------------------------
  257. # TESTING
  258. if __name__ == '__main__':
  259. baseurl = "http://localhost:2228"
  260. #driver_count = int(requests.get("{}/get_engine_driver_count".format(baseurl)).text)
  261. ## FIXME
  262. #driver_count -= 1
  263. #print("Driver names:")
  264. #for index in range(driver_count):
  265. #print("\t -", requests.get("{}/get_engine_driver_name/{}".format(baseurl, index)).text)
  266. #print("Driver device names:")
  267. #for index in range(driver_count):
  268. #for name in requests.get("{}/get_engine_driver_device_names/{}".format(baseurl, index)).text.split("\n"):
  269. #print("\t {}:".format(name), requests.get("{}/get_engine_driver_device_info/{}/{}".format(baseurl, index, name)).json())
  270. requests.get("{}/engine_close".format(baseurl)).status_code
  271. if requests.get("{}/engine_init/{}/{}".format(baseurl, "JACK", "test")).status_code == 200:
  272. stream = requests.get("{}/stream".format(baseurl), stream=True, timeout=0.25)
  273. #stream.add_done_callback(cb)
  274. #print(stream, dir(stream))
  275. #for line in stream.iter_lines():
  276. #print("line", stream, line)
  277. while True:
  278. print("idle")
  279. for line in iterate_stream_nonblock(stream):
  280. print("line", line)
  281. sleep(0.5)
  282. requests.get("{}/engine_close".format(baseurl)).status_code
  283. # ---------------------------------------------------------------------------------------------------------------------