chore(vcv): delete the dead test rig
Phase 1 group 7 (L33). vcv/test/smoke_test.cpp included a header that no longer exists (a retired nisps-core path), asserted the pre-P6 2x12 module shape, and ran in no gate; a compiled smoke_test binary was tracked alongside it. Removed the directory plus Makefile.dist, updated BUILDING.md's two references, and deleted the unreachable reply-to-sender branch in osc_server.hpp. Gates: run-all-tests.sh ALL GREEN.
This commit is contained in:
parent
232d51039d
commit
53da84c425
5 changed files with 7 additions and 315 deletions
|
|
@ -3,15 +3,13 @@
|
|||
> **Distribution & cross-platform builds:** for packaging `.vcvplugin` files,
|
||||
> the cross-platform CI matrix, and publishing to `/next/vcv`, see
|
||||
> [DISTRIBUTION.md](DISTRIBUTION.md). The official `make dist` target (from the
|
||||
> SDK's `plugin.mk`) produces `dist/<slug>-<version>-<platform>.vcvplugin`; the
|
||||
> legacy `Makefile.dist` (zip-based, hard-coded 0.1.0) is superseded by it.
|
||||
> SDK's `plugin.mk`) produces `dist/<slug>-<version>-<platform>.vcvplugin`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **VCV Rack 2 SDK** — download from https://vcvrack.com/manual/PluginDevelopmentTutorial or build from source
|
||||
- **C++20 compiler** — GCC 10+, Clang 11+, or MSVC 19.29+ (required by nisps-core for `std::span` and concepts)
|
||||
- **GNU Make**
|
||||
- **zip** (for distribution packaging only)
|
||||
|
||||
## Build Steps
|
||||
|
||||
|
|
@ -44,16 +42,13 @@ After installing, restart VCV Rack (or use the module browser refresh if availab
|
|||
## Distribution Packaging
|
||||
|
||||
```bash
|
||||
# Build and package in one step
|
||||
make -f Makefile.dist dist
|
||||
# Build and package a .vcvplugin (SDK plugin.mk target)
|
||||
make dist
|
||||
|
||||
# Or package an already-built plugin
|
||||
make -f Makefile.dist package-only
|
||||
|
||||
# Output: dist/MEMLNaut-0.1.0-Linux-x86_64.zip (platform name varies)
|
||||
# Output: dist/MEMLNaut-<version>-<platform>.vcvplugin
|
||||
```
|
||||
|
||||
The zip contains a `MEMLNaut/` directory with `plugin.so` (or `.dylib`/`.dll`), `plugin.json`, and `res/`. Users extract this into their VCV Rack plugins directory.
|
||||
See [DISTRIBUTION.md](DISTRIBUTION.md) for the full packaging, CI matrix, and publishing workflow.
|
||||
|
||||
## Cross-Compilation
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
# Distribution packaging for MEMLNaut VCV plugin
|
||||
#
|
||||
# Usage:
|
||||
# make -f Makefile.dist dist # Build plugin then package
|
||||
# make -f Makefile.dist package-only # Package without rebuilding
|
||||
# make -f Makefile.dist clean-dist # Remove dist/ directory
|
||||
|
||||
PLUGIN_NAME = MEMLNaut
|
||||
VERSION = 0.1.0
|
||||
PLATFORM = $(shell uname -s)-$(shell uname -m)
|
||||
|
||||
# Delegate build to the main Makefile (which includes the VCV SDK plugin.mk)
|
||||
all:
|
||||
$(MAKE) -f Makefile
|
||||
|
||||
# Build then package
|
||||
dist: all package-only
|
||||
|
||||
# Package the already-built plugin (no rebuild)
|
||||
package-only:
|
||||
@echo "Packaging $(PLUGIN_NAME) v$(VERSION) for $(PLATFORM)..."
|
||||
mkdir -p dist/$(PLUGIN_NAME)
|
||||
cp plugin.so dist/$(PLUGIN_NAME)/ 2>/dev/null || true
|
||||
cp plugin.dylib dist/$(PLUGIN_NAME)/ 2>/dev/null || true
|
||||
cp plugin.dll dist/$(PLUGIN_NAME)/ 2>/dev/null || true
|
||||
cp plugin.json dist/$(PLUGIN_NAME)/
|
||||
cp -r res dist/$(PLUGIN_NAME)/
|
||||
cd dist && zip -r $(PLUGIN_NAME)-$(VERSION)-$(PLATFORM).zip $(PLUGIN_NAME)
|
||||
rm -rf dist/$(PLUGIN_NAME)
|
||||
@echo "Created dist/$(PLUGIN_NAME)-$(VERSION)-$(PLATFORM).zip"
|
||||
|
||||
clean-dist:
|
||||
rm -rf dist
|
||||
|
||||
.PHONY: all dist package-only clean-dist
|
||||
|
|
@ -269,19 +269,10 @@ private:
|
|||
void recvLoop() {
|
||||
uint8_t buf[65536];
|
||||
while (!shouldStop_.load()) {
|
||||
sockaddr_in from{};
|
||||
socklen_t fromLen = sizeof(from);
|
||||
ssize_t n = recvfrom(recvSock_, reinterpret_cast<char*>(buf), sizeof(buf), 0,
|
||||
reinterpret_cast<sockaddr*>(&from), &fromLen);
|
||||
nullptr, nullptr);
|
||||
if (n <= 0) continue; // timeout or error
|
||||
|
||||
// Remember sender for replies
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sendMutex_);
|
||||
lastSender_ = from;
|
||||
hasLastSender_ = true;
|
||||
}
|
||||
|
||||
parseMessage(buf, static_cast<size_t>(n));
|
||||
}
|
||||
}
|
||||
|
|
@ -338,19 +329,14 @@ private:
|
|||
target.sin_family = AF_INET;
|
||||
|
||||
if (sendTargetDirty_ || !hasExplicitTarget_) {
|
||||
// Use explicit target if set, otherwise reply to last sender
|
||||
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 if (hasLastSender_) {
|
||||
target = lastSender_;
|
||||
target.sin_port = htons(static_cast<uint16_t>(sendPort_));
|
||||
sendAddr_ = target;
|
||||
} else {
|
||||
return; // nobody to send to
|
||||
return; // no target configured
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,8 +366,6 @@ private:
|
|||
int sendPort_ = 9001;
|
||||
bool sendTargetDirty_ = false;
|
||||
bool hasExplicitTarget_ = false;
|
||||
bool hasLastSender_ = false;
|
||||
sockaddr_in lastSender_{};
|
||||
sockaddr_in sendAddr_{};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,252 +0,0 @@
|
|||
/**
|
||||
* Standalone smoke test for MEMLNaut VCV module logic.
|
||||
* Tests the IML inference pipeline without the VCV Rack runtime.
|
||||
* Simulates what process() does: input CVs → IML → output CVs.
|
||||
*/
|
||||
#include <nisps/nisps.hpp>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
static constexpr int NUM_INPUTS = 2;
|
||||
static constexpr int NUM_OUTPUTS = 12;
|
||||
|
||||
struct SmokeTestResults {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
void check(const char* name, bool ok) {
|
||||
if (ok) { passed++; std::cout << " PASS: " << name << "\n"; }
|
||||
else { failed++; std::cerr << " FAIL: " << name << "\n"; }
|
||||
}
|
||||
};
|
||||
|
||||
// Test 1: Outputs are valid after inference
|
||||
bool test_basic_inference(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Basic inference pipeline ---\n";
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
|
||||
// Simulate input: X=0.5, Y=0.5 (center position)
|
||||
iml.set_input(0, 0.5f);
|
||||
iml.set_input(1, 0.5f);
|
||||
iml.process();
|
||||
|
||||
const float* outs = iml.get_outputs();
|
||||
bool all_valid = true;
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
if (std::isnan(outs[i]) || std::isinf(outs[i]) || outs[i] < 0.f || outs[i] > 1.f) {
|
||||
std::cerr << " Output " << i << " = " << outs[i] << " (invalid!)\n";
|
||||
all_valid = false;
|
||||
}
|
||||
}
|
||||
r.check("All 12 outputs in [0,1]", all_valid);
|
||||
return all_valid;
|
||||
}
|
||||
|
||||
// Test 2: Outputs change when inputs change
|
||||
bool test_outputs_respond_to_inputs(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Outputs respond to input changes ---\n";
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
|
||||
// Get outputs at (0.1, 0.1)
|
||||
iml.set_input(0, 0.1f);
|
||||
iml.set_input(1, 0.1f);
|
||||
iml.process();
|
||||
const float* outs1 = iml.get_outputs();
|
||||
std::vector<float> snapshot1(outs1, outs1 + NUM_OUTPUTS);
|
||||
|
||||
// Get outputs at (0.9, 0.9)
|
||||
iml.set_input(0, 0.9f);
|
||||
iml.set_input(1, 0.9f);
|
||||
iml.process();
|
||||
const float* outs2 = iml.get_outputs();
|
||||
|
||||
// At least some outputs should differ
|
||||
int changed = 0;
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
if (std::abs(outs2[i] - snapshot1[i]) > 0.001f) changed++;
|
||||
}
|
||||
std::cout << " " << changed << "/" << NUM_OUTPUTS << " outputs changed\n";
|
||||
r.check("At least 6 outputs changed between (0.1,0.1) and (0.9,0.9)", changed >= 6);
|
||||
return changed >= 6;
|
||||
}
|
||||
|
||||
// Test 3: Randomize produces different mappings
|
||||
bool test_randomize_changes_outputs(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Randomize produces different mappings ---\n";
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
|
||||
iml.set_input(0, 0.5f);
|
||||
iml.set_input(1, 0.5f);
|
||||
iml.process();
|
||||
const float* outs1 = iml.get_outputs();
|
||||
std::vector<float> before(outs1, outs1 + NUM_OUTPUTS);
|
||||
|
||||
// Randomize with spread=0.6 — this internally re-runs inference
|
||||
iml.set_mode(nisps::IML<float>::Mode::Training);
|
||||
iml.randomise_weights(0.6f);
|
||||
iml.set_mode(nisps::IML<float>::Mode::Inference);
|
||||
|
||||
// get_outputs() now reflects the new weights (randomise_weights runs inference internally)
|
||||
const float* outs2 = iml.get_outputs();
|
||||
|
||||
int changed = 0;
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
if (std::abs(outs2[i] - before[i]) > 0.001f) changed++;
|
||||
}
|
||||
std::cout << " " << changed << "/" << NUM_OUTPUTS << " outputs changed after randomize\n";
|
||||
r.check("Randomize changes most outputs", changed >= 8);
|
||||
return changed >= 8;
|
||||
}
|
||||
|
||||
// Test 4: Spread=0 vs spread=1 produce different weight distributions
|
||||
bool test_spread_affects_distribution(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Spread parameter affects output distribution ---\n";
|
||||
|
||||
auto get_output_stats = [](float spread, int trials) {
|
||||
float total_extreme = 0;
|
||||
int total_samples = 0;
|
||||
for (int t = 0; t < trials; t++) {
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
iml.set_mode(nisps::IML<float>::Mode::Training);
|
||||
iml.randomise_weights(spread);
|
||||
iml.set_mode(nisps::IML<float>::Mode::Inference);
|
||||
|
||||
for (float x = 0.1f; x <= 0.9f; x += 0.2f) {
|
||||
for (float y = 0.1f; y <= 0.9f; y += 0.2f) {
|
||||
iml.set_input(0, x);
|
||||
iml.set_input(1, y);
|
||||
iml.process();
|
||||
const float* outs = iml.get_outputs();
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
if (outs[i] < 0.1f || outs[i] > 0.9f) total_extreme++;
|
||||
total_samples++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return total_extreme / total_samples;
|
||||
};
|
||||
|
||||
float extreme_ratio_spread0 = get_output_stats(0.0f, 10);
|
||||
float extreme_ratio_spread1 = get_output_stats(1.0f, 10);
|
||||
|
||||
std::cout << " Spread=0 extreme ratio: " << extreme_ratio_spread0 << "\n";
|
||||
std::cout << " Spread=1 extreme ratio: " << extreme_ratio_spread1 << "\n";
|
||||
|
||||
// Spread parameter should produce measurably different distributions.
|
||||
// Note: with small networks [16,24,16], spread=0 doesn't necessarily produce
|
||||
// MORE extreme outputs (fan-in too small to saturate sigmoid). The saturation
|
||||
// effect is architecture-dependent. Just verify the distributions differ.
|
||||
float diff = std::abs(extreme_ratio_spread0 - extreme_ratio_spread1);
|
||||
std::cout << " Distribution difference: " << diff << "\n";
|
||||
r.check("Spread=0 and spread=1 produce different distributions (diff > 0.01)", diff > 0.01f);
|
||||
return diff > 0.01f;
|
||||
}
|
||||
|
||||
// Test 5: Network expressiveness — different input regions produce different output patterns
|
||||
bool test_network_expressiveness(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Network [16,24,16] is expressive for 12 outputs ---\n";
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
iml.set_mode(nisps::IML<float>::Mode::Training);
|
||||
iml.randomise_weights(0.6f);
|
||||
iml.set_mode(nisps::IML<float>::Mode::Inference);
|
||||
|
||||
// Sample 4 corners of input space
|
||||
float corners[4][2] = {{0.1f, 0.1f}, {0.9f, 0.1f}, {0.1f, 0.9f}, {0.9f, 0.9f}};
|
||||
std::vector<std::vector<float>> corner_outputs(4);
|
||||
|
||||
for (int c = 0; c < 4; c++) {
|
||||
iml.set_input(0, corners[c][0]);
|
||||
iml.set_input(1, corners[c][1]);
|
||||
iml.process();
|
||||
const float* outs = iml.get_outputs();
|
||||
corner_outputs[c].assign(outs, outs + NUM_OUTPUTS);
|
||||
}
|
||||
|
||||
// Check pairwise distances — all corner pairs should produce distinct output vectors
|
||||
int distinct_pairs = 0;
|
||||
for (int a = 0; a < 4; a++) {
|
||||
for (int b = a + 1; b < 4; b++) {
|
||||
float dist = 0;
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
float d = corner_outputs[a][i] - corner_outputs[b][i];
|
||||
dist += d * d;
|
||||
}
|
||||
dist = std::sqrt(dist);
|
||||
if (dist > 0.1f) distinct_pairs++;
|
||||
}
|
||||
}
|
||||
std::cout << " " << distinct_pairs << "/6 corner pairs are distinct (L2 > 0.1)\n";
|
||||
r.check("At least 4/6 corner pairs produce distinct outputs", distinct_pairs >= 4);
|
||||
|
||||
// Check output range utilization — how much of [0,1] do the outputs cover?
|
||||
float min_out = 1.f, max_out = 0.f;
|
||||
for (auto& co : corner_outputs) {
|
||||
for (float v : co) {
|
||||
min_out = std::min(min_out, v);
|
||||
max_out = std::max(max_out, v);
|
||||
}
|
||||
}
|
||||
float range = max_out - min_out;
|
||||
std::cout << " Output range utilization: " << range << " (min=" << min_out << " max=" << max_out << ")\n";
|
||||
r.check("Output range > 0.3 (sufficient variety)", range > 0.3f);
|
||||
|
||||
return distinct_pairs >= 4 && range > 0.3f;
|
||||
}
|
||||
|
||||
// Test 6: Continuous sweep — outputs vary smoothly, not just binary
|
||||
bool test_smooth_variation(SmokeTestResults& r) {
|
||||
std::cout << "\n--- Test: Outputs vary smoothly across input sweep ---\n";
|
||||
nisps::IML<float> iml(NUM_INPUTS, NUM_OUTPUTS, {16, 24, 16});
|
||||
iml.set_mode(nisps::IML<float>::Mode::Training);
|
||||
iml.randomise_weights(0.6f);
|
||||
iml.set_mode(nisps::IML<float>::Mode::Inference);
|
||||
|
||||
// Sweep X from 0 to 1, fixed Y=0.5
|
||||
std::vector<float> prev_outs(NUM_OUTPUTS, 0.f);
|
||||
int smooth_steps = 0;
|
||||
int total_steps = 0;
|
||||
|
||||
for (float x = 0.f; x <= 1.f; x += 0.05f) {
|
||||
iml.set_input(0, x);
|
||||
iml.set_input(1, 0.5f);
|
||||
iml.process();
|
||||
const float* outs = iml.get_outputs();
|
||||
|
||||
if (x > 0.f) {
|
||||
float max_jump = 0;
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
max_jump = std::max(max_jump, std::abs(outs[i] - prev_outs[i]));
|
||||
}
|
||||
// For a 0.05 input step, output jumps should be < 0.5 (smooth, not binary)
|
||||
if (max_jump < 0.5f) smooth_steps++;
|
||||
total_steps++;
|
||||
}
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) prev_outs[i] = outs[i];
|
||||
}
|
||||
|
||||
float smooth_ratio = (float)smooth_steps / total_steps;
|
||||
std::cout << " " << smooth_steps << "/" << total_steps << " steps were smooth (max jump < 0.5)\n";
|
||||
r.check("At least 80% of steps are smooth", smooth_ratio >= 0.8f);
|
||||
return smooth_ratio >= 0.8f;
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "=== MEMLNaut VCV Module Smoke Test ===\n";
|
||||
std::cout << "Network: [" << NUM_INPUTS << "+bias, 16, 24, 16, " << NUM_OUTPUTS << "]\n";
|
||||
|
||||
SmokeTestResults r;
|
||||
|
||||
test_basic_inference(r);
|
||||
test_outputs_respond_to_inputs(r);
|
||||
test_randomize_changes_outputs(r);
|
||||
test_spread_affects_distribution(r);
|
||||
test_network_expressiveness(r);
|
||||
test_smooth_variation(r);
|
||||
|
||||
std::cout << "\n=== Results: " << r.passed << " passed, " << r.failed << " failed ===\n";
|
||||
return r.failed > 0 ? 1 : 0;
|
||||
}
|
||||
Loading…
Reference in a new issue