#include "stdafx.h" #include "OscServer.h" #include #include #include #include #include #include #pragma comment(lib, "Ws2_32.lib") namespace { bool InitializeWinsock(std::string& error) { WSADATA wsaData = {}; const int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { error = "WSAStartup failed."; return false; } return true; } std::vector SplitAddress(const std::string& address) { std::vector parts; std::size_t start = !address.empty() && address[0] == '/' ? 1 : 0; while (start <= address.size()) { const std::size_t slash = address.find('/', start); const std::size_t end = slash == std::string::npos ? address.size() : slash; if (end > start) parts.push_back(address.substr(start, end - start)); if (slash == std::string::npos) break; start = slash + 1; } return parts; } } OscServer::OscServer() : mPort(0), mRunning(false) { } OscServer::~OscServer() { Stop(); } bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::string& error) { if (port == 0) return true; mCallbacks = callbacks; mPort = port; if (!InitializeWinsock(error)) return false; mSocket.reset(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); if (!mSocket.valid()) { error = "Could not create OSC UDP socket."; return false; } DWORD timeoutMilliseconds = 100; setsockopt(mSocket.get(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeoutMilliseconds), sizeof(timeoutMilliseconds)); sockaddr_in address = {}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_port = htons(static_cast(port)); if (bind(mSocket.get(), reinterpret_cast(&address), sizeof(address)) != 0) { error = "Could not bind OSC listener to UDP port " + std::to_string(port) + "."; mSocket.reset(); return false; } mRunning = true; mThread = std::thread(&OscServer::ServerLoop, this); return true; } void OscServer::Stop() { mRunning = false; mSocket.reset(); if (mThread.joinable()) mThread.join(); } void OscServer::ServerLoop() { std::array buffer = {}; while (mRunning) { sockaddr_in sender = {}; int senderLength = sizeof(sender); const int byteCount = recvfrom(mSocket.get(), buffer.data(), static_cast(buffer.size()), 0, reinterpret_cast(&sender), &senderLength); if (byteCount <= 0) continue; OscMessage message; std::string error; if (DecodeMessage(buffer.data(), byteCount, message, error)) { if (!DispatchMessage(message, error) && !error.empty()) OutputDebugStringA(("OSC dispatch failed: " + error + "\n").c_str()); } else if (!error.empty()) { OutputDebugStringA(("OSC decode failed: " + error + "\n").c_str()); } } } bool OscServer::DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const { int offset = 0; if (!ReadPaddedString(data, byteCount, offset, message.address) || message.address.empty() || message.address[0] != '/') { error = "Invalid OSC address."; return false; } std::string typeTags; if (!ReadPaddedString(data, byteCount, offset, typeTags) || typeTags.empty() || typeTags[0] != ',') { error = "Invalid OSC type tag string."; return false; } if (typeTags.size() < 2) { error = "OSC message has no parameter value."; return false; } std::vector values; for (std::size_t index = 1; index < typeTags.size(); ++index) { std::string valueJson; if (!DecodeArgument(data, byteCount, offset, typeTags[index], valueJson)) { error = "Unsupported or malformed OSC value type."; return false; } values.push_back(valueJson); } if (values.size() == 1) { message.valueJson = values.front(); return true; } std::ostringstream arrayJson; arrayJson << "["; for (std::size_t index = 0; index < values.size(); ++index) { if (index > 0) arrayJson << ","; arrayJson << values[index]; } arrayJson << "]"; message.valueJson = arrayJson.str(); return true; } bool OscServer::DispatchMessage(const OscMessage& message, std::string& error) const { const std::vector parts = SplitAddress(message.address); if (parts.size() != 3 || parts[0] != "VideoShaderToys") { error = "Unsupported OSC address: " + message.address; return false; } return mCallbacks.updateParameter && mCallbacks.updateParameter(parts[1], parts[2], message.valueJson, error); } bool OscServer::DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson) { if (valueType == 'f') { double value = 0.0; if (!ReadFloat32(data, byteCount, offset, value)) return false; std::ostringstream stream; stream << std::setprecision(9) << value; valueJson = stream.str(); return true; } if (valueType == 'd') { double value = 0.0; if (!ReadFloat64(data, byteCount, offset, value)) return false; std::ostringstream stream; stream << std::setprecision(17) << value; valueJson = stream.str(); return true; } if (valueType == 'i') { int value = 0; if (!ReadInt32(data, byteCount, offset, value)) return false; valueJson = std::to_string(value); return true; } if (valueType == 's') { std::string value; if (!ReadPaddedString(data, byteCount, offset, value)) return false; valueJson = BuildJsonString(value); return true; } if (valueType == 'T' || valueType == 'F') { valueJson = valueType == 'T' ? "true" : "false"; return true; } return false; } bool OscServer::ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value) { if (offset < 0 || offset >= byteCount) return false; const int start = offset; while (offset < byteCount && data[offset] != '\0') ++offset; if (offset >= byteCount) return false; value.assign(data + start, data + offset); ++offset; while (offset % 4 != 0) ++offset; return offset <= byteCount; } bool OscServer::ReadInt32(const char* data, int byteCount, int& offset, int& value) { if (offset + 4 > byteCount) return false; const unsigned char* bytes = reinterpret_cast(data + offset); value = static_cast((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); offset += 4; return true; } bool OscServer::ReadFloat32(const char* data, int byteCount, int& offset, double& value) { int bits = 0; if (!ReadInt32(data, byteCount, offset, bits)) return false; float floatValue = 0.0f; const unsigned int unsignedBits = static_cast(bits); std::memcpy(&floatValue, &unsignedBits, sizeof(floatValue)); value = static_cast(floatValue); return true; } bool OscServer::ReadFloat64(const char* data, int byteCount, int& offset, double& value) { if (offset + 8 > byteCount) return false; const unsigned char* bytes = reinterpret_cast(data + offset); uint64_t bits = 0; for (int index = 0; index < 8; ++index) bits = (bits << 8) | static_cast(bytes[index]); std::memcpy(&value, &bits, sizeof(value)); offset += 8; return true; } std::string OscServer::BuildJsonString(const std::string& value) { std::ostringstream stream; stream << '"'; for (char ch : value) { if (ch == '"' || ch == '\\') stream << '\\'; stream << ch; } stream << '"'; return stream.str(); }