diff --git a/include/bridge.hpp b/include/bridge.hpp new file mode 100644 index 00000000..aeea8dc3 --- /dev/null +++ b/include/bridge.hpp @@ -0,0 +1,14 @@ +#pragma once + + +#define BRIDGE_CHANNELS 16 + + +namespace rack { + + +void bridgeInit(); +void bridgeDestroy(); + + +} // namespace rack diff --git a/src/bridge.cpp b/src/bridge.cpp new file mode 100644 index 00000000..3e4ab263 --- /dev/null +++ b/src/bridge.cpp @@ -0,0 +1,303 @@ +#include "bridge.hpp" +#include "util/common.hpp" +#include "dsp/ringbuffer.hpp" + +#include + +#include +#include +#include +#include +#include +#include + + + +namespace rack { + + + +enum BridgeCommand { + NO_COMMAND = 0, + START_COMMAND, + QUIT_COMMAND, + CHANNEL_SET_COMMAND, + AUDIO_SAMPLE_RATE_SET_COMMAND, + AUDIO_CHANNELS_SET_COMMAND, + AUDIO_BUFFER_SEND_COMMAND, + MIDI_MESSAGE_SEND_COMMAND, + NUM_COMMANDS +}; + + +static std::thread serverThread; +static bool serverQuit; + + +#define RECV_BUFFER_SIZE (1<<13) +#define RECV_QUEUE_SIZE (1<<17) + + +struct BridgeClientConnection { + RingBuffer recvQueue; + BridgeCommand currentCommand = START_COMMAND; + bool closeRequested = false; + int channel = -1; + int sampleRate = -1; + int audioChannels = 0; + int audioBufferLength = -1; + // TEMP + // FILE *audioOutputFile; + + BridgeClientConnection() { + // audioOutputFile = fopen("out.f32", "w"); + // assert(audioOutputFile); + } + + ~BridgeClientConnection() { + // fclose(audioOutputFile); + } + + /** Does not check if the queue has enough data. You must do that yourself. */ + template + T shift() { + T x; + recvQueue.shiftBuffer((uint8_t*) &x, sizeof(x)); + return x; + } + + /** Steps the state machine + Returns true if step() should be called again + */ + bool step() { + switch (currentCommand) { + case NO_COMMAND: { + if (recvQueue.size() >= 1) { + // Read command type + uint8_t c = shift(); + currentCommand = (BridgeCommand) c; + return true; + } + } break; + + case START_COMMAND: { + // To prevent other TCP protocols from connecting, require a "password" on startup to continue the connection. + const int password = 0xff00fefd; + if (recvQueue.size() >= 4) { + int p = shift(); + if (p == password) { + currentCommand = NO_COMMAND; + return true; + } + else { + closeRequested = true; + } + } + } break; + + case QUIT_COMMAND: { + closeRequested = true; + currentCommand = NO_COMMAND; + debug("Quitting!"); + } break; + + case CHANNEL_SET_COMMAND: { + if (recvQueue.size() >= 1) { + channel = shift(); + debug("Set channel %d", channel); + currentCommand = NO_COMMAND; + return true; + } + } break; + + case AUDIO_SAMPLE_RATE_SET_COMMAND: { + if (recvQueue.size() >= 4) { + sampleRate = shift(); + debug("Set sample rate %d", sampleRate); + currentCommand = NO_COMMAND; + return true; + } + } break; + + case AUDIO_CHANNELS_SET_COMMAND: { + if (recvQueue.size() >= 1) { + audioChannels = shift(); + debug("Set audio channels %d", channel); + currentCommand = NO_COMMAND; + return true; + } + } break; + + case AUDIO_BUFFER_SEND_COMMAND: { + if (audioBufferLength < 0) { + // Get audio buffer size + if (recvQueue.size() >= 4) { + audioBufferLength = shift(); + if (audioBufferLength <= RECV_QUEUE_SIZE) { + return true; + } + else { + // Audio buffer is too large + closeRequested = true; + } + } + } + else { + if (recvQueue.size() >= (size_t) audioBufferLength) { + // TODO Do something with the data + recvQueue.start += audioBufferLength; + debug("Received %d audio samples", audioBufferLength); + audioBufferLength = -1; + currentCommand = NO_COMMAND; + return true; + } + } + } break; + + case MIDI_MESSAGE_SEND_COMMAND: { + if (recvQueue.size() >= 3) { + uint8_t midiBuffer[3]; + recvQueue.shiftBuffer(midiBuffer, 3); + debug("MIDI: %02x %02x %02x", midiBuffer[0], midiBuffer[1], midiBuffer[2]); + currentCommand = NO_COMMAND; + return true; + } + } break; + + default: { + warn("Bridge client: bad command detected, closing"); + closeRequested = true; + } break; + } + return false; + } + + void recv(uint8_t *buffer, int length) { + // Make sure we can fill the buffer before filling it + if (recvQueue.capacity() < (size_t) length) { + // If we can't accept it, future messages will be incomplete + closeRequested = true; + return; + } + + recvQueue.pushBuffer(buffer, length); + + // Loop the state machine until it returns false + while (step()) {} + } +}; + + + +static void clientRun(int client) { + int err; + + // // Get client address + // struct sockaddr_in addr; + // socklen_t clientAddrLen = sizeof(addr); + // err = getpeername(client, (struct sockaddr*) &addr, &clientAddrLen); + // assert(!err); + + // // Get client IP address + // struct in_addr ipAddr = addr.sin_addr; + // char ipBuffer[INET_ADDRSTRLEN]; + // inet_ntop(AF_INET, &ipAddr, ipBuffer, INET_ADDRSTRLEN); + + // info("Bridge client %s connected", ipBuffer); + info("Bridge client connected"); + +#ifndef ARCH_LIN + // Avoid SIGPIPE + int flag = 1; + setsockopt(client, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(int)); +#endif + + BridgeClientConnection connection; + + while (!connection.closeRequested) { + uint8_t buffer[RECV_BUFFER_SIZE]; +#ifdef ARCH_LIN + ssize_t received = recv(client, buffer, sizeof(buffer), MSG_NOSIGNAL); +#else + ssize_t received = recv(client, buffer, sizeof(buffer), 0); +#endif + if (received <= 0) + break; + + connection.recv(buffer, received); + } + + info("Bridge client closed"); + err = close(client); + (void) err; +} + +static void serverRun() { + int err; + + // Open socket + int server = socket(AF_INET, SOCK_STREAM, 0); + if (server < 0) + return; + + // Bind to 127.0.0.1 + const std::string host = "127.0.0.1"; + const int port = 5000; + struct sockaddr_in serverAddr; + memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr); + serverAddr.sin_port = htons(port); + err = bind(server, (struct sockaddr*) &serverAddr, sizeof(serverAddr)); + if (err) { + warn("Bridge server bind() failed"); + goto serverRun_cleanup; + } + + // Listen for clients + err = listen(server, 20); + if (err) { + warn("Bridge server listen() failed"); + goto serverRun_cleanup; + } + info("Bridge server started"); + + // Make server non-blocking + err = fcntl(server, F_SETFL, fcntl(server, F_GETFL, 0) | O_NONBLOCK); + serverQuit = false; + + while (!serverQuit) { + // Accept client socket + + int client = accept(server, NULL, NULL); + if (client < 0) { + // Wait a bit before attempting to accept another client + std::this_thread::sleep_for(std::chrono::duration(0.1)); + continue; + } + + // Launch client thread + std::thread clientThread(clientRun, client); + clientThread.detach(); + } + + // Cleanup +serverRun_cleanup: + err = close(server); + (void) err; + info("Bridge server closed"); +} + + +void bridgeInit() { + serverThread = std::thread(serverRun); +} + +void bridgeDestroy() { + serverQuit = true; + serverThread.join(); +} + + + +} // namespace rack diff --git a/src/main.cpp b/src/main.cpp index a1f1a157..1758f2c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "plugin.hpp" #include "settings.hpp" #include "asset.hpp" +#include "bridge.hpp" #include #include "osdialog.h" @@ -33,6 +34,7 @@ int main(int argc, char* argv[]) { pluginInit(); engineInit(); + bridgeInit(); windowInit(); appInit(); settingsLoad(assetLocal("settings.json")); @@ -59,6 +61,7 @@ int main(int argc, char* argv[]) { settingsSave(assetLocal("settings.json")); appDestroy(); windowDestroy(); + bridgeDestroy(); engineDestroy(); pluginDestroy();