Initial CLAP plugin scaffold for ES-5 encoder

Implements the full encoding pipeline for Expert Sleepers ES-5 with
ESX-8GT (gate) and ESX-8CV (CV) expanders as a native CLAP plugin.

Encoder logic ported from Expert Sleepers' MIT-licensed Pure Data
externals (https://github.com/expertsleepersltd/externals).

- ES-5 encoder: packs 6 × 8-bit header values into stereo 24-bit audio
- ESX-8GT encoder: 8 boolean gates → 1 byte
- ESX-8CV encoder: 8 × 12-bit CV values via 64-phase state machine
- 54 automatable parameters (6 header types + 48 gate/CV values)
- State save/load for preset support
- Builds on Linux (tested), macOS, and Windows
This commit is contained in:
sub
2026-03-21 01:43:32 +01:00
parent 977480ca3e
commit 6a117a9ba9
8 changed files with 636 additions and 1 deletions
+76
View File
@@ -0,0 +1,76 @@
// ES-5 CLAP Plugin — Bitwig-native encoder for Expert Sleepers ES-5 + ESX expanders
// Copyright (c) 2026 Sub-Net e.U. — MIT License
#pragma once
#include <clap/helpers/plugin.hh>
#include <clap/helpers/plugin.hxx>
#include <clap/helpers/host-proxy.hh>
#include <clap/helpers/host-proxy.hxx>
#include <array>
#include "encoders.h"
// Header type enum (matches parameter stepped values)
enum class HeaderType : int { Off = 0, GT = 1, CV = 2 };
// Parameter ID scheme:
// 16: Header type (Off/GT/CV)
// 101108: Header 1 values
// 201208: Header 2 values
// ...
// 601608: Header 6 values
constexpr uint32_t NUM_HEADERS = 6;
constexpr uint32_t CHANNELS_PER_HEADER = 8;
constexpr uint32_t TOTAL_PARAMS = NUM_HEADERS + NUM_HEADERS * CHANNELS_PER_HEADER; // 54
constexpr clap_id headerTypeParamId(uint32_t header) { return header + 1; }
constexpr clap_id headerValueParamId(uint32_t header, uint32_t ch) {
return (header + 1) * 100 + ch + 1;
}
class ES5Plugin : public clap::helpers::Plugin<
clap::helpers::MisbehaviourHandler::Terminate,
clap::helpers::CheckingLevel::Maximal>
{
public:
ES5Plugin(const clap_plugin_descriptor *desc, const clap_host *host);
// --- Audio Ports ---
bool implementsAudioPorts() const noexcept override { return true; }
uint32_t audioPortsCount(bool isInput) const noexcept override;
bool audioPortsInfo(uint32_t index, bool isInput,
clap_audio_port_info *info) const noexcept override;
// --- Parameters ---
bool implementsParams() const noexcept override { return true; }
uint32_t paramsCount() const noexcept override;
bool paramsInfo(uint32_t paramIndex, clap_param_info *info) const noexcept override;
bool paramsValue(clap_id paramId, double *value) noexcept override;
bool paramsValueToText(clap_id paramId, double value,
char *display, uint32_t size) noexcept override;
bool paramsTextToValue(clap_id paramId, const char *display,
double *value) noexcept override;
void paramsFlush(const clap_input_events *in,
const clap_output_events *out) noexcept override;
// --- State ---
bool implementsState() const noexcept override { return true; }
bool stateSave(const clap_ostream *stream) noexcept override;
bool stateLoad(const clap_istream *stream) noexcept override;
// --- Process ---
clap_process_status process(const clap_process *process) noexcept override;
private:
void handleEvent(const clap_event_header *hdr);
// Parameter storage
std::array<double, NUM_HEADERS> headerTypes_{}; // 0=off, 1=gt, 2=cv
std::array<std::array<double, CHANNELS_PER_HEADER>, NUM_HEADERS> values_{}; // 0.01.0
// Encoder instances
es5::ES5Encoder es5Encoder_;
std::array<es5::ESX8GTEncoder, NUM_HEADERS> gtEncoders_;
std::array<es5::ESX8CVEncoder, NUM_HEADERS> cvEncoders_;
};