chore: land janitor round 1 and graft integration
Round 1 findings and fixes, all previously uncommitted: - FXProcessor per-slot DSP history repair (HEAD does not compile without it) - SynthVoice::getModulationValue, editor control-lock guard - ExactCompare.h naming the deliberate exact float comparisons - KnobLookAndFeel replacing the never-installed SerumLookAndFeel - 55 first-party compiler warnings to 0 - AGENT.md renamed to AGENTS.md across Source/ - graft repository context graph and its hooks - JANITOR_TASKS.md and UI_UX_AUDIT.md records
This commit is contained in:
1 parent
5c942cad0c
commit
e4cea300db
63 files changed
+2360
-805
No files matched your search
+121
-51
@@ -9,6 +9,8 @@
|
||||
// ===========================================================================
|
||||
namespace
|
||||
{
|
||||
const int notes[3] = { 48, 60, 67 };
|
||||
|
||||
bool hasNaN (const juce::AudioBuffer<float>& b)
|
||||
{
|
||||
for (int ch = 0; ch < b.getNumChannels(); ++ch)
|
||||
@@ -32,6 +34,36 @@ namespace
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Plays three notes through the current preset, releases them partway, and
|
||||
// reports whether the render stayed finite and non-silent overall.
|
||||
bool runPreset (serum::SerumAltAudioProcessor& processor, const juce::String& label, double maxPeakFloor)
|
||||
{
|
||||
juce::MidiBuffer midi;
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOn (1, n, (juce::uint8) 100), 0);
|
||||
|
||||
juce::AudioBuffer<float> buffer (2, 512);
|
||||
float maxPeak = 0.0f;
|
||||
bool nan = false;
|
||||
|
||||
for (int block = 0; block < 240; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
if (block == 180)
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOff (1, n), 0);
|
||||
nan = nan || hasNaN (buffer);
|
||||
maxPeak = std::max (maxPeak, peak (buffer));
|
||||
}
|
||||
|
||||
const bool ok = ! nan && maxPeak > (float) maxPeakFloor;
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [" << label << "]"
|
||||
<< " peak=" << maxPeak << (nan ? " <NaN!>" : "") << "\n";
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -44,64 +76,18 @@ int main()
|
||||
const int numPresets = processor.getNumPrograms();
|
||||
int failures = 0;
|
||||
|
||||
const int notes[3] = { 48, 60, 67 };
|
||||
|
||||
// Default (init) preset.
|
||||
{
|
||||
juce::MidiBuffer midi;
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOn (1, n, (juce::uint8) 100), 0);
|
||||
|
||||
juce::AudioBuffer<float> buffer (2, 512);
|
||||
float maxPeak = 0.0f;
|
||||
bool nan = false;
|
||||
|
||||
for (int block = 0; block < 240; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
if (block == 180)
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOff (1, n), 0);
|
||||
nan = nan || hasNaN (buffer);
|
||||
maxPeak = std::max (maxPeak, peak (buffer));
|
||||
}
|
||||
|
||||
const bool ok = ! nan && maxPeak > 0.001f;
|
||||
if (! ok) ++failures;
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [Init] peak=" << maxPeak << (nan ? " <NaN!>" : "") << "\n";
|
||||
}
|
||||
if (! runPreset (processor, "Init", 0.001))
|
||||
++failures;
|
||||
|
||||
// Every factory preset.
|
||||
for (int preset = 0; preset < numPresets; ++preset)
|
||||
{
|
||||
processor.setCurrentProgram (preset);
|
||||
const juce::String name = processor.getProgramName (preset);
|
||||
|
||||
juce::MidiBuffer midi;
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOn (1, n, (juce::uint8) 100), 0);
|
||||
|
||||
juce::AudioBuffer<float> buffer (2, 512);
|
||||
float maxPeak = 0.0f;
|
||||
bool nan = false;
|
||||
|
||||
for (int block = 0; block < 240; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
if (block == 180)
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOff (1, n), 0);
|
||||
nan = nan || hasNaN (buffer);
|
||||
maxPeak = std::max (maxPeak, peak (buffer));
|
||||
}
|
||||
|
||||
const bool ok = ! nan && maxPeak > 0.001f;
|
||||
if (! ok) ++failures;
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [" << processor.getProgramName (preset) << "]"
|
||||
<< " peak=" << maxPeak << (nan ? " <NaN!>" : "") << "\n";
|
||||
if (! runPreset (processor, name, 0.001))
|
||||
++failures;
|
||||
}
|
||||
|
||||
// RAVE toggle + parameter churn while playing (no crash / NaN).
|
||||
@@ -129,6 +115,90 @@ int main()
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [RAVE toggle / param churn]" << (nan ? " <NaN!>" : "") << "\n";
|
||||
}
|
||||
|
||||
// FX stability: a preset change selects an FX type and its parameters, so
|
||||
// every unit has to survive the extremes of its own parameter range. A unit
|
||||
// that lets its state run away keeps producing inf/NaN, which is heard as
|
||||
// grainy audio that gets no better.
|
||||
{
|
||||
const char* const typeLabels[] = { "Hyper", "Chorus", "Flanger", "Phaser",
|
||||
"Distortion", "EQ", "Compressor", "Delay", "Reverb" };
|
||||
|
||||
for (int type = 1; type <= 9; ++type)
|
||||
{
|
||||
processor.setCurrentProgram (0);
|
||||
auto set = [&processor] (const char* id, float value)
|
||||
{
|
||||
if (auto* p = processor.parameters.getParameter (id))
|
||||
p->setValueNotifyingHost (value);
|
||||
};
|
||||
set ("fx1Type", (float) type / 9.0f);
|
||||
set ("fx1Mix", 1.0f);
|
||||
set ("fx1P1", 1.0f);
|
||||
set ("fx1P2", 1.0f);
|
||||
set ("fx1P3", 1.0f);
|
||||
set ("fx1P4", 1.0f);
|
||||
|
||||
juce::MidiBuffer midi;
|
||||
midi.addEvent (juce::MidiMessage::noteOn (1, 60, (juce::uint8) 100), 0);
|
||||
|
||||
juce::AudioBuffer<float> buffer (2, 512);
|
||||
bool nan = false;
|
||||
for (int block = 0; block < 120; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
nan = nan || hasNaN (buffer);
|
||||
}
|
||||
|
||||
const bool ok = ! nan;
|
||||
if (! ok) ++failures;
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [FX " << typeLabels[type - 1]
|
||||
<< " at full parameter range]" << (nan ? " <NaN!>" : "") << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// A preset change starts from clean DSP state: the tail of the preset being
|
||||
// left behind must not bleed into the one being loaded.
|
||||
{
|
||||
juce::AudioBuffer<float> buffer (2, 512);
|
||||
processor.setCurrentProgram (9); // Glass Bell, reverb mix 0.6
|
||||
|
||||
juce::MidiBuffer midi;
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOn (1, n, (juce::uint8) 100), 0);
|
||||
for (int block = 0; block < 40; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
}
|
||||
for (int n : notes)
|
||||
midi.addEvent (juce::MidiMessage::noteOff (1, n), 0);
|
||||
for (int block = 0; block < 10; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
processor.processBlock (buffer, midi);
|
||||
midi.clear();
|
||||
}
|
||||
const float tail = peak (buffer);
|
||||
|
||||
processor.setCurrentProgram (0); // the change under test
|
||||
float residual = 0.0f;
|
||||
for (int block = 0; block < 2; ++block)
|
||||
{
|
||||
buffer.clear();
|
||||
juce::MidiBuffer empty;
|
||||
processor.processBlock (buffer, empty);
|
||||
residual = std::max (residual, peak (buffer));
|
||||
}
|
||||
|
||||
const bool ok = tail > 0.01f && residual < 1.0e-4f;
|
||||
if (! ok) ++failures;
|
||||
std::cout << (ok ? "PASS" : "FAIL") << " [preset change clears the previous patch]"
|
||||
<< " tail=" << tail << " residual=" << residual << "\n";
|
||||
}
|
||||
|
||||
std::cout << "\n" << (failures == 0 ? "ALL QA CHECKS PASSED" : "QA FAILURES DETECTED")
|
||||
<< " (" << failures << " failures)\n";
|
||||
return failures == 0 ? 0 : 1;
|
||||
|
||||
Reference in new issue
Block a user