Thanks Connor! Here’s a minimal repro of the pattern (FMOD Core). It plays a looping sound on new channels, taps each by adding its head DSP as an input to a capture DSP (connected silently under the master head), and retires the oldest tap.
My question is the cleanup marked in the code:
- Is
capture->disconnectFrom(head, conn) before channel->stop() the correct/safe order?
- What’s the safe approach if FMOD may have already stopped or stolen the channel before I get there, so the head DSP could be invalid? How do I know when it’s safe to disconnect?
Also — as an alternative, would attaching a per-channel DSP via Channel::addDSP and cleaning up in that DSP’s release callback be a cleaner way to auto-clean exactly when the channel ends (instead of me detecting the stop myself)? Any downside vs the side-chain addInput approach?
cpp
// Minimal FMOD Core repro for:
// "capture a dynamic subset of channels' mixed audio in real-time via a
// side-chain capture DSP" + the connection-cleanup timing question.
//
// Build: link FMOD Core (fmod.hpp / fmod_vc.lib / fmod.dll).
// Put any short looping sound as "sound.wav" next to the exe.
#include <fmod.hpp>
#include <fmod_errors.h>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <windows.h>
#define CK(x) do{ FMOD_RESULT r=(x); if(r!=FMOD_OK) printf("FMOD %d %s @%d\n", r, FMOD_ErrorString(r), __LINE__);}while(0)
static float g_capturePeak = 0.0f;
// Capture DSP: its inputs are auto-mixed into 'in'; we read that (our subset-of-channels
// mix) and output silence so the tapped channels are NOT doubled into the master mix.
static FMOD_RESULT F_CALLBACK captureRead(FMOD_DSP_STATE*, float* in, float* out,
unsigned int length, int inch, int* outch) {
float peak = 0.0f;
if (in) for (unsigned int i = 0; i < length * (unsigned)inch; ++i) { float a = fabsf(in[i]); if (a>peak) peak=a; }
g_capturePeak = peak; // <-- captured mix of tapped channels
int oc = outch ? *outch : inch;
if (out) for (unsigned int i = 0; i < length * (unsigned)oc; ++i) out[i] = 0.0f; // silence out
return FMOD_OK;
}
int main() {
FMOD::System* sys = nullptr;
CK(FMOD::System_Create(&sys));
CK(sys->init(64, FMOD_INIT_NORMAL, nullptr));
FMOD::Sound* snd = nullptr;
CK(sys->createSound("sound.wav", FMOD_LOOP_NORMAL, nullptr, &snd)); // any short looping sound
// --- capture DSP ---
FMOD_DSP_DESCRIPTION d = {};
strncpy(d.name, "capture", sizeof(d.name) - 1);
d.numinputbuffers = 1; d.numoutputbuffers = 1;
d.read = captureRead;
FMOD::DSP* capture = nullptr;
CK(sys->createDSP(&d, &capture));
// Run the capture DSP by connecting it as a (silent) input of the master head DSP.
FMOD::ChannelGroup* master = nullptr; CK(sys->getMasterChannelGroup(&master));
FMOD::DSP* masterHead = nullptr; CK(master->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &masterHead));
CK(masterHead->addInput(capture, nullptr, FMOD_DSPCONNECTION_TYPE_STANDARD));
struct Tap { FMOD::Channel* ch; FMOD::DSP* head; FMOD_DSPCONNECTION* conn; };
std::vector<Tap> taps;
for (int step = 0; step < 30; ++step) {
// Start a new channel and TAP it: add its head DSP as an input to the capture DSP.
FMOD::Channel* ch = nullptr;
CK(sys->playSound(snd, master, false, &ch));
ch->setVolume(0.5f);
FMOD::DSP* head = nullptr; CK(ch->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &head));
FMOD_DSPCONNECTION* conn = nullptr;
CK(capture->addInput(head, &conn, FMOD_DSPCONNECTION_TYPE_STANDARD));
taps.push_back({ ch, head, conn });
// Exercise cleanup: retire the oldest tap.
if (taps.size() > 4) {
Tap t = taps.front(); taps.erase(taps.begin());
// =============== CLEANUP QUESTION (please review) ===============
// Is this order correct/safe? Should disconnectFrom happen BEFORE stop()?
// What is the safe approach if FMOD may have already stopped/stolen the
// channel before I get here (so 'head' could be invalid)?
CK(capture->disconnectFrom(t.head, t.conn));
t.ch->stop();
// ===============================================================
}
CK(sys->update());
printf("step %2d taps=%zu capturePeak=%.3f\n", step, (size_t)taps.size(), g_capturePeak);
Sleep(200);
}
for (auto& t : taps) { capture->disconnectFrom(t.head, t.conn); t.ch->stop(); }
masterHead->disconnectFrom(capture);
capture->release();
snd->release();
sys->release();
return 0;
}