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.
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2026 Ourpad Network
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include "MixerChannelStrip.hpp"
|
||||
#include "MixerBus.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class MixerChannelStrip;
|
||||
class MixerBus;
|
||||
class Lv2PedalboardErrorList;
|
||||
class IHost;
|
||||
|
||||
/// Routing entry: a source (channel or bus) feeds a target bus with a level.
|
||||
struct MixerRouteEntry {
|
||||
enum SourceType {
|
||||
SourceChannel,
|
||||
SourceBus
|
||||
};
|
||||
|
||||
SourceType sourceType;
|
||||
int64_t sourceId; // channel instanceId or bus ID
|
||||
int64_t targetBusId; // the bus being fed into
|
||||
float level = 0.0f; // dB
|
||||
};
|
||||
|
||||
/// The MixerEngine is the heart of the band-in-a-box digital mixer.
|
||||
///
|
||||
/// It owns and manages:
|
||||
/// - N channel strips (MixerChannelStrip), one per physical/logical input
|
||||
/// - M buses (MixerBus), including master, subgroups, aux sends
|
||||
/// - A routing graph connecting channels to buses and buses to buses
|
||||
///
|
||||
/// Processing order per audio cycle:
|
||||
/// 1. Clear all bus buffers
|
||||
/// 2. For each channel: process FX chain → apply volume/pan → accumulate to routed buses
|
||||
/// 3. For each aux send: calculate send level, accumulate to aux buses
|
||||
/// 4. Route buses to buses according to routing matrix
|
||||
/// 5. Process each bus (apply volume, compute VU)
|
||||
/// 6. Master bus outputs are the final mix
|
||||
///
|
||||
/// All control methods are thread-safe for use from the non-RT thread.
|
||||
/// The process() method runs in the RT audio thread.
|
||||
class MixerEngine {
|
||||
public:
|
||||
MixerEngine();
|
||||
~MixerEngine();
|
||||
|
||||
// Disable copy
|
||||
MixerEngine(const MixerEngine&) = delete;
|
||||
MixerEngine& operator=(const MixerEngine&) = delete;
|
||||
|
||||
/// --- Configuration ---
|
||||
|
||||
/// Set sample rate and max buffer size before preparation
|
||||
void setSampleRate(uint32_t sampleRate);
|
||||
void setMaxBufferSize(size_t frames);
|
||||
|
||||
/// --- Channel Management ---
|
||||
|
||||
/// Add a new channel strip for the given physical input index.
|
||||
/// Returns a pointer to the new channel (valid until removed).
|
||||
MixerChannelStrip* addChannel(int physicalInputIndex);
|
||||
|
||||
/// Remove a channel by its channel index.
|
||||
void removeChannel(int channelIndex);
|
||||
|
||||
/// Get a channel by index. Returns nullptr if not found.
|
||||
MixerChannelStrip* getChannel(int channelIndex);
|
||||
const MixerChannelStrip* getChannel(int channelIndex) const;
|
||||
|
||||
/// Number of channels currently in the mixer.
|
||||
size_t channelCount() const { return channels_.size(); }
|
||||
|
||||
/// --- Bus Management ---
|
||||
|
||||
/// Add a new bus and return its ID.
|
||||
int64_t addBus(MixerBusType type, const std::string& name, int channels = 2);
|
||||
|
||||
/// Remove a bus by ID.
|
||||
void removeBus(int64_t busId);
|
||||
|
||||
/// Get a bus by ID. Returns nullptr if not found.
|
||||
MixerBus* getBus(int64_t busId);
|
||||
const MixerBus* getBus(int64_t busId) const;
|
||||
|
||||
/// Access the master bus (always present).
|
||||
MixerBus* masterBus() { return masterBus_; }
|
||||
const MixerBus* masterBus() const { return masterBus_; }
|
||||
|
||||
/// Get all bus IDs (for iteration).
|
||||
std::vector<int64_t> busIds() const;
|
||||
|
||||
/// --- Routing ---
|
||||
|
||||
/// Route a channel to a bus with a given level in dB.
|
||||
void routeChannelToBus(int channelIndex, int64_t busId, float levelDb = 0.0f);
|
||||
|
||||
/// Route a bus to another bus (e.g., subgroup to master).
|
||||
void routeBusToBus(int64_t sourceBusId, int64_t targetBusId, float levelDb = 0.0f);
|
||||
|
||||
/// Remove a route.
|
||||
void removeRoute(int64_t sourceId, int64_t targetBusId);
|
||||
|
||||
/// Clear all routes.
|
||||
void clearRoutes();
|
||||
|
||||
/// Get all current routes.
|
||||
const std::vector<MixerRouteEntry>& routes() const { return routes_; }
|
||||
|
||||
/// --- Audio Processing (real-time thread) ---
|
||||
|
||||
/// Prepare all channels and allocate buffers.
|
||||
void Prepare(IHost* pHost, Lv2PedalboardErrorList& errorList);
|
||||
|
||||
/// Activate all channels.
|
||||
void Activate();
|
||||
|
||||
/// Deactivate all channels.
|
||||
void Deactivate();
|
||||
|
||||
/// Process one full mixer cycle.
|
||||
/// deviceInputs/outputs are the raw audio interface buffers.
|
||||
/// The mixer reads from inputs, processes through channels → buses, writes to outputs.
|
||||
void process(
|
||||
float** deviceInputs,
|
||||
uint32_t inputChannels,
|
||||
float** deviceOutputs,
|
||||
uint32_t outputChannels,
|
||||
uint32_t frames
|
||||
);
|
||||
|
||||
/// --- Solo Management ---
|
||||
|
||||
/// True if any channel has solo engaged.
|
||||
bool anySoloActive() const;
|
||||
|
||||
/// --- State Serialization ---
|
||||
|
||||
struct MixerSnapshot {
|
||||
struct ChannelState {
|
||||
int channelIndex;
|
||||
float volume;
|
||||
float pan;
|
||||
bool mute;
|
||||
bool solo;
|
||||
MixerChannelType channelType;
|
||||
std::string label;
|
||||
bool hpEnabled;
|
||||
float hpFrequency;
|
||||
std::vector<float> auxSendLevels; // indexed by aux bus index
|
||||
};
|
||||
struct BusState {
|
||||
int64_t id;
|
||||
std::string name;
|
||||
MixerBusType type;
|
||||
float volume;
|
||||
bool mute;
|
||||
};
|
||||
std::vector<ChannelState> channels;
|
||||
std::vector<BusState> buses;
|
||||
std::vector<MixerRouteEntry> routes;
|
||||
};
|
||||
|
||||
MixerSnapshot captureSnapshot() const;
|
||||
void applySnapshot(const MixerSnapshot& snapshot);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<MixerChannelStrip>> channels_;
|
||||
std::map<int64_t, std::unique_ptr<MixerBus>> buses_;
|
||||
MixerBus* masterBus_ = nullptr;
|
||||
|
||||
// Routing entries
|
||||
std::vector<MixerRouteEntry> routes_;
|
||||
|
||||
// Audio configuration
|
||||
uint32_t sampleRate_ = 48000;
|
||||
size_t maxBufferSize_ = 512;
|
||||
|
||||
// IHost reference for FX preparation
|
||||
IHost* pHost_ = nullptr;
|
||||
|
||||
// Next bus ID counter
|
||||
static std::atomic<int64_t> nextBusId_;
|
||||
|
||||
// Temporary per-channel output buffers for routing
|
||||
// Allocated once at prepare time
|
||||
std::vector<std::vector<float>> channelOutputBuffers_;
|
||||
|
||||
// Internal helper: accumulate a channel's output to all its routed buses
|
||||
void routeChannelOutput(
|
||||
MixerChannelStrip* channel,
|
||||
float** channelOutput,
|
||||
uint32_t frames
|
||||
);
|
||||
|
||||
// Internal helper: process all bus-to-bus routing
|
||||
void processBusRouting(uint32_t frames);
|
||||
|
||||
// Build a list of all routes from a given source
|
||||
std::vector<MixerRouteEntry*> findRoutesForSource(int64_t sourceId);
|
||||
};
|
||||
|
||||
} // namespace pipedal
|
||||
Reference in New Issue
Block a user