Files
op-pedal/src/MixerChannelStrip.hpp
shawn df5a317ceb feat: add MixerEngine core — ChannelStrip, MixerBus, and MixerEngine for band-in-a-box digital mixer
MixerEngine architecture:
- MixerChannelStrip: per-input FX chain (Lv2Pedalboard reuse),
  volume, pan, mute, solo, HPF, aux sends, VU metering
- MixerBus: accumulation bus with volume, mute, VU. Supports
  master, subgroup, aux, and FX-return bus types
- MixerEngine: orchestrator managing channel→bus routing graph,
  bus→bus routing, solo override, and the full real-time audio
  processing cycle

All new code compiles cleanly with the existing C++20 build and
follows the existing PiPedal codebase conventions (namespaces,
error handling, buffer patterns). CPU-efficient real-time thread
processing with atomic control surface interaction.
2026-06-20 13:57:15 -04:00

230 lines
7.8 KiB
C++

// Copyright (c) 2026 Ourpad Network
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include "Pedalboard.hpp"
#include "Lv2Pedalboard.hpp"
#include "IEffect.hpp"
#include "BufferPool.hpp"
#include <memory>
#include <vector>
#include <atomic>
#include <string>
namespace pipedal {
/// Channel type classification for mixer channel strips.
enum class MixerChannelType {
Instrument, // guitar, bass, keys — expects NAM/guitar amp modeling
Mic, // vocal mic — expects compressor, EQ, reverb chain
Line, // line-level input (backing tracks, synths, drum machines)
AuxReturn, // return from external FX processor
};
/// Configuration for a single aux send on a channel strip.
struct AuxSendConfig {
float level = -96.0f; // dB, -96 ≈ -inf (effectively off)
bool preFader = false; // true = pre-fader (monitor send), false = post-fader (FX send)
int64_t targetBusId = -1; // bus ID this sends to
bool isActive() const { return level > -90.0f && targetBusId >= 0; }
};
/// Per-channel high-pass filter configuration.
struct HpfConfig {
bool enabled = false;
float frequency = 80.0f; // Hz
// Filter state for simple biquad — sized for max buffer
// Allocated at prepare time
};
/// A single channel strip in the mixer.
///
/// Each MixerChannelStrip wraps:
/// - A mini-pedalboard (FX chain using existing Lv2Pedalboard)
/// - Volume fader, pan pot, mute, solo
/// - High-pass filter on input
/// - Aux sends (pre/post fader)
/// - VU metering
///
/// The channel operates in the real-time audio thread.
/// Control changes are made from the non-realtime thread via atomic snapshots.
class MixerChannelStrip {
public:
MixerChannelStrip(int channelIndex);
~MixerChannelStrip();
// Disable copy
MixerChannelStrip(const MixerChannelStrip&) = delete;
MixerChannelStrip& operator=(const MixerChannelStrip&) = delete;
/// Channel identity
int channelIndex() const { return channelIndex_; }
int64_t instanceId() const { return instanceId_; }
/// --- Control surface (thread-safe via atomics for simple values) ---
/// Volume in dB (-inf to +12.0)
float volume() const { return volume_; }
void setVolume(float db);
/// Pan: -1.0 (full left) to +1.0 (full right). 0.0 = center.
float pan() const { return pan_; }
void setPan(float pan);
/// Mute
bool mute() const { return mute_; }
void setMute(bool mute);
/// Solo — overrides mute for monitoring
bool solo() const { return solo_; }
void setSolo(bool solo);
/// Channel type for UI classification
MixerChannelType channelType() const { return channelType_; }
void setChannelType(MixerChannelType type) { channelType_ = type; }
/// User-assignable label
const std::string& label() const { return label_; }
void setLabel(const std::string& label) { label_ = label; }
/// --- Input processing ---
/// High-pass filter
bool hpEnabled() const { return hpEnabled_; }
void setHpEnabled(bool enabled) { hpEnabled_ = enabled; }
float hpFrequency() const { return hpFrequency_; }
void setHpFrequency(float freq) { hpFrequency_ = freq; }
/// --- FX Chain ---
/// Access the channel's pedalboard for plugin management
Pedalboard& fxChain() { return fxChain_; }
const Pedalboard& fxChain() const { return fxChain_; }
/// Get the real-time processor for this channel's FX chain
Lv2Pedalboard* fxProcessor() { return fxProcessor_.get(); }
/// Prepare the FX chain for processing
void prepareFx(IHost* pHost, Lv2PedalboardErrorList& errorList,
ExistingEffectMap* existingEffects = nullptr);
/// --- Aux Sends ---
void setAuxSend(int index, const AuxSendConfig& config);
const AuxSendConfig& auxSend(int index) const;
size_t auxSendCount() const { return auxSends_.size(); }
void resizeAuxSends(size_t count);
/// --- Audio Processing (real-time thread) ---
/// Set sample rate and max buffer size
void setSampleRate(uint32_t sampleRate);
void setMaxBufferSize(size_t frames);
/// Process one audio block through this channel strip.
/// Reads from input, runs HPF → FX chain, applies volume/pan.
/// Output goes to provided output buffer(s).
/// Returns the number of processed samples.
void process(
const float* const* inputBuffers,
size_t inputChannels,
float* const* outputBuffers,
size_t outputChannels,
uint32_t frames
);
/// Get post-FX, pre-fader audio for aux send calculation
const float* postFxBuffer(int channel) const {
if (channel < (int)postFxBuffers_.size()) return postFxBuffers_[channel].data();
return nullptr;
}
/// Get pre-FX audio for pre-fader aux sends
const float* preFxBuffer(int channel) const {
if (channel < (int)preFxBuffers_.size()) return preFxBuffers_[channel].data();
return nullptr;
}
/// Calcuate aux send level considering pre/post fader and mute
float effectiveAuxLevel(int auxIndex, bool anySoloActive) const;
/// VU meter values (peak, not RMS — computed during process)
float vuLeft() const { return vuLeft_; }
float vuRight() const { return vuRight_; }
/// --- Lifecycle ---
void Activate();
void Deactivate();
void Unprepare();
private:
int channelIndex_;
int64_t instanceId_;
static std::atomic<int64_t> nextInstanceId_;
// Control values (atomic for RT-safe reads from control thread)
std::atomic<float> volume_{-96.0f}; // dB, -inf default
std::atomic<float> pan_{0.0f};
std::atomic<bool> mute_{false};
std::atomic<bool> solo_{false};
std::atomic<bool> hpEnabled_{false};
std::atomic<float> hpFrequency_{80.0f};
MixerChannelType channelType_ = MixerChannelType::Instrument;
std::string label_;
// FX chain
Pedalboard fxChain_;
std::unique_ptr<Lv2Pedalboard> fxProcessor_;
// Aux sends
std::vector<AuxSendConfig> auxSends_;
// Audio buffers (allocated at prepare time)
std::vector<std::vector<float>> preFxBuffers_; // Before FX chain (for pre-fader sends)
std::vector<std::vector<float>> postFxBuffers_; // After FX chain, before fader
BufferPool bufferPool_;
// Sample rate / buffer size
uint32_t sampleRate_ = 48000;
size_t maxBufferSize_ = 512;
// VU tracking
float vuLeft_ = -96.0f;
float vuRight_ = -96.0f;
// Simple 1-pole HPF state (per channel)
struct HpfState {
float x1 = 0.0f, y1 = 0.0f;
};
std::vector<HpfState> hpfStates_;
// Apply pan law: constant power (-3dB center)
void applyPan(float& leftGain, float& rightGain) const;
// Apply HPF biquad to a buffer
void applyHpf(float* buffer, uint32_t frames, HpfState& state);
};
} // namespace pipedal