Initial: OPLabsBandChannel.lv2 + OPLabsBandBus.lv2

This commit is contained in:
2026-06-21 00:03:37 -04:00
commit f8e697a064
68 changed files with 9591 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
# ── OPLabsBandChannel shared library ─────────────────────────────────
add_library(OPLabsBandChannel SHARED
OPLabsBandChannel.cpp
OPLabsBandChannel.hpp
)
target_include_directories(OPLabsBandChannel
PRIVATE
${LV2_INCLUDE_DIR}
)
target_compile_options(OPLabsBandChannel PRIVATE -Wall -Wextra -Wpedantic)
# Output .so directly into the LV2 bundle directory
set_target_properties(OPLabsBandChannel PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lv2/OPLabsBandChannel.lv2"
OUTPUT_NAME "OPLabsBandChannel"
PREFIX ""
)
# Version script — export only lv2_descriptor
target_link_options(OPLabsBandChannel PRIVATE
-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/OPLabsBandChannel.version
-Wl,-z,nodelete
)
# ── OPLabsBandBus shared library ────────────────────────────────────
add_library(OPLabsBandBus SHARED
OPLabsBandBus.cpp
OPLabsBandBus.hpp
)
target_include_directories(OPLabsBandBus
PRIVATE
${LV2_INCLUDE_DIR}
)
target_compile_options(OPLabsBandBus PRIVATE -Wall -Wextra -Wpedantic)
set_target_properties(OPLabsBandBus PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lv2/OPLabsBandBus.lv2"
OUTPUT_NAME "OPLabsBandBus"
PREFIX ""
)
target_link_options(OPLabsBandBus PRIVATE
-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/OPLabsBandBus.version
-Wl,-z,nodelete
)
+319
View File
@@ -0,0 +1,319 @@
#include "OPLabsBandBus.hpp"
#include <cstdio>
#include <algorithm>
// ====================================================================
// LV2 Descriptor
// ====================================================================
static LV2_Descriptor sDescriptor = {
OPLabsBandBus::URI,
OPLabsBandBus::instantiate,
OPLabsBandBus::connectPort,
OPLabsBandBus::activate,
OPLabsBandBus::run,
OPLabsBandBus::deactivate,
OPLabsBandBus::cleanup,
OPLabsBandBus::extensionData
};
LV2_SYMBOL_EXPORT
const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
return (index == 0) ? &sDescriptor : nullptr;
}
// ====================================================================
// State interface (extension data)
// ====================================================================
static const LV2_State_Interface sStateInterface = {
OPLabsBandBus::saveState,
OPLabsBandBus::restoreState
};
// ====================================================================
// Constructor
// ====================================================================
OPLabsBandBus::OPLabsBandBus(double sampleRate,
const LV2_Feature* const* features)
{
(void)sampleRate;
initUrids(features);
}
bool OPLabsBandBus::initUrids(const LV2_Feature* const* features)
{
for (int i = 0; features && features[i]; ++i) {
if (strcmp(features[i]->URI, LV2_URID__map) == 0) {
uridMap = (LV2_URID_Map*)features[i]->data;
break;
}
}
if (!uridMap) {
fprintf(stderr, "OPLabsBandBus: Host does not support urid:map\n");
return false;
}
urid__stateDummy = uridMap->map(
uridMap->handle,
"http://ourpad.casa/plugins/oplabs-band-bus#dummy"
);
return true;
}
// ====================================================================
// Static callbacks
// ====================================================================
LV2_Handle OPLabsBandBus::instantiate(const LV2_Descriptor* descriptor,
double sampleRate,
const char* bundlePath,
const LV2_Feature* const* features)
{
(void)descriptor;
(void)bundlePath;
OPLabsBandBus* self = new OPLabsBandBus(sampleRate, features);
if (!self->uridMap) {
delete self;
return nullptr;
}
return (LV2_Handle)self;
}
void OPLabsBandBus::connectPort(LV2_Handle instance, uint32_t port, void* data)
{
OPLabsBandBus* self = (OPLabsBandBus*)instance;
switch ((Ports)port) {
// Audio inputs
case kCh1L: self->pAudioIn[0][0] = (const float*)data; break;
case kCh1R: self->pAudioIn[0][1] = (const float*)data; break;
case kCh2L: self->pAudioIn[1][0] = (const float*)data; break;
case kCh2R: self->pAudioIn[1][1] = (const float*)data; break;
case kCh3L: self->pAudioIn[2][0] = (const float*)data; break;
case kCh3R: self->pAudioIn[2][1] = (const float*)data; break;
case kCh4L: self->pAudioIn[3][0] = (const float*)data; break;
case kCh4R: self->pAudioIn[3][1] = (const float*)data; break;
case kCh5L: self->pAudioIn[4][0] = (const float*)data; break;
case kCh5R: self->pAudioIn[4][1] = (const float*)data; break;
case kCh6L: self->pAudioIn[5][0] = (const float*)data; break;
case kCh6R: self->pAudioIn[5][1] = (const float*)data; break;
case kCh7L: self->pAudioIn[6][0] = (const float*)data; break;
case kCh7R: self->pAudioIn[6][1] = (const float*)data; break;
case kCh8L: self->pAudioIn[7][0] = (const float*)data; break;
case kCh8R: self->pAudioIn[7][1] = (const float*)data; break;
// Audio outputs
case kOutL: self->pAudioOut[0] = (float*)data; break;
case kOutR: self->pAudioOut[1] = (float*)data; break;
// Per-channel volume
case kCh1Vol: self->pVol[0] = (const float*)data; break;
case kCh2Vol: self->pVol[1] = (const float*)data; break;
case kCh3Vol: self->pVol[2] = (const float*)data; break;
case kCh4Vol: self->pVol[3] = (const float*)data; break;
case kCh5Vol: self->pVol[4] = (const float*)data; break;
case kCh6Vol: self->pVol[5] = (const float*)data; break;
case kCh7Vol: self->pVol[6] = (const float*)data; break;
case kCh8Vol: self->pVol[7] = (const float*)data; break;
// Per-channel pan
case kCh1Pan: self->pPan[0] = (const float*)data; break;
case kCh2Pan: self->pPan[1] = (const float*)data; break;
case kCh3Pan: self->pPan[2] = (const float*)data; break;
case kCh4Pan: self->pPan[3] = (const float*)data; break;
case kCh5Pan: self->pPan[4] = (const float*)data; break;
case kCh6Pan: self->pPan[5] = (const float*)data; break;
case kCh7Pan: self->pPan[6] = (const float*)data; break;
case kCh8Pan: self->pPan[7] = (const float*)data; break;
// Per-channel mute
case kCh1Mute: self->pMute[0] = (const float*)data; break;
case kCh2Mute: self->pMute[1] = (const float*)data; break;
case kCh3Mute: self->pMute[2] = (const float*)data; break;
case kCh4Mute: self->pMute[3] = (const float*)data; break;
case kCh5Mute: self->pMute[4] = (const float*)data; break;
case kCh6Mute: self->pMute[5] = (const float*)data; break;
case kCh7Mute: self->pMute[6] = (const float*)data; break;
case kCh8Mute: self->pMute[7] = (const float*)data; break;
// Master controls
case kMasterVol: self->pMasterVol = (const float*)data; break;
case kMasterMute: self->pMasterMute = (const float*)data; break;
// Per-channel level L outputs
case kCh1LevelL: self->pLevelL[0] = (float*)data; break;
case kCh2LevelL: self->pLevelL[1] = (float*)data; break;
case kCh3LevelL: self->pLevelL[2] = (float*)data; break;
case kCh4LevelL: self->pLevelL[3] = (float*)data; break;
case kCh5LevelL: self->pLevelL[4] = (float*)data; break;
case kCh6LevelL: self->pLevelL[5] = (float*)data; break;
case kCh7LevelL: self->pLevelL[6] = (float*)data; break;
case kCh8LevelL: self->pLevelL[7] = (float*)data; break;
// Per-channel level R outputs
case kCh1LevelR: self->pLevelR[0] = (float*)data; break;
case kCh2LevelR: self->pLevelR[1] = (float*)data; break;
case kCh3LevelR: self->pLevelR[2] = (float*)data; break;
case kCh4LevelR: self->pLevelR[3] = (float*)data; break;
case kCh5LevelR: self->pLevelR[4] = (float*)data; break;
case kCh6LevelR: self->pLevelR[5] = (float*)data; break;
case kCh7LevelR: self->pLevelR[6] = (float*)data; break;
case kCh8LevelR: self->pLevelR[7] = (float*)data; break;
// Master level outputs
case kMasterLevelL: self->pMasterLevelL = (float*)data; break;
case kMasterLevelR: self->pMasterLevelR = (float*)data; break;
default: break;
}
}
void OPLabsBandBus::activate(LV2_Handle instance)
{
(void)instance;
}
void OPLabsBandBus::run(LV2_Handle instance, uint32_t nSamples)
{
OPLabsBandBus* self = (OPLabsBandBus*)instance;
// Read master controls
if (self->pMasterVol) self->cachedMasterVol = *self->pMasterVol;
if (self->pMasterMute) self->cachedMasterMute = (*self->pMasterMute > 0.5f);
// Read per-channel controls
for (uint32_t ch = 0; ch < kNumChannels; ++ch) {
if (self->pVol[ch]) self->cachedVol[ch] = *self->pVol[ch];
if (self->pPan[ch]) self->cachedPan[ch] = *self->pPan[ch];
if (self->pMute[ch]) self->cachedMute[ch] = (*self->pMute[ch] > 0.5f);
}
// Prepare audio output buffers (zero for accumulation)
float* outL = self->pAudioOut[0];
float* outR = self->pAudioOut[1];
if (outL) std::memset(outL, 0, nSamples * sizeof(float));
if (outR) std::memset(outR, 0, nSamples * sizeof(float));
// Master gain (linear)
float masterGain = self->cachedMasterMute ? 0.0f
: dBToGain(self->cachedMasterVol);
// Accumulate each channel into the bus
float newMasterPeakL = -70.0f;
float newMasterPeakR = -70.0f;
for (uint32_t ch = 0; ch < kNumChannels; ++ch) {
const float* inL = self->pAudioIn[ch][0];
const float* inR = self->pAudioIn[ch][1];
bool muted = self->cachedMute[ch];
float chGain = muted ? 0.0f : dBToGain(self->cachedVol[ch]);
float pan = self->cachedPan[ch];
// Equal-power pan coefficients
float panL = 1.0f;
float panR = 1.0f;
if (pan <= 0.0f) {
panR = 1.0f + pan;
} else {
panL = 1.0f - pan;
}
// Per-channel peak tracking
float chPeakL = -70.0f;
float chPeakR = -70.0f;
for (uint32_t s = 0; s < nSamples; ++s) {
float sampleL = inL ? inL[s] : 0.0f;
float sampleR = inR ? inR[s] : 0.0f;
// Apply channel gain
sampleL *= chGain;
sampleR *= chGain;
// Apply pan
float pannedL = sampleL * panL;
float pannedR = sampleR * panR;
// Accumulate into bus
if (outL) outL[s] += pannedL;
if (outR) outR[s] += pannedR;
// Track channel peaks
float absL = std::fabs(pannedL);
float absR = std::fabs(pannedR);
if (absL > chPeakL) chPeakL = absL;
if (absR > chPeakR) chPeakR = absR;
}
// Convert channel peaks to dB and write meters
self->peakL[ch] = (chPeakL > 0.0f)
? (20.0f * std::log10(chPeakL)) : -70.0f;
self->peakR[ch] = (chPeakR > 0.0f)
? (20.0f * std::log10(chPeakR)) : -70.0f;
if (self->peakL[ch] < -70.0f) self->peakL[ch] = -70.0f;
if (self->peakR[ch] < -70.0f) self->peakR[ch] = -70.0f;
if (self->pLevelL[ch]) *self->pLevelL[ch] = self->peakL[ch];
if (self->pLevelR[ch]) *self->pLevelR[ch] = self->peakR[ch];
// Track master peaks (pre-master-gain)
if (chPeakL > newMasterPeakL) newMasterPeakL = chPeakL;
if (chPeakR > newMasterPeakR) newMasterPeakR = chPeakR;
}
// Apply master gain to output buffers
if (masterGain != 1.0f) {
for (uint32_t s = 0; s < nSamples; ++s) {
if (outL) outL[s] *= masterGain;
if (outR) outR[s] *= masterGain;
}
}
// Write master level meters (pre-master-gain reading)
self->peakMasterL = (newMasterPeakL > 0.0f)
? (20.0f * std::log10(newMasterPeakL)) : -70.0f;
self->peakMasterR = (newMasterPeakR > 0.0f)
? (20.0f * std::log10(newMasterPeakR)) : -70.0f;
if (self->peakMasterL < -70.0f) self->peakMasterL = -70.0f;
if (self->peakMasterR < -70.0f) self->peakMasterR = -70.0f;
if (self->pMasterLevelL) *self->pMasterLevelL = self->peakMasterL;
if (self->pMasterLevelR) *self->pMasterLevelR = self->peakMasterR;
}
void OPLabsBandBus::deactivate(LV2_Handle instance)
{
(void)instance;
}
void OPLabsBandBus::cleanup(LV2_Handle instance)
{
delete (OPLabsBandBus*)instance;
}
const void* OPLabsBandBus::extensionData(const char* uri)
{
if (strcmp(uri, LV2_STATE__interface) == 0) {
return &sStateInterface;
}
return nullptr;
}
// ====================================================================
// State save/restore — stub implementations
// ====================================================================
LV2_State_Status OPLabsBandBus::saveState(
LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
(void)instance; (void)store; (void)handle; (void)flags; (void)features;
return LV2_STATE_SUCCESS;
}
LV2_State_Status OPLabsBandBus::restoreState(
LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
(void)instance; (void)retrieve; (void)handle; (void)flags; (void)features;
return LV2_STATE_SUCCESS;
}
+154
View File
@@ -0,0 +1,154 @@
#ifndef OPLABSBANDBUS_HPP
#define OPLABSBANDBUS_HPP
#include <lv2/core/lv2.h>
#include <lv2/state/state.h>
#include <lv2/urid/urid.h>
#include <cstdint>
#include <cmath>
#include <cstring>
/**
* OPLabsBandBus — 8-channel stereo bus mixer LV2 plugin.
*
* Sums 8 stereo input pairs into a stereo output with per-channel
* volume, pan, mute controls and peak level metering. Includes master
* volume and mute. Compatible with PiPedal and any LV2 host.
*
* Port layout (62 ports total):
* Audio inputs: ch1L..ch8R (015, 16 ports)
* Audio outputs: outL, outR (1617, 2 ports)
* Controls in: ch1Vol..ch8Vol (1825, 8 ports)
* ch1Pan..ch8Pan (2633, 8 ports)
* ch1Mute..ch8Mute(3441, 8 ports)
* masterVol (42, 1 port)
* masterMute (43, 1 port)
* Controls out: ch1LevelL..ch8LevelL (4451, 8 ports)
* ch1LevelR..ch8LevelR (5259, 8 ports)
* masterLevelL, masterLevelR (6061, 2 ports)
*
* URI: http://ourpad.casa/plugins/oplabs-band-bus#
*/
class OPLabsBandBus {
public:
static constexpr const char* URI = "http://ourpad.casa/plugins/oplabs-band-bus#";
static constexpr uint32_t kNumChannels = 8;
enum Ports : uint32_t {
// Audio inputs (16)
kCh1L = 0, kCh1R = 1,
kCh2L = 2, kCh2R = 3,
kCh3L = 4, kCh3R = 5,
kCh4L = 6, kCh4R = 7,
kCh5L = 8, kCh5R = 9,
kCh6L = 10, kCh6R = 11,
kCh7L = 12, kCh7R = 13,
kCh8L = 14, kCh8R = 15,
// Audio outputs (2)
kOutL = 16,
kOutR = 17,
// Per-channel volume (8)
kCh1Vol = 18, kCh2Vol = 19,
kCh3Vol = 20, kCh4Vol = 21,
kCh5Vol = 22, kCh6Vol = 23,
kCh7Vol = 24, kCh8Vol = 25,
// Per-channel pan (8)
kCh1Pan = 26, kCh2Pan = 27,
kCh3Pan = 28, kCh4Pan = 29,
kCh5Pan = 30, kCh6Pan = 31,
kCh7Pan = 32, kCh8Pan = 33,
// Per-channel mute (8)
kCh1Mute = 34, kCh2Mute = 35,
kCh3Mute = 36, kCh4Mute = 37,
kCh5Mute = 38, kCh6Mute = 39,
kCh7Mute = 40, kCh8Mute = 41,
// Master controls (2)
kMasterVol = 42,
kMasterMute = 43,
// Per-channel level L (8)
kCh1LevelL = 44, kCh2LevelL = 45,
kCh3LevelL = 46, kCh4LevelL = 47,
kCh5LevelL = 48, kCh6LevelL = 49,
kCh7LevelL = 50, kCh8LevelL = 51,
// Per-channel level R (8)
kCh1LevelR = 52, kCh2LevelR = 53,
kCh3LevelR = 54, kCh4LevelR = 55,
kCh5LevelR = 56, kCh6LevelR = 57,
kCh7LevelR = 58, kCh8LevelR = 59,
// Master level (2)
kMasterLevelL = 60,
kMasterLevelR = 61,
kNumPorts = 62
};
// ---- Static LV2 callbacks ----
static LV2_Handle instantiate(const LV2_Descriptor* descriptor,
double sampleRate,
const char* bundlePath,
const LV2_Feature* const* features);
static void connectPort(LV2_Handle instance, uint32_t port, void* data);
static void activate(LV2_Handle instance);
static void run(LV2_Handle instance, uint32_t nSamples);
static void deactivate(LV2_Handle instance);
static void cleanup(LV2_Handle instance);
static const void* extensionData(const char* uri);
// ---- State callbacks ----
static LV2_State_Status saveState(LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features);
static LV2_State_Status restoreState(LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features);
private:
OPLabsBandBus(double sampleRate, const LV2_Feature* const* features);
~OPLabsBandBus() = default;
OPLabsBandBus(const OPLabsBandBus&) = delete;
OPLabsBandBus& operator=(const OPLabsBandBus&) = delete;
bool initUrids(const LV2_Feature* const* features);
static inline float dBToGain(float dB) {
return std::pow(10.0f, dB / 20.0f);
}
// URID map
LV2_URID_Map* uridMap = nullptr;
LV2_URID urid__stateDummy = 0;
// Audio port pointers [ch][0=L,1=R]
const float* pAudioIn[8][2] = {};
float* pAudioOut[2] = {};
// Control input port pointers
const float* pVol[8] = {};
const float* pPan[8] = {};
const float* pMute[8] = {};
const float* pMasterVol = nullptr;
const float* pMasterMute = nullptr;
// Control output port pointers (meters)
float* pLevelL[8] = {};
float* pLevelR[8] = {};
float* pMasterLevelL = nullptr;
float* pMasterLevelR = nullptr;
// Cached parameter values
float cachedVol[8] = {};
float cachedPan[8] = {};
bool cachedMute[8] = {};
float cachedMasterVol = 0.0f;
bool cachedMasterMute = false;
// Peak meter state
float peakL[8] = {};
float peakR[8] = {};
float peakMasterL = -70.0f;
float peakMasterR = -70.0f;
};
#endif // OPLABSBANDBUS_HPP
+6
View File
@@ -0,0 +1,6 @@
VERS_1.0 {
global:
lv2_descriptor;
local:
*;
};
+312
View File
@@ -0,0 +1,312 @@
#include "OPLabsBandChannel.hpp"
#include <cstdlib>
#include <cstdio>
#include <algorithm>
// ====================================================================
// LV2 Descriptor
// ====================================================================
static LV2_Descriptor sDescriptor = {
OPLabsBandChannel::URI,
OPLabsBandChannel::instantiate,
OPLabsBandChannel::connectPort,
OPLabsBandChannel::activate,
OPLabsBandChannel::run,
OPLabsBandChannel::deactivate,
OPLabsBandChannel::cleanup,
OPLabsBandChannel::extensionData
};
LV2_SYMBOL_EXPORT
const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
return (index == 0) ? &sDescriptor : nullptr;
}
// ====================================================================
// State interface (extension data)
// ====================================================================
static const LV2_State_Interface sStateInterface = {
OPLabsBandChannel::saveState,
OPLabsBandChannel::restoreState
};
// ====================================================================
// Constructor / Destructor helpers
// ====================================================================
OPLabsBandChannel::OPLabsBandChannel(double sampleRate,
const LV2_Feature* const* features)
{
(void)sampleRate;
initUrids(features);
}
bool OPLabsBandChannel::initUrids(const LV2_Feature* const* features)
{
for (int i = 0; features && features[i]; ++i) {
if (strcmp(features[i]->URI, LV2_URID__map) == 0) {
uridMap = (LV2_URID_Map*)features[i]->data;
break;
}
}
if (!uridMap) {
fprintf(stderr, "OPLabsBandChannel: Host does not support urid:map\n");
return false;
}
// Map state key URIDs
urid__stateInstrument = uridMap->map(
uridMap->handle,
"http://ourpad.casa/plugins/oplabs-band-channel#instrument"
);
return true;
}
// ====================================================================
// Static callbacks
// ====================================================================
LV2_Handle OPLabsBandChannel::instantiate(const LV2_Descriptor* descriptor,
double sampleRate,
const char* bundlePath,
const LV2_Feature* const* features)
{
(void)descriptor;
(void)bundlePath;
OPLabsBandChannel* self = new OPLabsBandChannel(sampleRate, features);
if (!self->uridMap) {
delete self;
return nullptr;
}
return (LV2_Handle)self;
}
void OPLabsBandChannel::connectPort(LV2_Handle instance, uint32_t port, void* data)
{
OPLabsBandChannel* self = (OPLabsBandChannel*)instance;
switch ((Ports)port) {
case kVolume:
self->pVolume = (const float*)data;
break;
case kPan:
self->pPan = (const float*)data;
break;
case kMute:
self->pMute = (const float*)data;
break;
case kSolo:
self->pSolo = (const float*)data;
break;
case kInstrument:
self->pInstrument = (const float*)data;
break;
case kLevelL:
self->pLevelL = (float*)data;
break;
case kLevelR:
self->pLevelR = (float*)data;
break;
case kInL:
self->pInL = (const float*)data;
break;
case kInR:
self->pInR = (const float*)data;
break;
case kOutL:
self->pOutL = (float*)data;
break;
case kOutR:
self->pOutR = (float*)data;
break;
default:
break;
}
}
void OPLabsBandChannel::activate(LV2_Handle instance)
{
(void)instance;
// No-op for now — buffer allocations go here when DSP is added
}
void OPLabsBandChannel::run(LV2_Handle instance, uint32_t nSamples)
{
OPLabsBandChannel* self = (OPLabsBandChannel*)instance;
// Read control values (with caching to detect changes)
if (self->pVolume) self->cachedVolume = *self->pVolume;
if (self->pPan) self->cachedPan = *self->pPan;
if (self->pMute) self->cachedMute = (*self->pMute > 0.5f);
if (self->pSolo) self->cachedSolo = (*self->pSolo > 0.5f);
if (self->pInstrument) self->cachedInstrument = (int)(*self->pInstrument + 0.5f);
const float* inL = self->pInL ? self->pInL : nullptr;
const float* inR = self->pInR ? self->pInR : nullptr;
float* outL = self->pOutL ? self->pOutL : nullptr;
float* outR = self->pOutR ? self->pOutR : nullptr;
// If muted, output silence and update meters
if (self->cachedMute) {
if (outL) std::memset(outL, 0, nSamples * sizeof(float));
if (outR) std::memset(outR, 0, nSamples * sizeof(float));
if (self->pLevelL) *self->pLevelL = -60.0f;
if (self->pLevelR) *self->pLevelR = -60.0f;
self->peakL = -60.0f;
self->peakR = -60.0f;
return;
}
// Compute linear gain from dB
float linearGain = dBToGain(self->cachedVolume);
// Compute pan coefficients (equal power)
// pan = -1 → full left, 0 → center, +1 → full right
float panL = 1.0f;
float panR = 1.0f;
if (self->cachedPan <= 0.0f) {
// Pan left: reduce right
panR = 1.0f + self->cachedPan; // cachedPan is [-1..0], so panR is [0..1]
} else {
// Pan right: reduce left
panL = 1.0f - self->cachedPan; // cachedPan is [0..1], so panL is [0..1]
}
// Process audio samples
float newPeakL = -60.0f;
float newPeakR = -60.0f;
for (uint32_t s = 0; s < nSamples; ++s) {
float sampleL = inL ? inL[s] : 0.0f;
float sampleR = inR ? inR[s] : 0.0f;
// Apply gain
sampleL *= linearGain;
sampleR *= linearGain;
// Apply pan — stereo balance pan
// (stereo pan: independent per-channel would be different, but
// this is the standard "balance" pan for a stereo channel strip)
if (outL) outL[s] = sampleL * panL;
if (outR) outR[s] = sampleR * panR;
// Track peaks
float absL = std::fabs(outL ? outL[s] : 0.0f);
float absR = std::fabs(outR ? outR[s] : 0.0f);
if (absL > newPeakL) newPeakL = absL;
if (absR > newPeakR) newPeakR = absR;
}
// Convert peak amplitude to dB
self->peakL = (newPeakL > 0.0f) ? (20.0f * std::log10(newPeakL)) : -60.0f;
self->peakR = (newPeakR > 0.0f) ? (20.0f * std::log10(newPeakR)) : -60.0f;
// Clamp to -60 dB minimum for meter display
if (self->peakL < -60.0f) self->peakL = -60.0f;
if (self->peakR < -60.0f) self->peakR = -60.0f;
// Write meter outputs
if (self->pLevelL) *self->pLevelL = self->peakL;
if (self->pLevelR) *self->pLevelR = self->peakR;
}
void OPLabsBandChannel::deactivate(LV2_Handle instance)
{
(void)instance;
}
void OPLabsBandChannel::cleanup(LV2_Handle instance)
{
delete (OPLabsBandChannel*)instance;
}
const void* OPLabsBandChannel::extensionData(const char* uri)
{
if (strcmp(uri, LV2_STATE__interface) == 0) {
return &sStateInterface;
}
return nullptr;
}
// ====================================================================
// State save/restore
// ====================================================================
LV2_State_Status OPLabsBandChannel::saveState(
LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
(void)flags;
(void)features;
OPLabsBandChannel* self = (OPLabsBandChannel*)instance;
// Save instrument selection
if (self->urid__stateInstrument) {
int32_t instr = self->cachedInstrument;
store(handle,
self->urid__stateInstrument,
&instr,
sizeof(int32_t),
self->uridMap->map(self->uridMap->handle, LV2_ATOM__Int),
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
}
return LV2_STATE_SUCCESS;
}
LV2_State_Status OPLabsBandChannel::restoreState(
LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
(void)flags;
(void)features;
OPLabsBandChannel* self = (OPLabsBandChannel*)instance;
// Restore instrument selection
if (self->urid__stateInstrument) {
size_t size = 0;
uint32_t type = 0;
uint32_t valFlags = 0;
const void* data = retrieve(handle,
self->urid__stateInstrument,
&size, &type, &valFlags);
if (data && size == sizeof(int32_t)) {
int32_t instr = *(const int32_t*)data;
if (instr >= 0 && instr <= 4) {
self->cachedInstrument = instr;
// Update the control port if connected
if (self->pInstrument) {
*(const_cast<float*>(self->pInstrument)) = (float)instr;
}
}
}
}
return LV2_STATE_SUCCESS;
}
float OPLabsBandChannel::computePeakDB(const float* buffer,
uint32_t nSamples,
float currentPeak)
{
float peak = currentPeak;
for (uint32_t s = 0; s < nSamples; ++s) {
float absVal = std::fabs(buffer[s]);
if (absVal > peak) peak = absVal;
}
// Peak-to-decibel conversion happens in the caller;
// keeping this helper for future use
return peak;
}
+126
View File
@@ -0,0 +1,126 @@
#ifndef OPLABSBANDCHANNEL_HPP
#define OPLABSBANDCHANNEL_HPP
#include <lv2/core/lv2.h>
#include <lv2/state/state.h>
#include <lv2/urid/urid.h>
#include <lv2/atom/atom.h>
#include <cstdint>
#include <cmath>
#include <cstring>
/**
* OPLabsBandChannel — Stereo channel strip LV2 plugin.
*
* Provides volume, pan, mute, solo, and instrument type controls
* with peak level metering. Compatible with PiPedal and any LV2 host.
*
* URI: http://ourpad.casa/plugins/oplabs-band-channel#
*/
class OPLabsBandChannel {
public:
/** Plugin URI constant. */
static constexpr const char* URI = "http://ourpad.casa/plugins/oplabs-band-channel#";
/** Port index enum — must match OPLabsBandChannel.ttl lv2:index values exactly. */
enum Ports : uint32_t {
kVolume = 0, // Control input, -60..+6 dB, default 0
kPan = 1, // Control input, -1..+1, default 0
kMute = 2, // Control input, toggled 0/1
kSolo = 3, // Control input, toggled 0/1
kInstrument = 4, // Control input, enumeration 0..4
kLevelL = 5, // Control output, -60..0 dB
kLevelR = 6, // Control output, -60..0 dB
kInL = 7, // Audio input, left
kInR = 8, // Audio input, right
kOutL = 9, // Audio output, left
kOutR = 10, // Audio output, right
kNumPorts = 11
};
// ---- LV2 descriptor callbacks (static) ----
static LV2_Handle instantiate(const LV2_Descriptor* descriptor,
double sampleRate,
const char* bundlePath,
const LV2_Feature* const* features);
static void connectPort(LV2_Handle instance, uint32_t port, void* data);
static void activate(LV2_Handle instance);
static void run(LV2_Handle instance, uint32_t nSamples);
static void deactivate(LV2_Handle instance);
static void cleanup(LV2_Handle instance);
static const void* extensionData(const char* uri);
// ---- State extension callbacks (static) ----
static LV2_State_Status saveState(LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features);
static LV2_State_Status restoreState(LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features);
private:
OPLabsBandChannel(double sampleRate, const LV2_Feature* const* features);
~OPLabsBandChannel() = default;
// Non-copyable, non-movable
OPLabsBandChannel(const OPLabsBandChannel&) = delete;
OPLabsBandChannel& operator=(const OPLabsBandChannel&) = delete;
/** Initialize URID mapping from features. */
bool initUrids(const LV2_Feature* const* features);
/** Convert dB to linear gain. */
static inline float dBToGain(float dB) {
return std::pow(10.0f, dB / 20.0f);
}
/** Compute peak level in dB from a buffer. Returns -60 dB for silence. */
static float computePeakDB(const float* buffer, uint32_t nSamples, float currentPeak);
// ---- Instance data ----
// URID map feature (provided by host)
LV2_URID_Map* uridMap = nullptr;
// State keys (mapped URIDs)
LV2_URID urid__stateInstrument = 0;
// Port data pointers (set by connectPort)
const float* pVolume = nullptr;
const float* pPan = nullptr;
const float* pMute = nullptr;
const float* pSolo = nullptr;
const float* pInstrument = nullptr;
float* pLevelL = nullptr;
float* pLevelR = nullptr;
const float* pInL = nullptr;
const float* pInR = nullptr;
float* pOutL = nullptr;
float* pOutR = nullptr;
// Cached parameter values
float cachedVolume = 0.0f;
float cachedPan = 0.0f;
bool cachedMute = false;
bool cachedSolo = false;
int cachedInstrument = 0;
// Peak meters (smoothed)
float peakL = -60.0f;
float peakR = -60.0f;
};
#endif // OPLABSBANDCHANNEL_HPP
+6
View File
@@ -0,0 +1,6 @@
VERS_1.0 {
global:
lv2_descriptor;
local:
*;
};