d19e0ea7a8
MasterBus: stereo master fader, mute, level meters in PiPedal theme (purple accent per mixer-engine branch reference). BusStrip: added per-channel routing selector (ch1Route..ch8Route) for selecting output bus target (Main, Bus 2-8). MixerPage: integrated MasterBus with levels derived from bus master data. Local state for volume/mute until backend exposes master output.
135 lines
4.7 KiB
C++
135 lines
4.7 KiB
C++
// Copyright (c) 2026 Ourpad Network
|
|
// See LICENSE file in the project root for full license text.
|
|
//
|
|
// MixerDaemon — Standalone OPLabs Mixer Engine daemon.
|
|
//
|
|
// This daemon:
|
|
// 1. Creates a MixerEngine with JACK audio I/O
|
|
// 2. Creates a MixerApi for control
|
|
// 3. Starts the MixerIpcServer on a Unix domain socket
|
|
// 4. Runs the audio loop until SIGINT/SIGTERM
|
|
|
|
#include <cstdio>
|
|
#include <csignal>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <memory>
|
|
|
|
#include "MixerIpcServer.hpp"
|
|
#include "MixerApi.hpp"
|
|
#include "MixerEngine.hpp"
|
|
|
|
using namespace pipedal;
|
|
|
|
// Global pointers for signal handler
|
|
static MixerIpcServer* g_server = nullptr;
|
|
static std::atomic<bool> g_running{true};
|
|
|
|
void signalHandler(int sig) {
|
|
(void)sig;
|
|
fprintf(stderr, "\n[MixerDaemon] Signal received, shutting down...\n");
|
|
g_running.store(false);
|
|
if (g_server) {
|
|
g_server->stop();
|
|
}
|
|
}
|
|
|
|
void printUsage(const char* prog) {
|
|
fprintf(stderr, "Usage: %s [options]\n", prog);
|
|
fprintf(stderr, "Options:\n");
|
|
fprintf(stderr, " --socket-path PATH Unix socket path (default: /tmp/oplabs-mixer.sock)\n");
|
|
fprintf(stderr, " --sample-rate RATE Sample rate in Hz (default: 48000)\n");
|
|
fprintf(stderr, " --buffer-size SIZE Buffer size in frames (default: 512)\n");
|
|
fprintf(stderr, " --input-channels N Number of input channels (default: 8)\n");
|
|
fprintf(stderr, " --output-channels N Number of output channels (default: 8)\n");
|
|
fprintf(stderr, " --help Print this help\n");
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// Default parameters
|
|
std::string socketPath = "/tmp/oplabs-mixer.sock";
|
|
uint32_t sampleRate = 48000;
|
|
size_t bufferSize = 512;
|
|
uint32_t inputChannels = 8;
|
|
uint32_t outputChannels = 8;
|
|
|
|
// Parse command line
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string arg = argv[i];
|
|
if (arg == "--socket-path" && i + 1 < argc) {
|
|
socketPath = argv[++i];
|
|
} else if (arg == "--sample-rate" && i + 1 < argc) {
|
|
sampleRate = static_cast<uint32_t>(std::stoul(argv[++i]));
|
|
} else if (arg == "--buffer-size" && i + 1 < argc) {
|
|
bufferSize = static_cast<size_t>(std::stoul(argv[++i]));
|
|
} else if (arg == "--input-channels" && i + 1 < argc) {
|
|
inputChannels = static_cast<uint32_t>(std::stoul(argv[++i]));
|
|
} else if (arg == "--output-channels" && i + 1 < argc) {
|
|
outputChannels = static_cast<uint32_t>(std::stoul(argv[++i]));
|
|
} else if (arg == "--help") {
|
|
printUsage(argv[0]);
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "Unknown option: %s\n", arg.c_str());
|
|
printUsage(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "[MixerDaemon] Starting OPLabs Mixer Daemon\n");
|
|
fprintf(stderr, "[MixerDaemon] Socket: %s\n", socketPath.c_str());
|
|
fprintf(stderr, "[MixerDaemon] SampleRate: %u Hz\n", sampleRate);
|
|
fprintf(stderr, "[MixerDaemon] BufferSize: %zu frames\n", bufferSize);
|
|
fprintf(stderr, "[MixerDaemon] Inputs: %u\n", inputChannels);
|
|
fprintf(stderr, "[MixerDaemon] Outputs: %u\n", outputChannels);
|
|
|
|
// Set up signal handling
|
|
signal(SIGINT, signalHandler);
|
|
signal(SIGTERM, signalHandler);
|
|
|
|
// Create MixerEngine
|
|
auto engine = std::make_unique<MixerEngine>();
|
|
engine->setSampleRate(sampleRate);
|
|
engine->setMaxBufferSize(bufferSize);
|
|
|
|
// Auto-create channels
|
|
engine->autoCreateChannels(inputChannels);
|
|
|
|
fprintf(stderr, "[MixerDaemon] MixerEngine created with %zu channels\n",
|
|
engine->channelCount());
|
|
|
|
// Create MixerApi and attach engine
|
|
auto api = std::make_unique<MixerApi>();
|
|
api->setMixerEngine(engine.get());
|
|
|
|
// Create IPC server
|
|
auto server = std::make_unique<MixerIpcServer>();
|
|
server->setMixerApi(api.get());
|
|
server->setMixerEngine(engine.get());
|
|
g_server = server.get();
|
|
|
|
if (!server->start(socketPath)) {
|
|
fprintf(stderr, "[MixerDaemon] Failed to start IPC server on %s\n",
|
|
socketPath.c_str());
|
|
return 1;
|
|
}
|
|
|
|
fprintf(stderr, "[MixerDaemon] IPC server listening on %s\n", socketPath.c_str());
|
|
fprintf(stderr, "[MixerDaemon] Ready. Waiting for connections...\n");
|
|
|
|
// Main loop — keep running until signal
|
|
// In a full implementation, this would run the JACK audio processing loop.
|
|
// For now, the daemon provides IPC access to the MixerEngine.
|
|
while (g_running.load()) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
// Cleanup
|
|
server->stop();
|
|
api->setMixerEngine(nullptr);
|
|
|
|
fprintf(stderr, "[MixerDaemon] Shutdown complete.\n");
|
|
return 0;
|
|
}
|