refactor(source): move every implementation into its module directory
- 36 files move into the module whose AGENTS.md already claimed them: Engine/, Synth/, Modulation/, Params/, Plugin/, GUI/, EffectUnits/, Resources/ - Source/engine, modulation, params, plugin and synth become Engine, Modulation, Params, Plugin and Synth, matching the directories that already hold code - ExactCompare.h stays at the Source/ root as the one shared header - SERUMALT_INCLUDE_DIRS adds one include directory per module, so the 31 parent-relative includes become plain names with no path prefix - the module AGENTS.md files list owned files instead of describing a move that had not happened, and README.md and AGENTS.md describe the real tree Verified: the harness passes CTest, the Linux VST3 and Standalone link, the Windows cross-build passes its module checks, each configure exposes only its own targets, no first-party warning line appears, and the generated Windows manifest is unchanged.
This commit is contained in:
1 parent
facd5c5b5e
commit
4b0db50271
70 files changed
+115
-111
No files matched your search
@@ -0,0 +1,31 @@
|
||||
# modulation module
|
||||
|
||||
## Owned files
|
||||
|
||||
- ModulationMatrix.h, ModulationMatrix.cpp
|
||||
- MacroControls.h, MacroControls.cpp
|
||||
- RAVEButton.h, RAVEButton.cpp, which define `RaveController`
|
||||
|
||||
## Rules
|
||||
|
||||
- Use `bool` return codes for bounded inserts (`addConnection`, `addAssignment`).
|
||||
- Require a cap on connection and assignment counts (`kMaxConnections`, `kMaxAssignments`).
|
||||
- Serialise and restore state through `juce::ValueTree` so presets survive save and load.
|
||||
- Never accept a `ModTarget::None` connection; reject it and return false.
|
||||
- RaveController applies a non-destructive boost to render settings. It does not write APVTS parameters or keep a restoration snapshot.
|
||||
- Keep the existing RAVEButton filenames unless a rename is explicitly in scope.
|
||||
|
||||
## GUI/audio state
|
||||
|
||||
Hold `Engine::getControlLock()` while reading or editing the control-side matrix,
|
||||
macro assignments, or LFO shapes. The audio thread tries that lock once per block
|
||||
and copies into reserved audio-side storage. If the lock is busy, it keeps the
|
||||
previous settings and continues rendering. Never iterate the control-side vectors
|
||||
from the render path.
|
||||
|
||||
Master and FX-mix modulation use global sources directly. Per-voice sources for
|
||||
those destinations come from the newest active voice.
|
||||
|
||||
If `addConnection` or `addAssignment` returns false, handle the failed insert.
|
||||
|
||||
This file refines the repository-root AGENTS.md for the related sources.
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "MacroControls.h"
|
||||
#include "ExactCompare.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
bool MacroControls::addAssignment (int macro, ModTarget target, float depth)
|
||||
{
|
||||
if (macro < 0 || macro >= kNumMacros
|
||||
|| (int) target < 0 || (int) target >= kNumModTargets
|
||||
|| ! std::isfinite (depth) || (int) assignments[(size_t) macro].size() >= kMaxAssignments)
|
||||
return false;
|
||||
assignments[(size_t) macro].push_back ({ target, juce::jlimit (-1.0f, 1.0f, depth) });
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroControls::removeAssignment (int macro, int index)
|
||||
{
|
||||
macro = juce::jlimit (0, kNumMacros - 1, macro);
|
||||
auto& vec = assignments[(size_t) macro];
|
||||
if (index >= 0 && index < (int) vec.size())
|
||||
vec.erase (vec.begin() + index);
|
||||
}
|
||||
|
||||
void MacroControls::clear()
|
||||
{
|
||||
for (auto& a : assignments)
|
||||
a.clear();
|
||||
}
|
||||
|
||||
juce::String MacroControls::macroName (int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return "Energy";
|
||||
case 1: return "Width";
|
||||
case 2: return "Drive";
|
||||
case 3: return "Atmosphere";
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
juce::ValueTree MacroControls::toValueTree() const
|
||||
{
|
||||
MacroControls validated;
|
||||
for (int m = 0; m < kNumMacros; ++m)
|
||||
for (const auto& a : assignments[(size_t) m])
|
||||
{
|
||||
if ((int) validated.assignments[(size_t) m].size() >= kMaxAssignments)
|
||||
break;
|
||||
if (! validated.addAssignment (m, a.target, a.depth))
|
||||
continue;
|
||||
}
|
||||
juce::ValueTree tree ("MACROS");
|
||||
for (int m = 0; m < kNumMacros; ++m)
|
||||
{
|
||||
juce::ValueTree mac ("MACRO");
|
||||
mac.setProperty ("index", m, nullptr);
|
||||
for (const auto& a : validated.assignments[(size_t) m])
|
||||
{
|
||||
juce::ValueTree asg ("ASSIGN");
|
||||
asg.setProperty ("target", modTargetToString (a.target), nullptr);
|
||||
asg.setProperty ("depth", a.depth, nullptr);
|
||||
mac.appendChild (asg, nullptr);
|
||||
}
|
||||
tree.appendChild (mac, nullptr);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
void MacroControls::fromValueTree (const juce::ValueTree& tree)
|
||||
{
|
||||
clear();
|
||||
if (! tree.hasType ("MACROS"))
|
||||
return;
|
||||
|
||||
for (const auto& mac : tree)
|
||||
{
|
||||
if (! mac.hasType ("MACRO"))
|
||||
continue;
|
||||
const double savedIndex = (double) mac.getProperty ("index", -1);
|
||||
if (! std::isfinite (savedIndex) || savedIndex < 0.0 || savedIndex >= kNumMacros
|
||||
|| ! isWholeNumber (savedIndex))
|
||||
continue;
|
||||
const int index = (int) savedIndex;
|
||||
for (const auto& asg : mac)
|
||||
{
|
||||
if ((int) assignments[(size_t) index].size() >= kMaxAssignments)
|
||||
break;
|
||||
if (! asg.hasType ("ASSIGN"))
|
||||
continue;
|
||||
if (! addAssignment (index,
|
||||
modTargetFromString (asg.getProperty ("target").toString()),
|
||||
(float) asg.getProperty ("depth", 0.0)))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serum
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "Params.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
// ===========================================================================
|
||||
// One assignable destination for a macro knob.
|
||||
// ===========================================================================
|
||||
struct MacroAssignment
|
||||
{
|
||||
ModTarget target = ModTarget::None;
|
||||
float depth = 0.0f;
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// Four macros (Energy, Width, Drive, Atmosphere). Macro *values* are APVTS
|
||||
// parameters; this class stores the assignable destinations and serialises
|
||||
// them with the preset.
|
||||
// ===========================================================================
|
||||
class MacroControls
|
||||
{
|
||||
public:
|
||||
static constexpr int kMaxAssignments = 8;
|
||||
|
||||
std::array<std::vector<MacroAssignment>, kNumMacros> assignments;
|
||||
|
||||
bool addAssignment (int macro, ModTarget target, float depth);
|
||||
void removeAssignment (int macro, int index);
|
||||
void clear();
|
||||
|
||||
juce::ValueTree toValueTree() const;
|
||||
void fromValueTree (const juce::ValueTree& tree);
|
||||
|
||||
static juce::String macroName (int index);
|
||||
};
|
||||
|
||||
} // namespace serum
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "ModulationMatrix.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
bool ModulationMatrix::addConnection (ModSource source, ModTarget target, float depth, bool bipolar)
|
||||
{
|
||||
if ((int) source < 0 || (int) source >= kNumModSources
|
||||
|| (int) target < 0 || (int) target >= kNumModTargets
|
||||
|| ! std::isfinite (depth) || (int) connections.size() >= kMaxConnections)
|
||||
return false;
|
||||
connections.push_back ({ source, target, juce::jlimit (-1.0f, 1.0f, depth), bipolar });
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModulationMatrix::removeConnection (int index)
|
||||
{
|
||||
if (index >= 0 && index < (int) connections.size())
|
||||
connections.erase (connections.begin() + index);
|
||||
}
|
||||
|
||||
void ModulationMatrix::removeAllWithTarget (ModTarget target)
|
||||
{
|
||||
connections.erase (std::remove_if (connections.begin(), connections.end(),
|
||||
[target] (const ModConnection& c) { return c.target == target; }),
|
||||
connections.end());
|
||||
}
|
||||
|
||||
juce::ValueTree ModulationMatrix::toValueTree() const
|
||||
{
|
||||
ModulationMatrix validated;
|
||||
for (const auto& c : connections)
|
||||
{
|
||||
if (validated.size() >= kMaxConnections)
|
||||
break;
|
||||
if (! validated.addConnection (c.source, c.target, c.depth, c.bipolar))
|
||||
continue;
|
||||
}
|
||||
juce::ValueTree tree ("MODMATRIX");
|
||||
for (const auto& c : validated.connections)
|
||||
{
|
||||
juce::ValueTree con ("CONNECTION");
|
||||
con.setProperty ("source", modSourceToString (c.source), nullptr);
|
||||
con.setProperty ("target", modTargetToString (c.target), nullptr);
|
||||
con.setProperty ("depth", c.depth, nullptr);
|
||||
con.setProperty ("bipolar", c.bipolar, nullptr);
|
||||
tree.appendChild (con, nullptr);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
void ModulationMatrix::fromValueTree (const juce::ValueTree& tree)
|
||||
{
|
||||
connections.clear();
|
||||
if (! tree.hasType ("MODMATRIX"))
|
||||
return;
|
||||
|
||||
for (const auto& con : tree)
|
||||
{
|
||||
if ((int) connections.size() >= kMaxConnections)
|
||||
break;
|
||||
if (! con.hasType ("CONNECTION"))
|
||||
continue;
|
||||
const auto sourceName = con.getProperty ("source").toString();
|
||||
const auto source = modSourceFromString (sourceName);
|
||||
if (sourceName.isEmpty() || modSourceToString (source) != sourceName)
|
||||
continue;
|
||||
if (! addConnection (source,
|
||||
modTargetFromString (con.getProperty ("target").toString()),
|
||||
(float) con.getProperty ("depth", 0.0),
|
||||
(bool) con.getProperty ("bipolar", false)))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serum
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "Params.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
// ===========================================================================
|
||||
// A single modulation connection: source -> target with a static depth and a
|
||||
// bipolar flag. One level of modulation only (no modulation of modulation).
|
||||
// ===========================================================================
|
||||
struct ModConnection
|
||||
{
|
||||
ModSource source = ModSource::Lfo1;
|
||||
ModTarget target = ModTarget::None;
|
||||
float depth = 0.0f;
|
||||
bool bipolar = false;
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// Ordered collection of modulation connections. Serialisable to/from a
|
||||
// juce::ValueTree so that connections survive preset save/load.
|
||||
// ===========================================================================
|
||||
class ModulationMatrix
|
||||
{
|
||||
public:
|
||||
static constexpr int kMaxConnections = 32;
|
||||
|
||||
std::vector<ModConnection> connections;
|
||||
|
||||
bool addConnection (ModSource source, ModTarget target, float depth, bool bipolar);
|
||||
void removeConnection (int index);
|
||||
void removeAllWithTarget (ModTarget target);
|
||||
void clear() { connections.clear(); }
|
||||
|
||||
int size() const noexcept { return (int) connections.size(); }
|
||||
|
||||
juce::ValueTree toValueTree() const;
|
||||
void fromValueTree (const juce::ValueTree& tree);
|
||||
};
|
||||
|
||||
} // namespace serum
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "RAVEButton.h"
|
||||
#include "SynthVoice.h"
|
||||
#include "FXProcessor.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
void RaveController::apply (RenderContext& context, FxSlotParams* slots, int numSlots) noexcept
|
||||
{
|
||||
// Boost the static rendering parameters.
|
||||
context.oscA.unison = juce::jlimit (8, kMaxUnison, context.oscA.unison);
|
||||
context.oscB.unison = juce::jlimit (8, kMaxUnison, context.oscB.unison);
|
||||
context.oscA.spread = 1.0f;
|
||||
context.oscB.spread = 1.0f;
|
||||
context.oscA.detune = 1.0f;
|
||||
context.oscB.detune = 0.7f;
|
||||
|
||||
// Apply drive boosts.
|
||||
context.filters.f1Drive = 0.6f;
|
||||
context.filters.f2Drive = 0.6f;
|
||||
|
||||
// Boost FX-specific params (Hyper intensity / Reverb mix).
|
||||
if (slots == nullptr)
|
||||
return;
|
||||
for (int i = 0; i < juce::jlimit (0, kNumFxSlots, numSlots); ++i)
|
||||
{
|
||||
auto& slot = slots[i];
|
||||
if (slot.type == (int) FxType::Hyper)
|
||||
slot.p[0] = 1.0f; // OTT intensity 100%
|
||||
else if (slot.type == (int) FxType::Reverb)
|
||||
{
|
||||
const float mix = std::isfinite (slot.mix) ? slot.mix : 0.0f;
|
||||
slot.mix = juce::jlimit (0.0f, 1.0f, mix + 0.4f); // reverb send +6dB-ish
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serum
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
struct RenderContext;
|
||||
struct FxSlotParams;
|
||||
|
||||
// ===========================================================================
|
||||
// RAVE — non-destructive "make it huge" control. While enabled, rendering
|
||||
// boosts unison, width, drive, OTT and reverb in the current block's snapshots;
|
||||
// the underlying parameters remain unchanged when toggling on or off.
|
||||
// ===========================================================================
|
||||
class RaveController
|
||||
{
|
||||
public:
|
||||
static void apply (RenderContext& context, FxSlotParams* slots, int numSlots) noexcept;
|
||||
};
|
||||
|
||||
} // namespace serum
|
||||
Reference in new issue
Block a user