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.

rest-server.cpp 18KB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*
  2. * Carla REST API Server
  3. * Copyright (C) 2018 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. /* NOTE
  18. * Even though Carla is GPL, restbed if AGPL.
  19. * As such, the resulting binary will be AGPL.
  20. * Take this into consideration before deploying it to any servers.
  21. */
  22. #include "common.hpp"
  23. #include "carla-host.cpp"
  24. #include "carla-utils.cpp"
  25. #include "CarlaMutex.hpp"
  26. #include "CarlaStringList.hpp"
  27. // -------------------------------------------------------------------------------------------------------------------
  28. #include <map>
  29. #include <restbed>
  30. #include <system_error>
  31. #include <openssl/sha.h>
  32. #include <openssl/hmac.h>
  33. #include <openssl/evp.h>
  34. #include <openssl/bio.h>
  35. #include <openssl/buffer.h>
  36. using namespace std;
  37. using namespace restbed;
  38. using namespace std::chrono;
  39. // std::vector<std::shared_ptr<Session>> gSessions;
  40. CarlaStringList gSessionMessages;
  41. CarlaMutex gSessionMessagesMutex;
  42. std::map< string, shared_ptr< WebSocket > > sockets = { };
  43. // -------------------------------------------------------------------------------------------------------------------
  44. void send_server_side_message(const char* const message)
  45. {
  46. const CarlaMutexLocker cml(gSessionMessagesMutex);
  47. gSessionMessages.append(message);
  48. }
  49. // -------------------------------------------------------------------------------------------------------------------
  50. static void event_stream_handler(void)
  51. {
  52. static bool firstInit = true;
  53. if (firstInit)
  54. {
  55. firstInit = false;
  56. carla_stdout("Carla REST-API Server started");
  57. }
  58. const bool running = carla_is_engine_running();
  59. if (running)
  60. carla_engine_idle();
  61. CarlaStringList messages;
  62. {
  63. const CarlaMutexLocker cml(gSessionMessagesMutex);
  64. if (gSessionMessages.count() > 0)
  65. gSessionMessages.moveTo(messages);
  66. }
  67. for (auto message : messages)
  68. {
  69. for (auto entry : sockets)
  70. {
  71. auto socket = entry.second;
  72. if (socket->is_open())
  73. socket->send(message);
  74. }
  75. }
  76. if (running)
  77. {
  78. if (const uint count = carla_get_current_plugin_count())
  79. {
  80. char msgBuf[1024];
  81. float* peaks;
  82. for (uint i=0; i<count; ++i)
  83. {
  84. peaks = carla_get_peak_values(i);
  85. CARLA_SAFE_ASSERT_BREAK(peaks != nullptr);
  86. std::snprintf(msgBuf, 1023, "Peaks: %u %f %f %f %f", i, peaks[0], peaks[1], peaks[2], peaks[3]);
  87. msgBuf[1023] = '\0';
  88. for (auto entry : sockets)
  89. {
  90. auto socket = entry.second;
  91. if (socket->is_open())
  92. socket->send(msgBuf);
  93. }
  94. }
  95. }
  96. }
  97. for (auto entry : sockets)
  98. {
  99. auto socket = entry.second;
  100. if (socket->is_open())
  101. socket->send("Keep-Alive");
  102. }
  103. }
  104. // -------------------------------------------------------------------------------------------------------------------
  105. string base64_encode( const unsigned char* input, int length )
  106. {
  107. BIO* bmem, *b64;
  108. BUF_MEM* bptr;
  109. b64 = BIO_new( BIO_f_base64( ) );
  110. bmem = BIO_new( BIO_s_mem( ) );
  111. b64 = BIO_push( b64, bmem );
  112. BIO_write( b64, input, length );
  113. ( void ) BIO_flush( b64 );
  114. BIO_get_mem_ptr( b64, &bptr );
  115. char* buff = ( char* )malloc( bptr->length );
  116. memcpy( buff, bptr->data, bptr->length - 1 );
  117. buff[ bptr->length - 1 ] = 0;
  118. BIO_free_all( b64 );
  119. return buff;
  120. }
  121. multimap< string, string > build_websocket_handshake_response_headers( const shared_ptr< const Request >& request )
  122. {
  123. auto key = request->get_header( "Sec-WebSocket-Key" );
  124. key.append( "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" );
  125. Byte hash[ SHA_DIGEST_LENGTH ];
  126. SHA1( reinterpret_cast< const unsigned char* >( key.data( ) ), key.length( ), hash );
  127. multimap< string, string > headers;
  128. headers.insert( make_pair( "Upgrade", "websocket" ) );
  129. headers.insert( make_pair( "Connection", "Upgrade" ) );
  130. headers.insert( make_pair( "Sec-WebSocket-Accept", base64_encode( hash, SHA_DIGEST_LENGTH ) ) );
  131. return headers;
  132. }
  133. void close_handler( const shared_ptr< WebSocket > socket )
  134. {
  135. carla_stdout("CLOSE %i", __LINE__);
  136. if ( socket->is_open( ) )
  137. {
  138. auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 00 } ) );
  139. socket->send( response );
  140. }
  141. carla_stdout("CLOSE %i", __LINE__);
  142. const auto key = socket->get_key( );
  143. sockets.erase( key );
  144. fprintf( stderr, "Closed connection to %s.\n", key.data( ) );
  145. }
  146. void error_handler( const shared_ptr< WebSocket > socket, const error_code error )
  147. {
  148. const auto key = socket->get_key( );
  149. fprintf( stderr, "WebSocket Errored '%s' for %s.\n", error.message( ).data( ), key.data( ) );
  150. }
  151. void message_handler( const shared_ptr< WebSocket > source, const shared_ptr< WebSocketMessage > message )
  152. {
  153. const auto opcode = message->get_opcode( );
  154. if ( opcode == WebSocketMessage::PING_FRAME )
  155. {
  156. auto response = make_shared< WebSocketMessage >( WebSocketMessage::PONG_FRAME, message->get_data( ) );
  157. source->send( response );
  158. }
  159. else if ( opcode == WebSocketMessage::PONG_FRAME )
  160. {
  161. //Ignore PONG_FRAME.
  162. //
  163. //Every time the ping_handler is scheduled to run, it fires off a PING_FRAME to each
  164. //WebSocket. The client, if behaving correctly, will respond with a PONG_FRAME.
  165. //
  166. //On each occasion the underlying TCP socket sees any packet data transfer, whether
  167. //a PING, PONG, TEXT, or BINARY... frame. It will automatically reset the timeout counter
  168. //leaving the connection active; see also Settings::set_connection_timeout.
  169. return;
  170. }
  171. else if ( opcode == WebSocketMessage::CONNECTION_CLOSE_FRAME )
  172. {
  173. source->close( );
  174. }
  175. else if ( opcode == WebSocketMessage::BINARY_FRAME )
  176. {
  177. //We don't support binary data.
  178. auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 03 } ) );
  179. source->send( response );
  180. }
  181. else if ( opcode == WebSocketMessage::TEXT_FRAME )
  182. {
  183. auto response = make_shared< WebSocketMessage >( *message );
  184. response->set_mask( 0 );
  185. for ( auto socket : sockets )
  186. {
  187. auto destination = socket.second;
  188. destination->send( response );
  189. }
  190. const auto key = source->get_key( );
  191. const auto data = String::format( "Received message '%.*s' from %s\n", message->get_data( ).size( ), message->get_data( ).data( ), key.data( ) );
  192. fprintf( stderr, "%s", data.data( ) );
  193. }
  194. }
  195. void get_method_handler(const shared_ptr<Session> session)
  196. {
  197. carla_stdout("HERE %i", __LINE__);
  198. const auto request = session->get_request();
  199. const auto connection_header = request->get_header("connection", String::lowercase);
  200. carla_stdout("HERE %i", __LINE__);
  201. if ( connection_header.find( "upgrade" ) not_eq string::npos )
  202. {
  203. if ( request->get_header( "upgrade", String::lowercase ) == "websocket" )
  204. {
  205. const auto headers = build_websocket_handshake_response_headers( request );
  206. session->upgrade( SWITCHING_PROTOCOLS, headers, [ ]( const shared_ptr< WebSocket > socket )
  207. {
  208. if ( socket->is_open( ) )
  209. {
  210. socket->set_close_handler( close_handler );
  211. socket->set_error_handler( error_handler );
  212. socket->set_message_handler( message_handler );
  213. socket->send("Welcome to Corvusoft Chat!");
  214. auto key = socket->get_key( );
  215. sockets[key] = socket;
  216. }
  217. else
  218. {
  219. fprintf( stderr, "WebSocket Negotiation Failed: Client closed connection.\n" );
  220. }
  221. } );
  222. return;
  223. }
  224. }
  225. session->close( BAD_REQUEST );
  226. }
  227. void ping_handler( void )
  228. {
  229. for ( auto entry : sockets )
  230. {
  231. auto key = entry.first;
  232. auto socket = entry.second;
  233. if ( socket->is_open( ) )
  234. {
  235. socket->send( WebSocketMessage::PING_FRAME );
  236. }
  237. else
  238. {
  239. socket->close( );
  240. }
  241. }
  242. }
  243. // -------------------------------------------------------------------------------------------------------------------
  244. static void make_resource(Service& service,
  245. const char* const path,
  246. const std::function<void (const std::shared_ptr<Session>)>& callback)
  247. {
  248. std::shared_ptr<Resource> resource = std::make_shared<Resource>();
  249. resource->set_path(path);
  250. resource->set_method_handler("GET", callback);
  251. service.publish(resource);
  252. }
  253. // -------------------------------------------------------------------------------------------------------------------
  254. int main(int, const char**)
  255. {
  256. Service service;
  257. // websocket
  258. {
  259. std::shared_ptr<Resource> resource = std::make_shared<Resource>();
  260. resource->set_path("/ws");
  261. resource->set_method_handler("GET", get_method_handler);
  262. service.publish(resource);
  263. }
  264. // carla-host
  265. make_resource(service, "/get_engine_driver_count", handle_carla_get_engine_driver_count);
  266. make_resource(service, "/get_engine_driver_name", handle_carla_get_engine_driver_name);
  267. make_resource(service, "/get_engine_driver_device_names", handle_carla_get_engine_driver_device_names);
  268. make_resource(service, "/get_engine_driver_device_info", handle_carla_get_engine_driver_device_info);
  269. make_resource(service, "/engine_init", handle_carla_engine_init);
  270. make_resource(service, "/engine_close", handle_carla_engine_close);
  271. make_resource(service, "/is_engine_running", handle_carla_is_engine_running);
  272. make_resource(service, "/set_engine_about_to_close", handle_carla_set_engine_about_to_close);
  273. make_resource(service, "/set_engine_option", handle_carla_set_engine_option);
  274. make_resource(service, "/load_file", handle_carla_load_file);
  275. make_resource(service, "/load_project", handle_carla_load_project);
  276. make_resource(service, "/save_project", handle_carla_save_project);
  277. make_resource(service, "/patchbay_connect", handle_carla_patchbay_connect);
  278. make_resource(service, "/patchbay_disconnect", handle_carla_patchbay_disconnect);
  279. make_resource(service, "/patchbay_refresh", handle_carla_patchbay_refresh);
  280. make_resource(service, "/transport_play", handle_carla_transport_play);
  281. make_resource(service, "/transport_pause", handle_carla_transport_pause);
  282. make_resource(service, "/transport_bpm", handle_carla_transport_bpm);
  283. make_resource(service, "/transport_relocate", handle_carla_transport_relocate);
  284. make_resource(service, "/get_current_transport_frame", handle_carla_get_current_transport_frame);
  285. make_resource(service, "/get_transport_info", handle_carla_get_transport_info);
  286. make_resource(service, "/get_current_plugin_count", handle_carla_get_current_plugin_count);
  287. make_resource(service, "/get_max_plugin_number", handle_carla_get_max_plugin_number);
  288. make_resource(service, "/add_plugin", handle_carla_add_plugin);
  289. make_resource(service, "/remove_plugin", handle_carla_remove_plugin);
  290. make_resource(service, "/remove_all_plugins", handle_carla_remove_all_plugins);
  291. make_resource(service, "/rename_plugin", handle_carla_rename_plugin);
  292. make_resource(service, "/clone_plugin", handle_carla_clone_plugin);
  293. make_resource(service, "/replace_plugin", handle_carla_replace_plugin);
  294. make_resource(service, "/switch_plugins", handle_carla_switch_plugins);
  295. make_resource(service, "/load_plugin_state", handle_carla_load_plugin_state);
  296. make_resource(service, "/save_plugin_state", handle_carla_save_plugin_state);
  297. make_resource(service, "/export_plugin_lv2", handle_carla_export_plugin_lv2);
  298. make_resource(service, "/get_plugin_info", handle_carla_get_plugin_info);
  299. make_resource(service, "/get_audio_port_count_info", handle_carla_get_audio_port_count_info);
  300. make_resource(service, "/get_midi_port_count_info", handle_carla_get_midi_port_count_info);
  301. make_resource(service, "/get_parameter_count_info", handle_carla_get_parameter_count_info);
  302. make_resource(service, "/get_parameter_info", handle_carla_get_parameter_info);
  303. make_resource(service, "/get_parameter_scalepoint_info", handle_carla_get_parameter_scalepoint_info);
  304. make_resource(service, "/get_parameter_data", handle_carla_get_parameter_data);
  305. make_resource(service, "/get_parameter_ranges", handle_carla_get_parameter_ranges);
  306. make_resource(service, "/get_midi_program_data", handle_carla_get_midi_program_data);
  307. make_resource(service, "/get_custom_data", handle_carla_get_custom_data);
  308. make_resource(service, "/get_custom_data_value", handle_carla_get_custom_data_value);
  309. make_resource(service, "/get_chunk_data", handle_carla_get_chunk_data);
  310. make_resource(service, "/get_parameter_count", handle_carla_get_parameter_count);
  311. make_resource(service, "/get_program_count", handle_carla_get_program_count);
  312. make_resource(service, "/get_midi_program_count", handle_carla_get_midi_program_count);
  313. make_resource(service, "/get_custom_data_count", handle_carla_get_custom_data_count);
  314. make_resource(service, "/get_parameter_text", handle_carla_get_parameter_text);
  315. make_resource(service, "/get_program_name", handle_carla_get_program_name);
  316. make_resource(service, "/get_midi_program_name", handle_carla_get_midi_program_name);
  317. make_resource(service, "/get_real_plugin_name", handle_carla_get_real_plugin_name);
  318. make_resource(service, "/get_current_program_index", handle_carla_get_current_program_index);
  319. make_resource(service, "/get_current_midi_program_index", handle_carla_get_current_midi_program_index);
  320. make_resource(service, "/get_default_parameter_value", handle_carla_get_default_parameter_value);
  321. make_resource(service, "/get_current_parameter_value", handle_carla_get_current_parameter_value);
  322. make_resource(service, "/get_internal_parameter_value", handle_carla_get_internal_parameter_value);
  323. make_resource(service, "/get_input_peak_value", handle_carla_get_input_peak_value);
  324. make_resource(service, "/get_output_peak_value", handle_carla_get_output_peak_value);
  325. make_resource(service, "/set_active", handle_carla_set_active);
  326. make_resource(service, "/set_drywet", handle_carla_set_drywet);
  327. make_resource(service, "/set_volume", handle_carla_set_volume);
  328. make_resource(service, "/set_balance_left", handle_carla_set_balance_left);
  329. make_resource(service, "/set_balance_right", handle_carla_set_balance_right);
  330. make_resource(service, "/set_panning", handle_carla_set_panning);
  331. make_resource(service, "/set_ctrl_channel", handle_carla_set_ctrl_channel);
  332. make_resource(service, "/set_option", handle_carla_set_option);
  333. make_resource(service, "/set_parameter_value", handle_carla_set_parameter_value);
  334. make_resource(service, "/set_parameter_midi_channel", handle_carla_set_parameter_midi_channel);
  335. make_resource(service, "/set_parameter_midi_cc", handle_carla_set_parameter_midi_cc);
  336. make_resource(service, "/set_program", handle_carla_set_program);
  337. make_resource(service, "/set_midi_program", handle_carla_set_midi_program);
  338. make_resource(service, "/set_custom_data", handle_carla_set_custom_data);
  339. make_resource(service, "/set_chunk_data", handle_carla_set_chunk_data);
  340. make_resource(service, "/prepare_for_save", handle_carla_prepare_for_save);
  341. make_resource(service, "/reset_parameters", handle_carla_reset_parameters);
  342. make_resource(service, "/randomize_parameters", handle_carla_randomize_parameters);
  343. make_resource(service, "/send_midi_note", handle_carla_send_midi_note);
  344. make_resource(service, "/get_buffer_size", handle_carla_get_buffer_size);
  345. make_resource(service, "/get_sample_rate", handle_carla_get_sample_rate);
  346. make_resource(service, "/get_last_error", handle_carla_get_last_error);
  347. make_resource(service, "/get_host_osc_url_tcp", handle_carla_get_host_osc_url_tcp);
  348. make_resource(service, "/get_host_osc_url_udp", handle_carla_get_host_osc_url_udp);
  349. // carla-utils
  350. make_resource(service, "/get_complete_license_text", handle_carla_get_complete_license_text);
  351. make_resource(service, "/get_supported_file_extensions", handle_carla_get_supported_file_extensions);
  352. make_resource(service, "/get_supported_features", handle_carla_get_supported_features);
  353. make_resource(service, "/get_cached_plugin_count", handle_carla_get_cached_plugin_count);
  354. make_resource(service, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info);
  355. // schedule events
  356. service.schedule(event_stream_handler, std::chrono::milliseconds(33));
  357. service.schedule(ping_handler, milliseconds(5000));
  358. std::shared_ptr<Settings> settings = std::make_shared<Settings>();
  359. settings->set_port(2228);
  360. settings->set_default_header("Connection", "close");
  361. service.start(settings);
  362. return 0;
  363. }
  364. // -------------------------------------------------------------------------------------------------------------------