mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-11 03:24:10 +00:00
feat(audio): per-slot polarity flip (setPhase) for parallel rigs
Two amps whose circuits invert differently (e.g. a 3-stage vs a 4-stage preamp) partially CANCEL when their parallel branches merge. setPhase(slotId, inverted) flips one slot's polarity: the Oe factor rides the same multiply as postGain in runSlot, is saved/restored with the chain state (phaseInv, only emitted when set), and is exposed through NodeAddon + the audio bridge + preload like the other St-1/St-2 routing calls. Default off = bit-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
68f9bc5fad
commit
d8356d2ada
@@ -2707,6 +2707,20 @@ static Napi::Value SetBranch(const Napi::CallbackInfo& info)
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
// setPhase(slotId, inverted): polarity flip (Ø) — de-phases one amp of a
|
||||
// parallel rig whose circuit inverts relative to the other.
|
||||
static Napi::Value SetPhase(const Napi::CallbackInfo& info)
|
||||
{
|
||||
auto liveEngine = snapshotEngine();
|
||||
if (liveEngine && info.Length() >= 2)
|
||||
{
|
||||
int slotId = info[0].As<Napi::Number>().Int32Value();
|
||||
bool inv = info[1].ToBoolean().Value();
|
||||
liveEngine->getSignalChain().setPhase(slotId, inv);
|
||||
}
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
// setBranchSrc(slotId, 0=both/1=L/2=R): channel a branch reads from the split.
|
||||
static Napi::Value SetBranchSrc(const Napi::CallbackInfo& info)
|
||||
{
|
||||
@@ -3386,6 +3400,8 @@ public:
|
||||
liveEngine->getSignalChain().setPostGain(slotId, (float)(double)slotObj->getProperty("postGain"));
|
||||
if (slotObj->hasProperty("branchSrc"))
|
||||
liveEngine->getSignalChain().setBranchSrc(slotId, (int)slotObj->getProperty("branchSrc"));
|
||||
if (slotObj->hasProperty("phaseInv"))
|
||||
liveEngine->getSignalChain().setPhase(slotId, (bool)slotObj->getProperty("phaseInv"));
|
||||
}
|
||||
|
||||
// Restore processor state (JUCE-format base64; IR/NAM slots also
|
||||
@@ -3660,6 +3676,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
||||
exports.Set("setBranch", Napi::Function::New(env, SetBranch));
|
||||
exports.Set("setPostGain", Napi::Function::New(env, SetPostGain));
|
||||
exports.Set("setBranchSrc", Napi::Function::New(env, SetBranchSrc));
|
||||
exports.Set("setPhase", Napi::Function::New(env, SetPhase));
|
||||
exports.Set("clearChain", Napi::Function::New(env, ClearChain));
|
||||
exports.Set("getChainState", Napi::Function::New(env, GetChainState));
|
||||
exports.Set("openPluginEditor", Napi::Function::New(env, OpenPluginEditor));
|
||||
|
||||
@@ -339,8 +339,11 @@ void SignalChain::processLocked(juce::AudioBuffer<float>& buffer, juce::MidiBuff
|
||||
slotMidi.addEvent(drained[i].msg, 0);
|
||||
invokePlugin(*slot, [&](juce::AudioProcessor& p) { p.processBlock(buf, slotMidi); });
|
||||
applyPan(buf, numSamples, slot->pan);
|
||||
if (slot->postGain != 1.0f)
|
||||
buf.applyGain(0, numSamples, slot->postGain);
|
||||
// Ø (phaseInv) rides the same multiply as the trim: a -1 factor flips
|
||||
// the slot's polarity (de-phases one amp of a parallel rig).
|
||||
const float post = slot->phaseInv ? -slot->postGain : slot->postGain;
|
||||
if (post != 1.0f)
|
||||
buf.applyGain(0, numSamples, post);
|
||||
};
|
||||
|
||||
// Fast path: no parallel branch → plain serial chain. Behaviour is unchanged
|
||||
@@ -676,6 +679,13 @@ void SignalChain::setBranchSrc(int slotId, int src)
|
||||
if (idx >= 0) slots[idx]->branchSrc = juce::jlimit(0, 2, src);
|
||||
}
|
||||
|
||||
void SignalChain::setPhase(int slotId, bool inverted)
|
||||
{
|
||||
const juce::ScopedLock sl(lock);
|
||||
int idx = findSlotIndex(slotId);
|
||||
if (idx >= 0) slots[idx]->phaseInv = inverted;
|
||||
}
|
||||
|
||||
void SignalChain::clear()
|
||||
{
|
||||
// Detach the slots under a BRIEF lock, then destroy them OFF the lock. The
|
||||
@@ -791,6 +801,7 @@ juce::String SignalChain::savePreset() const
|
||||
// Per-slot output trim (loudness leveling). LoadPresetWorker reads this
|
||||
// back, so it must be written here or a save/load round-trip drops it.
|
||||
if (slot->postGain != 1.0f) slotObj->setProperty("postGain", slot->postGain);
|
||||
if (slot->phaseInv) slotObj->setProperty("phaseInv", true);
|
||||
|
||||
// Save processor state as base64
|
||||
auto state = slot->getState();
|
||||
|
||||
@@ -33,6 +33,10 @@ struct ProcessorSlot
|
||||
// Linear output gain applied right after the processor (and pan). Carries
|
||||
// the per-amp loudness trim / per-branch level; 1.0 (default) = no-op.
|
||||
float postGain = 1.0f;
|
||||
// Polarity flip (Ø), applied with postGain. Lets one amp of a parallel rig
|
||||
// be phase-inverted so two amps whose circuits invert differently (e.g. a
|
||||
// 3-stage vs 4-stage preamp) don't cancel when their branches merge.
|
||||
bool phaseInv = false;
|
||||
|
||||
// For VST plugins — their state as base64 for preset save/load
|
||||
juce::MemoryBlock getState() const;
|
||||
@@ -90,6 +94,7 @@ public:
|
||||
void setBranch(int slotId, int branch);
|
||||
void setBranchSrc(int slotId, int src);
|
||||
void setPostGain(int slotId, float gain);
|
||||
void setPhase(int slotId, bool inverted); // polarity flip (Ø), see slot.phaseInv
|
||||
void clear();
|
||||
|
||||
// Info
|
||||
|
||||
@@ -1220,6 +1220,9 @@ export function initAudioBridge(): void {
|
||||
ipcMain.handle('audio:setBranchSrc', (_event, slotId: number, src: number) => {
|
||||
audio?.setBranchSrc?.(slotId, src);
|
||||
});
|
||||
ipcMain.handle('audio:setPhase', (_event, slotId: number, inverted: boolean) => {
|
||||
audio?.setPhase?.(slotId, inverted);
|
||||
});
|
||||
|
||||
ipcMain.handle('audio:clearChain', () => {
|
||||
audio?.clearChain();
|
||||
|
||||
@@ -436,6 +436,7 @@ const feedBackDesktopApi = {
|
||||
setBranch: (slotId: number, branch: number) => ipcRenderer.invoke('audio:setBranch', slotId, branch),
|
||||
setBranchSrc: (slotId: number, src: number) => ipcRenderer.invoke('audio:setBranchSrc', slotId, src),
|
||||
setPostGain: (slotId: number, gain: number) => ipcRenderer.invoke('audio:setPostGain', slotId, gain),
|
||||
setPhase: (slotId: number, inverted: boolean) => ipcRenderer.invoke('audio:setPhase', slotId, inverted),
|
||||
clearChain: () => ipcRenderer.invoke('audio:clearChain'),
|
||||
getChainState: () => ipcRenderer.invoke('audio:getChainState'),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user