feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
// osc_server.hpp — Minimal OSC server for MEMLNaut VCV module
|
|
|
|
|
// Lightweight header-only implementation using raw UDP sockets.
|
|
|
|
|
// Supports a tiny subset of OSC: float and string arguments only.
|
|
|
|
|
// No bundles, no timetags, no pattern matching.
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <atomic>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#endif
|
|
|
|
|
#include <winsock2.h>
|
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
|
#pragma comment(lib, "ws2_32.lib")
|
|
|
|
|
using socket_t = SOCKET;
|
|
|
|
|
static constexpr socket_t INVALID_SOCK = INVALID_SOCKET;
|
|
|
|
|
#define CLOSE_SOCKET(s) closesocket(s)
|
|
|
|
|
#else
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
using socket_t = int;
|
|
|
|
|
static constexpr socket_t INVALID_SOCK = -1;
|
|
|
|
|
#define CLOSE_SOCKET(s) ::close(s)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace memlnaut {
|
|
|
|
|
|
|
|
|
|
// ── OSC encoding/decoding helpers ────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
namespace osc {
|
|
|
|
|
|
|
|
|
|
// Pad length to next multiple of 4
|
|
|
|
|
inline size_t padded(size_t len) {
|
|
|
|
|
return (len + 3) & ~size_t(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read a null-terminated, 4-byte-padded OSC string from buf at offset.
|
|
|
|
|
// Returns the string and advances offset past the padded data.
|
|
|
|
|
inline std::string readString(const uint8_t* buf, size_t bufLen, size_t& offset) {
|
|
|
|
|
if (offset >= bufLen) return "";
|
|
|
|
|
const char* start = reinterpret_cast<const char*>(buf + offset);
|
|
|
|
|
size_t maxLen = bufLen - offset;
|
|
|
|
|
size_t slen = strnlen(start, maxLen);
|
|
|
|
|
std::string s(start, slen);
|
|
|
|
|
offset += padded(slen + 1); // +1 for null terminator
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read a big-endian float32 from buf at offset.
|
|
|
|
|
inline float readFloat(const uint8_t* buf, size_t bufLen, size_t& offset) {
|
|
|
|
|
if (offset + 4 > bufLen) return 0.f;
|
|
|
|
|
uint32_t raw = (uint32_t(buf[offset]) << 24)
|
|
|
|
|
| (uint32_t(buf[offset + 1]) << 16)
|
|
|
|
|
| (uint32_t(buf[offset + 2]) << 8)
|
|
|
|
|
| uint32_t(buf[offset + 3]);
|
|
|
|
|
offset += 4;
|
|
|
|
|
float f;
|
|
|
|
|
std::memcpy(&f, &raw, 4);
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read a big-endian int32 from buf at offset.
|
|
|
|
|
inline int32_t readInt32(const uint8_t* buf, size_t bufLen, size_t& offset) {
|
|
|
|
|
if (offset + 4 > bufLen) return 0;
|
|
|
|
|
int32_t val = (int32_t(buf[offset]) << 24)
|
|
|
|
|
| (int32_t(buf[offset + 1]) << 16)
|
|
|
|
|
| (int32_t(buf[offset + 2]) << 8)
|
|
|
|
|
| int32_t(buf[offset + 3]);
|
|
|
|
|
offset += 4;
|
|
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a null-terminated, 4-byte-padded string into out.
|
|
|
|
|
inline void writeString(std::vector<uint8_t>& out, const std::string& s) {
|
|
|
|
|
size_t start = out.size();
|
|
|
|
|
size_t total = padded(s.size() + 1);
|
|
|
|
|
out.resize(start + total, 0);
|
|
|
|
|
std::memcpy(out.data() + start, s.c_str(), s.size());
|
|
|
|
|
// Remaining bytes are already zero (null terminator + padding)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a big-endian float32 into out.
|
|
|
|
|
inline void writeFloat(std::vector<uint8_t>& out, float f) {
|
|
|
|
|
uint32_t raw;
|
|
|
|
|
std::memcpy(&raw, &f, 4);
|
|
|
|
|
out.push_back(uint8_t(raw >> 24));
|
|
|
|
|
out.push_back(uint8_t(raw >> 16));
|
|
|
|
|
out.push_back(uint8_t(raw >> 8));
|
|
|
|
|
out.push_back(uint8_t(raw));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build an OSC message with a float array payload.
|
|
|
|
|
// Address: e.g. "/nisps/output"
|
|
|
|
|
// Type tag string: ",fff..." (one 'f' per float)
|
|
|
|
|
inline std::vector<uint8_t> messageFloats(const std::string& address,
|
|
|
|
|
const float* values, size_t count) {
|
|
|
|
|
std::vector<uint8_t> msg;
|
|
|
|
|
writeString(msg, address);
|
|
|
|
|
// Type tag string: "," + count 'f' chars
|
|
|
|
|
std::string tags = ",";
|
|
|
|
|
for (size_t i = 0; i < count; i++) tags += 'f';
|
|
|
|
|
writeString(msg, tags);
|
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
writeFloat(msg, values[i]);
|
|
|
|
|
}
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build an OSC message with a single string payload.
|
|
|
|
|
inline std::vector<uint8_t> messageString(const std::string& address,
|
|
|
|
|
const std::string& value) {
|
|
|
|
|
std::vector<uint8_t> msg;
|
|
|
|
|
writeString(msg, address);
|
|
|
|
|
writeString(msg, ",s");
|
|
|
|
|
writeString(msg, value);
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace osc
|
|
|
|
|
|
|
|
|
|
// ── OscServer ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class OscServer {
|
|
|
|
|
public:
|
|
|
|
|
using StringCallback = std::function<void(const std::string&)>;
|
2026-06-28 04:14:30 +02:00
|
|
|
using FloatVecCallback = std::function<void(const std::vector<float>&)>;
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
|
|
|
|
|
OscServer() = default;
|
|
|
|
|
~OscServer() { stop(); }
|
|
|
|
|
|
|
|
|
|
// Non-copyable
|
|
|
|
|
OscServer(const OscServer&) = delete;
|
|
|
|
|
OscServer& operator=(const OscServer&) = delete;
|
|
|
|
|
|
2026-06-28 04:14:30 +02:00
|
|
|
// Register handlers before starting.
|
|
|
|
|
// onState — full JSON state snapshot (/nisps/state <s>)
|
|
|
|
|
// onWeights — weights-only JSON (/nisps/weights <s>)
|
|
|
|
|
// onInput — live input vector (browser drives the model) (/nisps/input <f…f>)
|
|
|
|
|
// onFeedback— verdict op JSON (thumbs/place/rand/clear) (/nisps/feedback <s>)
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
void onState(StringCallback cb) { stateCallback_ = std::move(cb); }
|
|
|
|
|
void onWeights(StringCallback cb) { weightsCallback_ = std::move(cb); }
|
2026-06-28 04:14:30 +02:00
|
|
|
void onInput(FloatVecCallback cb) { inputCallback_ = std::move(cb); }
|
|
|
|
|
void onFeedback(StringCallback cb) { feedbackCallback_ = std::move(cb); }
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
|
|
|
|
|
// Set the target address for sending (where the webapp bridge listens).
|
|
|
|
|
// Default: 127.0.0.1:9001
|
|
|
|
|
void setSendTarget(const std::string& host, int port) {
|
|
|
|
|
std::lock_guard<std::mutex> lock(sendMutex_);
|
|
|
|
|
sendHost_ = host;
|
|
|
|
|
sendPort_ = port;
|
|
|
|
|
sendTargetDirty_ = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool start(int listenPort = 9000) {
|
|
|
|
|
if (running_.load()) return true;
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
WSADATA wsaData;
|
|
|
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return false;
|
|
|
|
|
wsaInit_ = true;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
recvSock_ = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
|
if (recvSock_ == INVALID_SOCK) return false;
|
|
|
|
|
|
|
|
|
|
// Allow address reuse
|
|
|
|
|
int opt = 1;
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
setsockopt(recvSock_, SOL_SOCKET, SO_REUSEADDR,
|
|
|
|
|
reinterpret_cast<const char*>(&opt), sizeof(opt));
|
|
|
|
|
#else
|
|
|
|
|
setsockopt(recvSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
sockaddr_in addr{};
|
|
|
|
|
addr.sin_family = AF_INET;
|
|
|
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
|
addr.sin_port = htons(static_cast<uint16_t>(listenPort));
|
|
|
|
|
|
|
|
|
|
if (bind(recvSock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) {
|
|
|
|
|
CLOSE_SOCKET(recvSock_);
|
|
|
|
|
recvSock_ = INVALID_SOCK;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set receive timeout so the thread can check shouldStop
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
DWORD timeout = 200; // ms
|
|
|
|
|
setsockopt(recvSock_, SOL_SOCKET, SO_RCVTIMEO,
|
|
|
|
|
reinterpret_cast<const char*>(&timeout), sizeof(timeout));
|
|
|
|
|
#else
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
tv.tv_sec = 0;
|
|
|
|
|
tv.tv_usec = 200000; // 200ms
|
|
|
|
|
setsockopt(recvSock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Create send socket
|
|
|
|
|
sendSock_ = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
|
if (sendSock_ == INVALID_SOCK) {
|
|
|
|
|
CLOSE_SOCKET(recvSock_);
|
|
|
|
|
recvSock_ = INVALID_SOCK;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
listenPort_ = listenPort;
|
|
|
|
|
shouldStop_.store(false);
|
|
|
|
|
running_.store(true);
|
|
|
|
|
recvThread_ = std::thread(&OscServer::recvLoop, this);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void stop() {
|
|
|
|
|
if (!running_.load()) return;
|
|
|
|
|
shouldStop_.store(true);
|
|
|
|
|
if (recvThread_.joinable()) recvThread_.join();
|
|
|
|
|
if (recvSock_ != INVALID_SOCK) { CLOSE_SOCKET(recvSock_); recvSock_ = INVALID_SOCK; }
|
|
|
|
|
if (sendSock_ != INVALID_SOCK) { CLOSE_SOCKET(sendSock_); sendSock_ = INVALID_SOCK; }
|
|
|
|
|
running_.store(false);
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
if (wsaInit_) { WSACleanup(); wsaInit_ = false; }
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isRunning() const { return running_.load(); }
|
|
|
|
|
int getPort() const { return listenPort_; }
|
|
|
|
|
|
|
|
|
|
// ── Send methods ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
// Send current output values (12 floats) to the webapp bridge
|
|
|
|
|
void sendOutputs(const float* values, size_t count) {
|
|
|
|
|
auto msg = osc::messageFloats("/nisps/output", values, count);
|
|
|
|
|
sendPacket(msg);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 04:14:30 +02:00
|
|
|
// Send current input values (N floats)
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
void sendInputs(const float* values, size_t count) {
|
|
|
|
|
auto msg = osc::messageFloats("/nisps/input", values, count);
|
|
|
|
|
sendPacket(msg);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 04:14:30 +02:00
|
|
|
// Send a full JSON state snapshot (module → browser).
|
|
|
|
|
void sendState(const std::string& json) {
|
|
|
|
|
auto msg = osc::messageString("/nisps/state", json);
|
|
|
|
|
sendPacket(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send weights-only JSON (module → browser).
|
|
|
|
|
void sendWeights(const std::string& json) {
|
|
|
|
|
auto msg = osc::messageString("/nisps/weights", json);
|
|
|
|
|
sendPacket(msg);
|
|
|
|
|
}
|
|
|
|
|
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
private:
|
|
|
|
|
void recvLoop() {
|
|
|
|
|
uint8_t buf[65536];
|
|
|
|
|
while (!shouldStop_.load()) {
|
|
|
|
|
ssize_t n = recvfrom(recvSock_, reinterpret_cast<char*>(buf), sizeof(buf), 0,
|
2026-07-21 12:49:25 +02:00
|
|
|
nullptr, nullptr);
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
if (n <= 0) continue; // timeout or error
|
|
|
|
|
|
|
|
|
|
parseMessage(buf, static_cast<size_t>(n));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void parseMessage(const uint8_t* buf, size_t len) {
|
|
|
|
|
size_t offset = 0;
|
|
|
|
|
|
|
|
|
|
// Read address
|
|
|
|
|
std::string address = osc::readString(buf, len, offset);
|
|
|
|
|
if (address.empty() || address[0] != '/') return;
|
|
|
|
|
|
|
|
|
|
// Read type tag string
|
|
|
|
|
std::string tags = osc::readString(buf, len, offset);
|
|
|
|
|
if (tags.empty() || tags[0] != ',') return;
|
|
|
|
|
|
|
|
|
|
// Dispatch based on address
|
|
|
|
|
if (address == "/nisps/state") {
|
|
|
|
|
// Expect a single string argument
|
|
|
|
|
if (tags.size() >= 2 && tags[1] == 's') {
|
|
|
|
|
std::string payload = osc::readString(buf, len, offset);
|
|
|
|
|
if (stateCallback_) stateCallback_(payload);
|
|
|
|
|
}
|
|
|
|
|
} else if (address == "/nisps/weights") {
|
|
|
|
|
// Expect a single string argument (JSON)
|
|
|
|
|
if (tags.size() >= 2 && tags[1] == 's') {
|
|
|
|
|
std::string payload = osc::readString(buf, len, offset);
|
|
|
|
|
if (weightsCallback_) weightsCallback_(payload);
|
|
|
|
|
}
|
2026-06-28 04:14:30 +02:00
|
|
|
} else if (address == "/nisps/feedback") {
|
|
|
|
|
// Verdict op as a JSON string:
|
|
|
|
|
// {"op":"up|down|rand|clear","spread":f,"input":[…],"output":[…]}
|
|
|
|
|
if (tags.size() >= 2 && tags[1] == 's') {
|
|
|
|
|
std::string payload = osc::readString(buf, len, offset);
|
|
|
|
|
if (feedbackCallback_) feedbackCallback_(payload);
|
|
|
|
|
}
|
|
|
|
|
} else if (address == "/nisps/input") {
|
|
|
|
|
// Live input vector from the browser → drive the model inputs.
|
|
|
|
|
std::vector<float> values;
|
|
|
|
|
for (size_t i = 1; i < tags.size(); ++i) {
|
|
|
|
|
if (tags[i] == 'f') values.push_back(osc::readFloat(buf, len, offset));
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
if (!values.empty() && inputCallback_) inputCallback_(values);
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
}
|
|
|
|
|
// Unknown addresses are silently ignored
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendPacket(const std::vector<uint8_t>& packet) {
|
|
|
|
|
if (sendSock_ == INVALID_SOCK) return;
|
|
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(sendMutex_);
|
|
|
|
|
|
|
|
|
|
sockaddr_in target{};
|
|
|
|
|
target.sin_family = AF_INET;
|
|
|
|
|
|
|
|
|
|
if (sendTargetDirty_ || !hasExplicitTarget_) {
|
|
|
|
|
if (!sendHost_.empty()) {
|
|
|
|
|
inet_pton(AF_INET, sendHost_.c_str(), &target.sin_addr);
|
|
|
|
|
target.sin_port = htons(static_cast<uint16_t>(sendPort_));
|
|
|
|
|
hasExplicitTarget_ = true;
|
|
|
|
|
sendTargetDirty_ = false;
|
|
|
|
|
sendAddr_ = target;
|
|
|
|
|
} else {
|
2026-07-21 12:49:25 +02:00
|
|
|
return; // no target configured
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendto(sendSock_, reinterpret_cast<const char*>(packet.data()), packet.size(), 0,
|
|
|
|
|
reinterpret_cast<sockaddr*>(&sendAddr_), sizeof(sendAddr_));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sockets
|
|
|
|
|
socket_t recvSock_ = INVALID_SOCK;
|
|
|
|
|
socket_t sendSock_ = INVALID_SOCK;
|
|
|
|
|
int listenPort_ = 9000;
|
|
|
|
|
|
|
|
|
|
// Thread control
|
|
|
|
|
std::thread recvThread_;
|
|
|
|
|
std::atomic<bool> shouldStop_{false};
|
|
|
|
|
std::atomic<bool> running_{false};
|
|
|
|
|
|
|
|
|
|
// Callbacks
|
|
|
|
|
StringCallback stateCallback_;
|
|
|
|
|
StringCallback weightsCallback_;
|
2026-06-28 04:14:30 +02:00
|
|
|
StringCallback feedbackCallback_;
|
|
|
|
|
FloatVecCallback inputCallback_;
|
feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay
Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED
Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 00:48:44 +01:00
|
|
|
|
|
|
|
|
// Send target
|
|
|
|
|
std::mutex sendMutex_;
|
|
|
|
|
std::string sendHost_ = "127.0.0.1";
|
|
|
|
|
int sendPort_ = 9001;
|
|
|
|
|
bool sendTargetDirty_ = false;
|
|
|
|
|
bool hasExplicitTarget_ = false;
|
|
|
|
|
sockaddr_in sendAddr_{};
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
bool wsaInit_ = false;
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace memlnaut
|