// 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 #include #include #include #include #include #include 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( 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(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(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; }