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.

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