Files
sub 4d0ad127b5 Add shared memory daemon architecture and systemd service
Move ES-5 encoding from the CLAP plugin to a standalone PipeWire
daemon (es5d) communicating via POSIX shared memory. The plugin now
acts as a parameter/state frontend while es5d outputs directly to
the ES-9. Includes systemd user service for autostart after reboot
and an install script for local deployment.
2026-03-27 22:01:04 +01:00

152 lines
4.8 KiB
C++

// Direct ES-5 test — outputs encoded gate pattern via ALSA, bypassing Bitwig
// Tests header 1 (GT, all gates toggling) + header 6 (CV encoder running)
// Headers 2-5 should remain silent — any activity = encoding leak
// Usage: es5_test [alsa_device] [channel_offset]
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <cmath>
#include <csignal>
#include <algorithm>
#include <alsa/asoundlib.h>
static volatile bool running = true;
static void sighandler(int) { running = false; }
// --- ESX-8CV encoder (from Expert Sleepers MIT-licensed source) ---
struct ESX8CVEncoder {
int phase = 0;
uint32_t value = 0;
uint8_t encode(const double values[8]) {
int p = phase & ~1;
int state = (p >> 1) & 3;
int dac = (p >> 3) & 7;
if (state == 0) {
double s = values[dac];
value = 2048 + static_cast<int32_t>(
std::max(-2048.0, std::min(2047.0, s)));
}
phase += 2;
if ((phase & 7) == 6)
phase += 2;
phase &= 63;
uint32_t out;
if (state == 0)
out = 0x80 | (value & 0x1F);
else if (state == 1)
out = (value >> 5) & 0x1F;
else
out = ((dac > 3) ? 0x40 : 0x20) | (value >> 10) | ((dac & 3) << 2);
return static_cast<uint8_t>(out);
}
};
// Pack 24-bit integer into 3 bytes (little-endian)
static void pack_s24_3le(uint8_t *dst, int32_t val) {
uint32_t u = static_cast<uint32_t>(val) & 0xFFFFFF;
dst[0] = u & 0xFF;
dst[1] = (u >> 8) & 0xFF;
dst[2] = (u >> 16) & 0xFF;
}
int main(int argc, char *argv[]) {
setbuf(stdout, nullptr);
const char *device = argc > 1 ? argv[1] : "hw:7";
int ch_offset = argc > 2 ? atoi(argv[2]) : 12;
const unsigned rate = 48000;
const unsigned channels = 16;
signal(SIGINT, sighandler);
snd_pcm_t *pcm = nullptr;
int err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
fprintf(stderr, "Cannot open %s: %s\n", device, snd_strerror(err));
return 1;
}
snd_pcm_hw_params_t *hw;
snd_pcm_hw_params_alloca(&hw);
snd_pcm_hw_params_any(pcm, hw);
snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S24_3LE);
snd_pcm_hw_params_set_channels(pcm, hw, channels);
unsigned actual_rate = rate;
snd_pcm_hw_params_set_rate_near(pcm, hw, &actual_rate, 0);
snd_pcm_uframes_t period = 1024;
snd_pcm_hw_params_set_period_size_near(pcm, hw, &period, 0);
if ((err = snd_pcm_hw_params(pcm, hw)) < 0) {
fprintf(stderr, "Cannot set hw params: %s\n", snd_strerror(err));
return 1;
}
printf("ES-5 test: device=%s rate=%u channels=%u ES-5_L/R=ch%d/%d\n",
device, actual_rate, channels, ch_offset, ch_offset + 1);
printf("Header 1: GT (toggling all gates every second)\n");
printf("Header 6: CV (sweeping all 8 CVs)\n");
printf("Headers 2-5: should be OFF (any activity = leak!)\n");
printf("Ctrl+C to stop.\n\n");
int buf_frame_bytes = channels * 3;
uint8_t *buf = new uint8_t[period * buf_frame_bytes]();
uint32_t sample_count = 0;
uint32_t total_samples = 0;
bool gates_on = false;
ESX8CVEncoder cv_encoder;
while (running) {
for (unsigned f = 0; f < period; ++f) {
if (++sample_count >= rate) {
sample_count = 0;
gates_on = !gates_on;
printf(" Header 1 gates: %s | CV sweep: %.0f%%\n",
gates_on ? "ALL ON " : "ALL OFF",
fmod(total_samples / (double)rate, 8.0) / 8.0 * 100.0);
}
total_samples++;
// Header 1: GT
uint8_t h1 = gates_on ? 0xFF : 0x00;
// Header 6: CV — sweep each channel slowly
double cvs[8];
for (int c = 0; c < 8; ++c) {
double t = (total_samples + c * rate) / (double)(rate * 8);
cvs[c] = sin(t * 3.14159265) * 2047.0; // ±2047 sweep
}
uint8_t h6 = cv_encoder.encode(cvs);
// Headers 2-5: OFF (must stay 0x00)
uint8_t h2 = 0, h3 = 0, h4 = 0, h5 = 0;
int32_t bitsL = (h1 << 16) | (h2 << 8) | h3;
int32_t bitsR = (h4 << 16) | (h5 << 8) | h6;
uint8_t *frame = &buf[f * buf_frame_bytes];
memset(frame, 0, buf_frame_bytes);
pack_s24_3le(&frame[ch_offset * 3], bitsL);
pack_s24_3le(&frame[(ch_offset + 1) * 3], bitsR);
}
snd_pcm_sframes_t written = snd_pcm_writei(pcm, buf, period);
if (written < 0) {
fprintf(stderr, " Write error: %s, recovering...\n", snd_strerror(written));
snd_pcm_recover(pcm, written, 0);
}
}
delete[] buf;
snd_pcm_close(pcm);
printf("\nDone.\n");
return 0;
}