a5423b7f55
Extracted from op-pedal mixer-engine branch. Core components: - MixerEngine / MixerChannelStrip / MixerBus — cleaned of LV2/Pedalboard/IHost/MidiMapper deps - MixerControlServer — Unix domain socket JSON control interface - main.cpp — JACK audio I/O client with headless fallback - CMake build — auto-fetches nlohmann/json, links JACK - 9 core tests passing Architecture: JACK ports → MixerEngine::process() → JACK ports, controlled via Unix socket JSON commands.
211 lines
6.7 KiB
C++
211 lines
6.7 KiB
C++
// 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 "mixer/MixerChannelStrip.hpp"
|
|
#include "mixer/MixerBus.hpp"
|
|
#include "mixer/Types.hpp"
|
|
|
|
namespace mixer {
|
|
|
|
class MixerChannelStrip;
|
|
class MixerBus;
|
|
|
|
/// 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: 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);
|
|
|
|
/// Auto-create channels based on the number of detected input channels.
|
|
/// Removes existing channels and creates one strip per input.
|
|
/// Also sets up default output routes.
|
|
void autoCreateChannels(uint32_t inputChannelCount, uint32_t outputChannelCount = 2);
|
|
|
|
/// 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();
|
|
|
|
/// --- Output Routing ---
|
|
|
|
/// Get the current output routing table (bus → physical output channel mapping).
|
|
const std::vector<MixerOutputRoute>& outputRoutes() const { return outputRoutes_; }
|
|
|
|
/// Set the entire output routing table.
|
|
void setOutputRoutes(const std::vector<MixerOutputRoute>& routes);
|
|
|
|
/// Add a single output route.
|
|
void addOutputRoute(int64_t busId, int sourceStartChannel, int targetStartChannel, int channels);
|
|
|
|
/// Remove all output routes for a given bus.
|
|
void removeOutputRoutes(int64_t busId);
|
|
|
|
/// Get all routes for a given bus.
|
|
std::vector<MixerOutputRoute> findOutputRoutesForBus(int64_t busId) const;
|
|
|
|
/// Get all current routes.
|
|
const std::vector<MixerRouteEntry>& routes() const { return routes_; }
|
|
|
|
/// --- Physical I/O Info ---
|
|
|
|
/// Number of physical input channels detected on the audio device.
|
|
uint32_t physicalInputCount() const { return physicalInputCount_; }
|
|
/// Number of physical output channels detected on the audio device.
|
|
uint32_t physicalOutputCount() const { return physicalOutputCount_; }
|
|
|
|
/// Set the physical I/O channel counts.
|
|
void setPhysicalChannelCounts(uint32_t inputs, uint32_t outputs) {
|
|
physicalInputCount_ = inputs;
|
|
physicalOutputCount_ = outputs;
|
|
}
|
|
|
|
/// --- Audio Processing (real-time thread) ---
|
|
|
|
/// Prepare all channels and allocate buffers.
|
|
void Prepare();
|
|
|
|
/// Activate all channels.
|
|
void Activate();
|
|
|
|
/// Deactivate all channels.
|
|
void Deactivate();
|
|
|
|
/// Process one full mixer cycle.
|
|
/// deviceInputs/outputs are the raw audio interface buffers.
|
|
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 ---
|
|
|
|
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_;
|
|
|
|
// Output routing entries (bus → physical output channel mapping)
|
|
std::vector<MixerOutputRoute> outputRoutes_;
|
|
|
|
// Audio configuration
|
|
uint32_t sampleRate_ = 48000;
|
|
size_t maxBufferSize_ = 512;
|
|
|
|
// Next bus ID counter
|
|
static std::atomic<int64_t> nextBusId_;
|
|
|
|
// Temporary per-channel output buffers for routing
|
|
std::vector<std::vector<float>> channelOutputBuffers_;
|
|
|
|
// Physical I/O channel counts
|
|
uint32_t physicalInputCount_ = 0;
|
|
uint32_t physicalOutputCount_ = 0;
|
|
|
|
// 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 mixer
|