| @@ -731,7 +731,7 @@ CARLA_EXPORT float carla_get_internal_parameter_value(uint pluginId, int32_t par | |||||
| * Get a plugin's peak values. | * Get a plugin's peak values. | ||||
| * @param pluginId Plugin | * @param pluginId Plugin | ||||
| */ | */ | ||||
| float* carla_get_peak_values(uint pluginId); | |||||
| CARLA_EXPORT float* carla_get_peak_values(uint pluginId); | |||||
| /*! | /*! | ||||
| * Get a plugin's input peak value. | * Get a plugin's input peak value. | ||||
| @@ -20,6 +20,7 @@ | |||||
| # Imports (Global) | # Imports (Global) | ||||
| import requests | import requests | ||||
| from websocket import WebSocket, WebSocketConnectionClosedException | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Imports (Custom) | # Imports (Custom) | ||||
| @@ -29,49 +30,6 @@ from carla_backend_qt import * | |||||
| import os | import os | ||||
| from time import sleep | from time import sleep | ||||
| # --------------------------------------------------------------------------------------------------------------------- | |||||
| # Iterates over the content of a file-like object line-by-line. | |||||
| # Based on code by Lars Kellogg-Stedman, see https://github.com/requests/requests/issues/2433 | |||||
| def iterate_stream_nonblock(stream, chunk_size=1024): | |||||
| pending = None | |||||
| while True: | |||||
| try: | |||||
| chunk = os.read(stream.raw.fileno(), chunk_size) | |||||
| except BlockingIOError: | |||||
| break | |||||
| if not chunk: | |||||
| break | |||||
| if pending is not None: | |||||
| chunk = pending + chunk | |||||
| pending = None | |||||
| lines = chunk.splitlines() | |||||
| if lines and lines[-1]: | |||||
| pending = lines.pop() | |||||
| for line in lines: | |||||
| yield line | |||||
| if not pending: | |||||
| break | |||||
| if pending: | |||||
| yield pending | |||||
| # --------------------------------------------------------------------------------------------------------------------- | |||||
| def create_stream(baseurl): | |||||
| stream = requests.get("{}/stream".format(baseurl), stream=True, timeout=0.1) | |||||
| if stream.encoding is None: | |||||
| stream.encoding = 'utf-8' | |||||
| return stream | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Carla Host object for connecting to the REST API backend | # Carla Host object for connecting to the REST API backend | ||||
| @@ -79,14 +37,23 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| def __init__(self): | def __init__(self): | ||||
| CarlaHostQtNull.__init__(self) | CarlaHostQtNull.__init__(self) | ||||
| self.baseurl = "http://localhost:2228" | |||||
| self.stream = create_stream(self.baseurl) | |||||
| self.host = "localhost" | |||||
| self.port = 2228 | |||||
| self.baseurl = "http://{}:{}".format(self.host, self.port) | |||||
| self.socket = WebSocket() | |||||
| self.socket.connect("ws://{}:{}/ws".format(self.host, self.port), timeout=1) | |||||
| self.isRemote = True | self.isRemote = True | ||||
| self.isRunning = True | |||||
| self.peaks = [] | |||||
| for i in range(99): | |||||
| self.peaks.append((0.0, 0.0, 0.0, 0.0)) | |||||
| def get_engine_driver_count(self): | def get_engine_driver_count(self): | ||||
| # FIXME | |||||
| return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) - 1 | |||||
| return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) | |||||
| def get_engine_driver_name(self, index): | def get_engine_driver_name(self, index): | ||||
| return requests.get("{}/get_engine_driver_name".format(self.baseurl), params={ | return requests.get("{}/get_engine_driver_name".format(self.baseurl), params={ | ||||
| @@ -114,13 +81,22 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| return bool(int(requests.get("{}/engine_close".format(self.baseurl)).text)) | return bool(int(requests.get("{}/engine_close".format(self.baseurl)).text)) | ||||
| def engine_idle(self): | def engine_idle(self): | ||||
| closed = False | |||||
| stream = self.stream | |||||
| if not self.isRunning: | |||||
| return | |||||
| while True: | |||||
| try: | |||||
| line = self.socket.recv().strip() | |||||
| except WebSocketConnectionClosedException: | |||||
| self.isRunning = False | |||||
| if self.fEngineCallback is None: | |||||
| self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
| return | |||||
| for line in iterate_stream_nonblock(stream): | |||||
| line = line.decode('utf-8', errors='ignore') | |||||
| if line == "Keep-Alive": | |||||
| return | |||||
| if line.startswith("Carla: "): | |||||
| elif line.startswith("Carla: "): | |||||
| if self.fEngineCallback is None: | if self.fEngineCallback is None: | ||||
| continue | continue | ||||
| @@ -137,15 +113,24 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| # pass to callback | # pass to callback | ||||
| self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr) | self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr) | ||||
| elif line == "Connection: close": | |||||
| if not closed: | |||||
| self.stream = create_stream(self.baseurl) | |||||
| closed = True | |||||
| elif line.startswith("Peaks: "): | |||||
| # split values from line | |||||
| pluginId, value1, value2, value3, value4 = line[7:].split(" ",5) | |||||
| if closed: | |||||
| stream.close() | |||||
| # convert to proper types | |||||
| pluginId = int(pluginId) | |||||
| value1 = float(value1) | |||||
| value2 = float(value2) | |||||
| value3 = float(value3) | |||||
| value4 = float(value4) | |||||
| # store peaks | |||||
| self.peaks[pluginId] = (value1, value2, value3, value4) | |||||
| def is_engine_running(self): | def is_engine_running(self): | ||||
| if not self.isRunning: | |||||
| return False | |||||
| try: | try: | ||||
| return bool(int(requests.get("{}/is_engine_running".format(self.baseurl)).text)) | return bool(int(requests.get("{}/is_engine_running".format(self.baseurl)).text)) | ||||
| except requests.exceptions.ConnectionError: | except requests.exceptions.ConnectionError: | ||||
| @@ -192,7 +177,7 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| def patchbay_refresh(self, external): | def patchbay_refresh(self, external): | ||||
| return bool(int(requests.get("{}/patchbay_refresh".format(self.baseurl), params={ | return bool(int(requests.get("{}/patchbay_refresh".format(self.baseurl), params={ | ||||
| 'external': external, | |||||
| 'external': int(external), | |||||
| }).text)) | }).text)) | ||||
| def transport_play(self): | def transport_play(self): | ||||
| @@ -215,7 +200,13 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text) | return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text) | ||||
| def get_transport_info(self): | def get_transport_info(self): | ||||
| return requests.get("{}/get_transport_info".format(self.baseurl)).json() | |||||
| if self.isRunning: | |||||
| try: | |||||
| return requests.get("{}/get_transport_info".format(self.baseurl)).json() | |||||
| except requests.exceptions.ConnectionError: | |||||
| if self.fEngineCallback is None: | |||||
| self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
| return PyCarlaTransportInfo() | |||||
| def get_current_plugin_count(self): | def get_current_plugin_count(self): | ||||
| return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text) | return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text) | ||||
| @@ -411,10 +402,16 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| }).text) | }).text) | ||||
| def get_current_parameter_value(self, pluginId, parameterId): | def get_current_parameter_value(self, pluginId, parameterId): | ||||
| return float(requests.get("{}/get_current_parameter_value".format(self.baseurl), params={ | |||||
| 'pluginId': pluginId, | |||||
| 'parameterId': parameterId, | |||||
| }).text) | |||||
| if self.isRunning: | |||||
| try: | |||||
| return float(requests.get("{}/get_current_parameter_value".format(self.baseurl), params={ | |||||
| 'pluginId': pluginId, | |||||
| 'parameterId': parameterId, | |||||
| }).text) | |||||
| except requests.exceptions.ConnectionError: | |||||
| if self.fEngineCallback is None: | |||||
| self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
| return 0.0 | |||||
| def get_internal_parameter_value(self, pluginId, parameterId): | def get_internal_parameter_value(self, pluginId, parameterId): | ||||
| return float(requests.get("{}/get_internal_parameter_value".format(self.baseurl), params={ | return float(requests.get("{}/get_internal_parameter_value".format(self.baseurl), params={ | ||||
| @@ -423,28 +420,22 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
| }).text) | }).text) | ||||
| def get_input_peak_value(self, pluginId, isLeft): | def get_input_peak_value(self, pluginId, isLeft): | ||||
| return float(requests.get("{}/get_input_peak_value".format(self.baseurl), params={ | |||||
| 'pluginId': pluginId, | |||||
| 'isLeft': isLeft, | |||||
| }).text) | |||||
| return self.peaks[pluginId][0 if isLeft else 1] | |||||
| def get_output_peak_value(self, pluginId, isLeft): | def get_output_peak_value(self, pluginId, isLeft): | ||||
| return float(requests.get("{}/get_output_peak_value".format(self.baseurl), params={ | |||||
| 'pluginId': pluginId, | |||||
| 'isLeft': isLeft, | |||||
| }).text) | |||||
| return self.peaks[pluginId][2 if isLeft else 3] | |||||
| def set_option(self, pluginId, option, yesNo): | def set_option(self, pluginId, option, yesNo): | ||||
| requests.get("{}/set_option".format(self.baseurl), params={ | requests.get("{}/set_option".format(self.baseurl), params={ | ||||
| 'pluginId': pluginId, | 'pluginId': pluginId, | ||||
| 'option': option, | 'option': option, | ||||
| 'yesNo': yesNo, | |||||
| 'yesNo': int(yesNo), | |||||
| }) | }) | ||||
| def set_active(self, pluginId, onOff): | def set_active(self, pluginId, onOff): | ||||
| requests.get("{}/set_active".format(self.baseurl), params={ | requests.get("{}/set_active".format(self.baseurl), params={ | ||||
| 'pluginId': pluginId, | 'pluginId': pluginId, | ||||
| 'onOff': onOff, | |||||
| 'onOff': int(onOff), | |||||
| }) | }) | ||||
| def set_drywet(self, pluginId, value): | def set_drywet(self, pluginId, value): | ||||
| @@ -23,7 +23,10 @@ endif | |||||
| BUILD_CXX_FLAGS += -I$(CWD) -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils | BUILD_CXX_FLAGS += -I$(CWD) -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils | ||||
| LINK_FLAGS += -L$(BINDIR) -lcarla_standalone2 -lcarla_utils -lrestbed -lpthread -Wl,-rpath=$(shell realpath $(CWD)/../bin) | |||||
| LINK_FLAGS += -Wl,-rpath=$(shell realpath $(CWD)/../bin) | |||||
| LINK_FLAGS += -L$(BINDIR) -lcarla_standalone2 -lcarla_utils | |||||
| LINK_FLAGS += -lrestbed -lssl -lcrypto | |||||
| LINK_FLAGS += -lpthread | |||||
| # ---------------------------------------------------------------------------------------------------------------------- | # ---------------------------------------------------------------------------------------------------------------------- | ||||
| @@ -24,23 +24,15 @@ | |||||
| static bool gEngineRunning = false; | static bool gEngineRunning = false; | ||||
| void engine_idle_handler() | |||||
| { | |||||
| if (gEngineRunning) | |||||
| carla_engine_idle(); | |||||
| } | |||||
| // ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
| static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId, int value1, int value2, float value3, const char* valueStr) | static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId, int value1, int value2, float value3, const char* valueStr) | ||||
| { | { | ||||
| #if 0 | |||||
| carla_stdout("EngineCallback(%p, %u:%s, %u, %i, %i, %f, %s)", | |||||
| ptr, (uint)action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); | |||||
| #endif | |||||
| carla_debug("EngineCallback(%p, %u:%s, %u, %i, %i, %f, %s)", | |||||
| ptr, (uint)action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); | |||||
| char msgBuf[1024]; | char msgBuf[1024]; | ||||
| std::snprintf(msgBuf, 1023, "Carla: %u %u %i %i %f %s\n", action, pluginId, value1, value2, value3, valueStr); | |||||
| std::snprintf(msgBuf, 1023, "Carla: %u %u %i %i %f %s", action, pluginId, value1, value2, value3, valueStr); | |||||
| msgBuf[1023] = '\0'; | msgBuf[1023] = '\0'; | ||||
| switch (action) | switch (action) | ||||
| @@ -56,7 +48,10 @@ static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId | |||||
| break; | break; | ||||
| } | } | ||||
| send_server_side_message(msgBuf); | |||||
| return send_server_side_message(msgBuf); | |||||
| // maybe unused | |||||
| (void)ptr; | |||||
| } | } | ||||
| static const char* FileCallback(void* ptr, FileCallbackOpcode action, bool isDir, const char* title, const char* filter) | static const char* FileCallback(void* ptr, FileCallbackOpcode action, bool isDir, const char* title, const char* filter) | ||||
| @@ -274,7 +269,7 @@ void handle_carla_transport_bpm(const std::shared_ptr<Session> session) | |||||
| const std::shared_ptr<const Request> request = session->get_request(); | const std::shared_ptr<const Request> request = session->get_request(); | ||||
| const double bpm = std::atof(request->get_query_parameter("bpm").c_str()); | const double bpm = std::atof(request->get_query_parameter("bpm").c_str()); | ||||
| CARLA_SAFE_ASSERT_RETURN(bpm > 0.0,) // FIXME | |||||
| CARLA_SAFE_ASSERT_RETURN(bpm > 0.0, session->close(OK)) // FIXME | |||||
| carla_transport_bpm(bpm); | carla_transport_bpm(bpm); | ||||
| session->close(OK); | session->close(OK); | ||||
| @@ -31,11 +31,26 @@ | |||||
| // ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
| std::vector<std::shared_ptr<Session>> gSessions; | |||||
| #include <map> | |||||
| #include <restbed> | |||||
| #include <system_error> | |||||
| #include <openssl/sha.h> | |||||
| #include <openssl/hmac.h> | |||||
| #include <openssl/evp.h> | |||||
| #include <openssl/bio.h> | |||||
| #include <openssl/buffer.h> | |||||
| using namespace std; | |||||
| using namespace restbed; | |||||
| using namespace std::chrono; | |||||
| // std::vector<std::shared_ptr<Session>> gSessions; | |||||
| CarlaStringList gSessionMessages; | CarlaStringList gSessionMessages; | ||||
| CarlaMutex gSessionMessagesMutex; | CarlaMutex gSessionMessagesMutex; | ||||
| std::map< string, shared_ptr< WebSocket > > sockets = { }; | |||||
| // ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
| void send_server_side_message(const char* const message) | void send_server_side_message(const char* const message) | ||||
| @@ -47,20 +62,6 @@ void send_server_side_message(const char* const message) | |||||
| // ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
| static void register_server_side_handler(const std::shared_ptr<Session> session) | |||||
| { | |||||
| const auto headers = std::multimap<std::string, std::string> { | |||||
| { "Connection", "keep-alive" }, | |||||
| { "Cache-Control", "no-cache" }, | |||||
| { "Content-Type", "text/event-stream" }, | |||||
| { "Access-Control-Allow-Origin", "*" } //Only required for demo purposes. | |||||
| }; | |||||
| session->yield(OK, headers, [](const std::shared_ptr<Session> rsession) { | |||||
| gSessions.push_back(rsession); | |||||
| }); | |||||
| } | |||||
| static void event_stream_handler(void) | static void event_stream_handler(void) | ||||
| { | { | ||||
| static bool firstInit = true; | static bool firstInit = true; | ||||
| @@ -71,12 +72,10 @@ static void event_stream_handler(void) | |||||
| carla_stdout("Carla REST-API Server started"); | carla_stdout("Carla REST-API Server started"); | ||||
| } | } | ||||
| gSessions.erase( | |||||
| std::remove_if(gSessions.begin(), gSessions.end(), | |||||
| [](const std::shared_ptr<Session> &a) { | |||||
| return a->is_closed(); | |||||
| }), | |||||
| gSessions.end()); | |||||
| const bool running = carla_is_engine_running(); | |||||
| if (running) | |||||
| carla_engine_idle(); | |||||
| CarlaStringList messages; | CarlaStringList messages; | ||||
| @@ -89,27 +88,214 @@ static void event_stream_handler(void) | |||||
| for (auto message : messages) | for (auto message : messages) | ||||
| { | { | ||||
| // std::puts(message); | |||||
| for (auto entry : sockets) | |||||
| { | |||||
| auto socket = entry.second; | |||||
| if (socket->is_open()) | |||||
| socket->send(message); | |||||
| } | |||||
| } | |||||
| if (running) | |||||
| { | |||||
| if (const uint count = carla_get_current_plugin_count()) | |||||
| { | |||||
| char msgBuf[1024]; | |||||
| float* peaks; | |||||
| for (uint i=0; i<count; ++i) | |||||
| { | |||||
| peaks = carla_get_peak_values(i); | |||||
| CARLA_SAFE_ASSERT_BREAK(peaks != nullptr); | |||||
| std::snprintf(msgBuf, 1023, "Peaks: %u %f %f %f %f", i, peaks[0], peaks[1], peaks[2], peaks[3]); | |||||
| msgBuf[1023] = '\0'; | |||||
| for (auto entry : sockets) | |||||
| { | |||||
| auto socket = entry.second; | |||||
| if (socket->is_open()) | |||||
| socket->send(msgBuf); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| for (auto entry : sockets) | |||||
| { | |||||
| auto socket = entry.second; | |||||
| if (socket->is_open()) | |||||
| socket->send("Keep-Alive"); | |||||
| } | |||||
| } | |||||
| // ------------------------------------------------------------------------------------------------------------------- | |||||
| string base64_encode( const unsigned char* input, int length ) | |||||
| { | |||||
| BIO* bmem, *b64; | |||||
| BUF_MEM* bptr; | |||||
| b64 = BIO_new( BIO_f_base64( ) ); | |||||
| bmem = BIO_new( BIO_s_mem( ) ); | |||||
| b64 = BIO_push( b64, bmem ); | |||||
| BIO_write( b64, input, length ); | |||||
| ( void ) BIO_flush( b64 ); | |||||
| BIO_get_mem_ptr( b64, &bptr ); | |||||
| char* buff = ( char* )malloc( bptr->length ); | |||||
| memcpy( buff, bptr->data, bptr->length - 1 ); | |||||
| buff[ bptr->length - 1 ] = 0; | |||||
| BIO_free_all( b64 ); | |||||
| return buff; | |||||
| } | |||||
| multimap< string, string > build_websocket_handshake_response_headers( const shared_ptr< const Request >& request ) | |||||
| { | |||||
| auto key = request->get_header( "Sec-WebSocket-Key" ); | |||||
| key.append( "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); | |||||
| Byte hash[ SHA_DIGEST_LENGTH ]; | |||||
| SHA1( reinterpret_cast< const unsigned char* >( key.data( ) ), key.length( ), hash ); | |||||
| multimap< string, string > headers; | |||||
| headers.insert( make_pair( "Upgrade", "websocket" ) ); | |||||
| headers.insert( make_pair( "Connection", "Upgrade" ) ); | |||||
| headers.insert( make_pair( "Sec-WebSocket-Accept", base64_encode( hash, SHA_DIGEST_LENGTH ) ) ); | |||||
| for (auto session : gSessions) | |||||
| session->yield(OK, message); | |||||
| return headers; | |||||
| } | |||||
| void close_handler( const shared_ptr< WebSocket > socket ) | |||||
| { | |||||
| carla_stdout("CLOSE %i", __LINE__); | |||||
| if ( socket->is_open( ) ) | |||||
| { | |||||
| auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 00 } ) ); | |||||
| socket->send( response ); | |||||
| } | } | ||||
| carla_stdout("CLOSE %i", __LINE__); | |||||
| const auto key = socket->get_key( ); | |||||
| sockets.erase( key ); | |||||
| fprintf( stderr, "Closed connection to %s.\n", key.data( ) ); | |||||
| } | |||||
| void error_handler( const shared_ptr< WebSocket > socket, const error_code error ) | |||||
| { | |||||
| const auto key = socket->get_key( ); | |||||
| fprintf( stderr, "WebSocket Errored '%s' for %s.\n", error.message( ).data( ), key.data( ) ); | |||||
| } | |||||
| void message_handler( const shared_ptr< WebSocket > source, const shared_ptr< WebSocketMessage > message ) | |||||
| { | |||||
| const auto opcode = message->get_opcode( ); | |||||
| if (const uint count = carla_get_current_plugin_count()) | |||||
| if ( opcode == WebSocketMessage::PING_FRAME ) | |||||
| { | |||||
| auto response = make_shared< WebSocketMessage >( WebSocketMessage::PONG_FRAME, message->get_data( ) ); | |||||
| source->send( response ); | |||||
| } | |||||
| else if ( opcode == WebSocketMessage::PONG_FRAME ) | |||||
| { | { | ||||
| char msgBuf[1024]; | |||||
| float* peaks; | |||||
| //Ignore PONG_FRAME. | |||||
| // | |||||
| //Every time the ping_handler is scheduled to run, it fires off a PING_FRAME to each | |||||
| //WebSocket. The client, if behaving correctly, will respond with a PONG_FRAME. | |||||
| // | |||||
| //On each occasion the underlying TCP socket sees any packet data transfer, whether | |||||
| //a PING, PONG, TEXT, or BINARY... frame. It will automatically reset the timeout counter | |||||
| //leaving the connection active; see also Settings::set_connection_timeout. | |||||
| return; | |||||
| } | |||||
| else if ( opcode == WebSocketMessage::CONNECTION_CLOSE_FRAME ) | |||||
| { | |||||
| source->close( ); | |||||
| } | |||||
| else if ( opcode == WebSocketMessage::BINARY_FRAME ) | |||||
| { | |||||
| //We don't support binary data. | |||||
| auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 03 } ) ); | |||||
| source->send( response ); | |||||
| } | |||||
| else if ( opcode == WebSocketMessage::TEXT_FRAME ) | |||||
| { | |||||
| auto response = make_shared< WebSocketMessage >( *message ); | |||||
| response->set_mask( 0 ); | |||||
| for (uint i=0; i<count; ++i) | |||||
| for ( auto socket : sockets ) | |||||
| { | { | ||||
| peaks = carla_get_peak_values(i); | |||||
| CARLA_SAFE_ASSERT_BREAK(peaks != nullptr); | |||||
| auto destination = socket.second; | |||||
| destination->send( response ); | |||||
| } | |||||
| const auto key = source->get_key( ); | |||||
| const auto data = String::format( "Received message '%.*s' from %s\n", message->get_data( ).size( ), message->get_data( ).data( ), key.data( ) ); | |||||
| fprintf( stderr, "%s", data.data( ) ); | |||||
| } | |||||
| } | |||||
| std::snprintf(msgBuf, 1023, "Peaks: %u %f %f %f %f\n", i, peaks[0], peaks[1], peaks[2], peaks[3]); | |||||
| msgBuf[1023] = '\0'; | |||||
| void get_method_handler(const shared_ptr<Session> session) | |||||
| { | |||||
| carla_stdout("HERE %i", __LINE__); | |||||
| const auto request = session->get_request(); | |||||
| const auto connection_header = request->get_header("connection", String::lowercase); | |||||
| carla_stdout("HERE %i", __LINE__); | |||||
| for (auto session : gSessions) | |||||
| session->yield(OK, msgBuf); | |||||
| if ( connection_header.find( "upgrade" ) not_eq string::npos ) | |||||
| { | |||||
| if ( request->get_header( "upgrade", String::lowercase ) == "websocket" ) | |||||
| { | |||||
| const auto headers = build_websocket_handshake_response_headers( request ); | |||||
| session->upgrade( SWITCHING_PROTOCOLS, headers, [ ]( const shared_ptr< WebSocket > socket ) | |||||
| { | |||||
| if ( socket->is_open( ) ) | |||||
| { | |||||
| socket->set_close_handler( close_handler ); | |||||
| socket->set_error_handler( error_handler ); | |||||
| socket->set_message_handler( message_handler ); | |||||
| socket->send("Welcome to Corvusoft Chat!"); | |||||
| auto key = socket->get_key( ); | |||||
| sockets[key] = socket; | |||||
| } | |||||
| else | |||||
| { | |||||
| fprintf( stderr, "WebSocket Negotiation Failed: Client closed connection.\n" ); | |||||
| } | |||||
| } ); | |||||
| return; | |||||
| } | |||||
| } | |||||
| session->close( BAD_REQUEST ); | |||||
| } | |||||
| void ping_handler( void ) | |||||
| { | |||||
| for ( auto entry : sockets ) | |||||
| { | |||||
| auto key = entry.first; | |||||
| auto socket = entry.second; | |||||
| if ( socket->is_open( ) ) | |||||
| { | |||||
| socket->send( WebSocketMessage::PING_FRAME ); | |||||
| } | |||||
| else | |||||
| { | |||||
| socket->close( ); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -132,11 +318,11 @@ int main(int, const char**) | |||||
| { | { | ||||
| Service service; | Service service; | ||||
| // server-side messages | |||||
| // websocket | |||||
| { | { | ||||
| std::shared_ptr<Resource> resource = std::make_shared<Resource>(); | std::shared_ptr<Resource> resource = std::make_shared<Resource>(); | ||||
| resource->set_path("/stream"); | |||||
| resource->set_method_handler("GET", register_server_side_handler); | |||||
| resource->set_path("/ws"); | |||||
| resource->set_method_handler("GET", get_method_handler); | |||||
| service.publish(resource); | service.publish(resource); | ||||
| } | } | ||||
| @@ -251,8 +437,8 @@ int main(int, const char**) | |||||
| make_resource(service, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info); | make_resource(service, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info); | ||||
| // schedule events | // schedule events | ||||
| service.schedule(engine_idle_handler); // FIXME, crashes on fast times, but we need ~30Hz for OSC.. | |||||
| service.schedule(event_stream_handler, std::chrono::milliseconds(500)); | |||||
| service.schedule(event_stream_handler, std::chrono::milliseconds(33)); | |||||
| service.schedule(ping_handler, milliseconds(5000)); | |||||
| std::shared_ptr<Settings> settings = std::make_shared<Settings>(); | std::shared_ptr<Settings> settings = std::make_shared<Settings>(); | ||||
| settings->set_port(2228); | settings->set_port(2228); | ||||