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.

569 lines
20KB

  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=1024):
  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. self.isRemote = True
  63. def get_engine_driver_count(self):
  64. # FIXME
  65. return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) - 1
  66. def get_engine_driver_name(self, index):
  67. return requests.get("{}/get_engine_driver_name".format(self.baseurl), params={
  68. 'index': index,
  69. }).text
  70. def get_engine_driver_device_names(self, index):
  71. return requests.get("{}/get_engine_driver_device_names".format(self.baseurl), params={
  72. 'index': index,
  73. }).text.split("\n")
  74. def get_engine_driver_device_info(self, index, name):
  75. return requests.get("{}/get_engine_driver_device_info".format(self.baseurl), params={
  76. 'index': index,
  77. 'name': name,
  78. }).json()
  79. def engine_init(self, driverName, clientName):
  80. return bool(int(requests.get("{}/engine_init".format(self.baseurl), params={
  81. 'driverName': driverName,
  82. 'clientName': clientName,
  83. }).text))
  84. def engine_close(self):
  85. return bool(int(requests.get("{}/engine_close".format(self.baseurl)).text))
  86. def engine_idle(self):
  87. closed = False
  88. stream = self.stream
  89. for line in iterate_stream_nonblock(stream):
  90. line = line.decode('utf-8', errors='ignore')
  91. if line.startswith("Carla: "):
  92. if self.fEngineCallback is None:
  93. continue
  94. # split values from line
  95. action, pluginId, value1, value2, value3, valueStr = line[7:].split(" ",5)
  96. # convert to proper types
  97. action = int(action)
  98. pluginId = int(pluginId)
  99. value1 = int(value1)
  100. value2 = int(value2)
  101. value3 = float(value3)
  102. # pass to callback
  103. self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr)
  104. elif line == "Connection: close":
  105. if not closed:
  106. self.stream = create_stream(self.baseurl)
  107. closed = True
  108. if closed:
  109. stream.close()
  110. def is_engine_running(self):
  111. try:
  112. return bool(int(requests.get("{}/is_engine_running".format(self.baseurl)).text))
  113. except requests.exceptions.ConnectionError:
  114. if self.fEngineCallback is None:
  115. self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "")
  116. def set_engine_about_to_close(self):
  117. return bool(int(requests.get("{}/set_engine_about_to_close".format(self.baseurl)).text))
  118. def set_engine_option(self, option, value, valueStr):
  119. requests.get("{}/set_engine_option".format(self.baseurl), params={
  120. 'option': option,
  121. 'value': value,
  122. 'valueStr': valueStr,
  123. })
  124. def load_file(self, filename):
  125. return bool(int(requests.get("{}/load_file".format(self.baseurl), params={
  126. 'filename': filename,
  127. }).text))
  128. def load_project(self, filename):
  129. return bool(int(requests.get("{}/load_project".format(self.baseurl), params={
  130. 'filename': filename,
  131. }).text))
  132. def save_project(self, filename):
  133. return bool(int(requests.get("{}/save_project".format(self.baseurl), params={
  134. 'filename': filename,
  135. }).text))
  136. def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB):
  137. return bool(int(requests.get("{}/patchbay_connect".format(self.baseurl), params={
  138. 'groupIdA': groupIdA,
  139. 'portIdA': portIdA,
  140. 'groupIdB': groupIdB,
  141. 'portIdB': portIdB,
  142. }).text))
  143. def patchbay_disconnect(self, connectionId):
  144. return bool(int(requests.get("{}/patchbay_disconnect".format(self.baseurl), params={
  145. 'connectionId': connectionId,
  146. }).text))
  147. def patchbay_refresh(self, external):
  148. return bool(int(requests.get("{}/patchbay_refresh".format(self.baseurl), params={
  149. 'external': external,
  150. }).text))
  151. def transport_play(self):
  152. requests.get("{}/transport_play".format(self.baseurl))
  153. def transport_pause(self):
  154. requests.get("{}/transport_pause".format(self.baseurl))
  155. def transport_bpm(self, bpm):
  156. requests.get("{}/transport_bpm".format(self.baseurl), params={
  157. 'bpm': bpm,
  158. })
  159. def transport_relocate(self, frame):
  160. requests.get("{}/transport_relocate".format(self.baseurl), params={
  161. 'frame': frame,
  162. })
  163. def get_current_transport_frame(self):
  164. return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text)
  165. def get_transport_info(self):
  166. return requests.get("{}/get_transport_info".format(self.baseurl)).json()
  167. def get_current_plugin_count(self):
  168. return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text)
  169. def get_max_plugin_number(self):
  170. return int(requests.get("{}/get_max_plugin_number".format(self.baseurl)).text)
  171. def add_plugin(self, btype, ptype, filename, name, label, uniqueId, extraPtr, options):
  172. return bool(int(requests.get("{}/add_plugin".format(self.baseurl), params={
  173. 'btype': btype,
  174. 'ptype': ptype,
  175. 'filename': filename,
  176. 'name': name,
  177. 'label': label,
  178. 'uniqueId': uniqueId,
  179. 'options': options,
  180. }).text))
  181. def remove_plugin(self, pluginId):
  182. return bool(int(requests.get("{}/remove_plugin".format(self.baseurl), params={
  183. 'filename': pluginId,
  184. }).text))
  185. def remove_all_plugins(self):
  186. return bool(int(requests.get("{}/remove_all_plugins".format(self.baseurl)).text))
  187. def rename_plugin(self, pluginId, newName):
  188. return requests.get("{}/rename_plugin".format(self.baseurl), params={
  189. 'pluginId': pluginId,
  190. 'newName': newName,
  191. }).text
  192. def clone_plugin(self, pluginId):
  193. return bool(int(requests.get("{}/clone_plugin".format(self.baseurl), params={
  194. 'pluginId': pluginId,
  195. }).text))
  196. def replace_plugin(self, pluginId):
  197. return bool(int(requests.get("{}/replace_plugin".format(self.baseurl), params={
  198. 'pluginId': pluginId,
  199. }).text))
  200. def switch_plugins(self, pluginIdA, pluginIdB):
  201. return bool(int(requests.get("{}/switch_plugins".format(self.baseurl), params={
  202. 'pluginIdA': pluginIdA,
  203. 'pluginIdB': pluginIdB,
  204. }).text))
  205. def load_plugin_state(self, pluginId, filename):
  206. return bool(int(requests.get("{}/load_plugin_state".format(self.baseurl), params={
  207. 'pluginId': pluginId,
  208. 'filename': filename,
  209. }).text))
  210. def save_plugin_state(self, pluginId, filename):
  211. return bool(int(requests.get("{}/save_plugin_state".format(self.baseurl), params={
  212. 'pluginId': pluginId,
  213. 'filename': filename,
  214. }).text))
  215. def export_plugin_lv2(self, pluginId, lv2path):
  216. return bool(int(requests.get("{}/export_plugin_lv2".format(self.baseurl), params={
  217. 'pluginId': pluginId,
  218. 'lv2path': lv2path,
  219. }).text))
  220. def get_plugin_info(self, pluginId):
  221. return requests.get("{}/get_plugin_info".format(self.baseurl), params={
  222. 'pluginId': pluginId,
  223. }).json()
  224. def get_audio_port_count_info(self, pluginId):
  225. return requests.get("{}/get_audio_port_count_info".format(self.baseurl), params={
  226. 'pluginId': pluginId,
  227. }).json()
  228. def get_midi_port_count_info(self, pluginId):
  229. return requests.get("{}/get_midi_port_count_info".format(self.baseurl), params={
  230. 'pluginId': pluginId,
  231. }).json()
  232. def get_parameter_count_info(self, pluginId):
  233. return requests.get("{}/get_parameter_count_info".format(self.baseurl), params={
  234. 'pluginId': pluginId,
  235. }).json()
  236. def get_parameter_info(self, pluginId, parameterId):
  237. return requests.get("{}/get_parameter_info".format(self.baseurl), params={
  238. 'pluginId': pluginId,
  239. 'parameterId': parameterId,
  240. }).json()
  241. def get_parameter_scalepoint_info(self, pluginId, parameterId, scalePointId):
  242. return requests.get("{}/get_parameter_scalepoint_info".format(self.baseurl), params={
  243. 'pluginId': pluginId,
  244. 'parameterId': parameterId,
  245. 'scalePointId': scalePointId,
  246. }).json()
  247. def get_parameter_data(self, pluginId, parameterId):
  248. return requests.get("{}/get_parameter_data".format(self.baseurl), params={
  249. 'pluginId': pluginId,
  250. 'parameterId': parameterId,
  251. }).json()
  252. def get_parameter_ranges(self, pluginId, parameterId):
  253. return requests.get("{}/get_parameter_ranges".format(self.baseurl), params={
  254. 'pluginId': pluginId,
  255. 'parameterId': parameterId,
  256. }).json()
  257. def get_midi_program_data(self, pluginId, midiProgramId):
  258. return requests.get("{}/get_midi_program_data".format(self.baseurl), params={
  259. 'pluginId': pluginId,
  260. 'midiProgramId': midiProgramId,
  261. }).json()
  262. def get_custom_data(self, pluginId, customDataId):
  263. return requests.get("{}/get_custom_data".format(self.baseurl), params={
  264. 'pluginId': pluginId,
  265. 'customDataId': customDataId,
  266. }).json()
  267. def get_custom_data_value(self, pluginId, type_, key):
  268. return requests.get("{}/get_custom_data_value".format(self.baseurl), params={
  269. 'pluginId': pluginId,
  270. 'type_': type_,
  271. 'key': key,
  272. }).text
  273. def get_chunk_data(self, pluginId):
  274. return requests.get("{}/get_chunk_data".format(self.baseurl), params={
  275. 'pluginId': pluginId,
  276. }).text
  277. def get_parameter_count(self, pluginId):
  278. return int(requests.get("{}/get_parameter_count".format(self.baseurl), params={
  279. 'pluginId': pluginId,
  280. }).text)
  281. def get_program_count(self, pluginId):
  282. return int(requests.get("{}/get_program_count".format(self.baseurl), params={
  283. 'pluginId': pluginId,
  284. }).text)
  285. def get_midi_program_count(self, pluginId):
  286. return int(requests.get("{}/get_midi_program_count".format(self.baseurl), params={
  287. 'pluginId': pluginId,
  288. }).text)
  289. def get_custom_data_count(self, pluginId):
  290. return int(requests.get("{}/get_custom_data_count".format(self.baseurl), params={
  291. 'pluginId': pluginId,
  292. }).text)
  293. def get_parameter_text(self, pluginId, parameterId):
  294. return requests.get("{}/get_parameter_text".format(self.baseurl), params={
  295. 'pluginId': pluginId,
  296. 'parameterId': parameterId,
  297. }).text
  298. def get_program_name(self, pluginId, programId):
  299. return requests.get("{}/get_program_name".format(self.baseurl), params={
  300. 'pluginId': pluginId,
  301. 'programId': programId,
  302. }).text
  303. def get_midi_program_name(self, pluginId, midiProgramId):
  304. return requests.get("{}/get_midi_program_name".format(self.baseurl), params={
  305. 'pluginId': pluginId,
  306. 'midiProgramId': midiProgramId,
  307. }).text
  308. def get_real_plugin_name(self, pluginId):
  309. return requests.get("{}/get_real_plugin_name".format(self.baseurl), params={
  310. 'pluginId': pluginId,
  311. }).text
  312. def get_current_program_index(self, pluginId):
  313. return int(requests.get("{}/get_custom_data_count".format(self.baseurl), params={
  314. 'pluginId': pluginId,
  315. }).text)
  316. def get_current_midi_program_index(self, pluginId):
  317. return int(requests.get("{}/get_custom_data_count".format(self.baseurl), params={
  318. 'pluginId': pluginId,
  319. }).text)
  320. def get_default_parameter_value(self, pluginId, parameterId):
  321. return float(requests.get("{}/get_default_parameter_value".format(self.baseurl), params={
  322. 'pluginId': pluginId,
  323. 'parameterId': parameterId,
  324. }).text)
  325. def get_current_parameter_value(self, pluginId, parameterId):
  326. return float(requests.get("{}/get_current_parameter_value".format(self.baseurl), params={
  327. 'pluginId': pluginId,
  328. 'parameterId': parameterId,
  329. }).text)
  330. def get_internal_parameter_value(self, pluginId, parameterId):
  331. return float(requests.get("{}/get_internal_parameter_value".format(self.baseurl), params={
  332. 'pluginId': pluginId,
  333. 'parameterId': parameterId,
  334. }).text)
  335. def get_input_peak_value(self, pluginId, isLeft):
  336. return float(requests.get("{}/get_input_peak_value".format(self.baseurl), params={
  337. 'pluginId': pluginId,
  338. 'isLeft': isLeft,
  339. }).text)
  340. def get_output_peak_value(self, pluginId, isLeft):
  341. return float(requests.get("{}/get_output_peak_value".format(self.baseurl), params={
  342. 'pluginId': pluginId,
  343. 'isLeft': isLeft,
  344. }).text)
  345. def set_option(self, pluginId, option, yesNo):
  346. requests.get("{}/set_option".format(self.baseurl), params={
  347. 'pluginId': pluginId,
  348. 'option': option,
  349. 'yesNo': yesNo,
  350. })
  351. def set_active(self, pluginId, onOff):
  352. requests.get("{}/set_active".format(self.baseurl), params={
  353. 'pluginId': pluginId,
  354. 'onOff': onOff,
  355. })
  356. def set_drywet(self, pluginId, value):
  357. requests.get("{}/set_drywet".format(self.baseurl), params={
  358. 'pluginId': pluginId,
  359. 'value': value,
  360. })
  361. def set_volume(self, pluginId, value):
  362. requests.get("{}/set_volume".format(self.baseurl), params={
  363. 'pluginId': pluginId,
  364. 'value': value,
  365. })
  366. def set_balance_left(self, pluginId, value):
  367. requests.get("{}/set_balance_left".format(self.baseurl), params={
  368. 'pluginId': pluginId,
  369. 'value': value,
  370. })
  371. def set_balance_right(self, pluginId, value):
  372. requests.get("{}/set_balance_right".format(self.baseurl), params={
  373. 'pluginId': pluginId,
  374. 'value': value,
  375. })
  376. def set_panning(self, pluginId, value):
  377. requests.get("{}/set_panning".format(self.baseurl), params={
  378. 'pluginId': pluginId,
  379. 'value': value,
  380. })
  381. def set_ctrl_channel(self, pluginId, channel):
  382. requests.get("{}/set_ctrl_channel".format(self.baseurl), params={
  383. 'pluginId': pluginId,
  384. 'channel': channel,
  385. })
  386. def set_parameter_value(self, pluginId, parameterId, value):
  387. requests.get("{}/set_parameter_value".format(self.baseurl), params={
  388. 'pluginId': pluginId,
  389. 'parameterId': parameterId,
  390. 'value': value,
  391. })
  392. def set_parameter_midi_channel(self, pluginId, parameterId, channel):
  393. requests.get("{}/set_parameter_midi_channel".format(self.baseurl), params={
  394. 'pluginId': pluginId,
  395. 'parameterId': parameterId,
  396. 'channel': channel,
  397. })
  398. def set_parameter_midi_cc(self, pluginId, parameterId, cc):
  399. requests.get("{}/set_parameter_midi_cc".format(self.baseurl), params={
  400. 'pluginId': pluginId,
  401. 'parameterId': parameterId,
  402. 'cc': cc,
  403. })
  404. def set_program(self, pluginId, programId):
  405. requests.get("{}/set_program".format(self.baseurl), params={
  406. 'pluginId': pluginId,
  407. })
  408. def set_midi_program(self, pluginId, midiProgramId):
  409. requests.get("{}/set_midi_program".format(self.baseurl), params={
  410. 'pluginId': pluginId,
  411. 'midiProgramId': midiProgramId,
  412. })
  413. def set_custom_data(self, pluginId, type_, key, value):
  414. requests.get("{}/set_custom_data".format(self.baseurl), params={
  415. 'pluginId': pluginId,
  416. 'type': type_,
  417. 'key': key,
  418. 'value': value,
  419. })
  420. def set_chunk_data(self, pluginId, chunkData):
  421. requests.get("{}/set_chunk_data".format(self.baseurl), params={
  422. 'pluginId': pluginId,
  423. 'chunkData': chunkData,
  424. })
  425. def prepare_for_save(self, pluginId):
  426. requests.get("{}/prepare_for_save".format(self.baseurl), params={
  427. 'pluginId': pluginId,
  428. })
  429. def reset_parameters(self, pluginId):
  430. requests.get("{}/reset_parameters".format(self.baseurl), params={
  431. 'pluginId': pluginId,
  432. })
  433. def randomize_parameters(self, pluginId):
  434. requests.get("{}/randomize_parameters".format(self.baseurl), params={
  435. 'pluginId': pluginId,
  436. })
  437. def send_midi_note(self, pluginId, channel, note, velocity):
  438. requests.get("{}/send_midi_note".format(self.baseurl), params={
  439. 'pluginId': pluginId,
  440. 'channel': channel,
  441. 'note': note,
  442. 'velocity': velocity,
  443. })
  444. def get_buffer_size(self):
  445. return int(requests.get("{}/get_buffer_size".format(self.baseurl)).text)
  446. def get_sample_rate(self):
  447. return float(requests.get("{}/get_sample_rate".format(self.baseurl)).text)
  448. def get_last_error(self):
  449. return requests.get("{}/get_last_error".format(self.baseurl)).text
  450. def get_host_osc_url_tcp(self):
  451. return requests.get("{}/get_host_osc_url_tcp".format(self.baseurl)).text
  452. def get_host_osc_url_udp(self):
  453. return requests.get("{}/get_host_osc_url_udp".format(self.baseurl)).text