Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cmake_minimum_required(VERSION 3.22)
# cmake --build build --config Release
# ============================================================================

project(Anamorph VERSION 0.6.4 LANGUAGES C CXX)
project(Anamorph VERSION 0.6.5 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down
15 changes: 10 additions & 5 deletions src/PluginEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ AnamorphAudioProcessorEditor::AnamorphAudioProcessorEditor (AnamorphAudioProcess
presetPrev.setTooltip ("Previous preset");
presetNext.setTooltip ("Next preset");
presetName.setTooltip ("Presets"); // short, no period (#12)
presetPrev.onClick = [this] { processor.getPresets().step (-1); knobSweepTime = 0.45; refreshPresetDisplay(); };
presetNext.onClick = [this] { processor.getPresets().step (+1); knobSweepTime = 0.45; refreshPresetDisplay(); };
presetPrev.onClick = [this] { processor.getEngine().requestDuck(); processor.getPresets().step (-1); knobSweepTime = 0.45; refreshPresetDisplay(); };
presetNext.onClick = [this] { processor.getEngine().requestDuck(); processor.getPresets().step (+1); knobSweepTime = 0.45; refreshPresetDisplay(); };
presetName.onClick = [this] { showPresetMenu(); };
addAndMakeVisible (presetPrev);
addAndMakeVisible (presetNext);
Expand Down Expand Up @@ -1143,6 +1143,7 @@ void AnamorphAudioProcessorEditor::showPresetMenu()
if (r == 0) return;
if (r == 10001) { showSavePreset (true); return; }
if (r == 10002) { showLoadPreset(); return; }
processor.getEngine().requestDuck(); // mask the level jump (#1, 0.6.4)
processor.getPresets().load (r - 1);
knobSweepTime = 0.45; // sweep the knobs to the preset (#3)
refreshPresetDisplay();
Expand All @@ -1160,10 +1161,14 @@ void AnamorphAudioProcessorEditor::showLoadPreset()
[this] (const juce::FileChooser& fc)
{
const auto file = fc.getResult();
if (file.existsAsFile() && processor.getPresets().loadFile (file))
if (file.existsAsFile())
{
knobSweepTime = 0.45; // sweep the knobs to the preset (#3)
refreshPresetDisplay();
processor.getEngine().requestDuck(); // mask the level jump (#1, 0.6.4)
if (processor.getPresets().loadFile (file))
{
knobSweepTime = 0.45; // sweep the knobs to the preset (#3)
refreshPresetDisplay();
}
}
});
}
Expand Down
3 changes: 3 additions & 0 deletions src/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ void AnamorphAudioProcessor::undo()
{
auto& st = abUndo[abActive];
if (st.undo.empty()) return;
engine.requestDuck(); // mask the level jump (#1, 0.6.4)
st.redo.push_back (currentStateSet());
committed = st.undo.back(); st.undo.pop_back();
applyStateSet (committed);
Expand All @@ -203,6 +204,7 @@ void AnamorphAudioProcessor::redo()
{
auto& st = abUndo[abActive];
if (st.redo.empty()) return;
engine.requestDuck(); // mask the level jump (#1, 0.6.4)
st.undo.push_back (currentStateSet());
committed = st.redo.back(); st.redo.pop_back();
applyStateSet (committed);
Expand Down Expand Up @@ -235,6 +237,7 @@ void AnamorphAudioProcessor::abSwitchTo (int slot)
{
abEnsureInit();
if (slot == abActive) return;
engine.requestDuck(); // mask the level jump (#1, 0.6.4)
abSlot[abActive] = currentStateSet(); // store the whole state set in the old slot
abMatchGain[abActive] = engine.getMatchGainDb(); // remember this slot's match (#23)
abActive = slot;
Expand Down
11 changes: 8 additions & 3 deletions src/dsp/AnamorphEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,13 @@ void AnamorphEngine::copyContinuous (EngineParameters& dst, const EngineParamete

void AnamorphEngine::setParameters (const EngineParameters& np) noexcept
{
// A bulk swap (A/B, preset, undo) asks for a masking duck even when only
// continuous controls move, so a big level jump can't pop (#1, 0.6.4).
const bool forceDuck = duckRequest.exchange (0, std::memory_order_relaxed) != 0;

if (switchState == SwitchState::Normal)
{
if (discreteDiffers (np, p))
if (forceDuck || discreteDiffers (np, p))
{
pendingP = np;
pendingAlgoReset = (np.algorithm != p.algorithm);
Expand All @@ -205,9 +209,10 @@ void AnamorphEngine::setParameters (const EngineParameters& np) noexcept
{
// Mid-duck: remember the latest target and keep continuous controls live.
pendingP = np;
if (switchState == SwitchState::FadeIn && discreteDiffers (np, p))
if (switchState == SwitchState::FadeIn && (forceDuck || discreteDiffers (np, p)))
{
// A new discrete change arrived as we were fading back in: duck again.
// A new discrete change (or a forced bulk swap) arrived as we were
// fading back in: duck again.
pendingAlgoReset = (np.algorithm != p.algorithm);
switchState = SwitchState::FadeOut;
}
Expand Down
8 changes: 8 additions & 0 deletions src/dsp/AnamorphEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class AnamorphEngine
// remembered match value on a slot switch; consumed on the audio thread.
void injectMatchGainDb (float db) noexcept { matchInject.store (db, std::memory_order_relaxed); }

// Request a short raised-cosine output duck around the NEXT parameter swap,
// even if it's continuous-only (#1, 0.6.4 feedback): an A/B or preset jump can
// move many params at once and pop, so the wrapper asks for the same masking
// duck the engine already uses for discrete switches. Call BEFORE changing the
// parameters so the duck is already running when the new values arrive.
void requestDuck() noexcept { duckRequest.store (1, std::memory_order_relaxed); }

private:
void updateDerived();
void applyInputConditioning (float* L, float* R, int n) noexcept;
Expand Down Expand Up @@ -130,6 +137,7 @@ class AnamorphEngine

static constexpr float kNoInject = -1000.0f;
std::atomic<float> matchInject { kNoInject }; // pending per-slot match restore (#23)
std::atomic<int> duckRequest { 0 }; // force a duck around a bulk param swap (#1, 0.6.4)

// Dry-path delay (integer) to align dry with wet latency in the mix.
juce::AudioBuffer<float> dryDelayBuffer;
Expand Down
Loading