// Offline probe: feed the real RMX19AudioProcessor MIDI and analyze output. #include "PluginProcessor.h" #include #include #include static int runScenario (int patch, bool holdG, int noteOffCAtBlock, long* cutAt, bool hqOn = false) { RMX19AudioProcessor proc; proc.setRateAndBufferSizeDetails (48000.0, 512); proc.prepareToPlay (48000.0, 512); proc.selectPatch (patch); if (hqOn) if (auto* h = proc.apvts.getParameter ("hq")) h->setValue (1.0f); const int C = 60, G = 67; const int totalBlocks = 240; juce::AudioBuffer buf (2, 512); std::vector peak (totalBlocks, 0.0f); auto noteOn = [](int n, float v){ return juce::MidiMessage::noteOn (1, (juce::uint8) n, v); }; auto noteOff = [](int n){ return juce::MidiMessage::noteOff (1, (juce::uint8) n); }; for (int b = 0; b < totalBlocks; ++b) { juce::MidiBuffer midi; const int cOn = 0, gOn = 40; // C held from block 0, G pressed at block 40 if (b == cOn) midi.addEvent (noteOn (C, 0.8f), 0); if (b == gOn) midi.addEvent (noteOn (G, 0.8f), 0); if (noteOffCAtBlock >= 0 && b == noteOffCAtBlock) midi.addEvent (noteOff (C), 0); buf.clear(); proc.processBlock (buf, midi); float p = 0.0f; for (int s = 0; s < 512; ++s) p = std::max (p, std::abs (buf.getSample (0, s))); peak[b] = p; } proc.releaseResources(); // find the first block after G onset where amplitude collapses for a run long cmax = 250; float gPeak = 0.0f; for (int b = 41; b < totalBlocks; ++b) gPeak = std::max (gPeak, peak[b]); // detect a "cut": after G onset, a gap where peak < 2% of max for >= 8 blocks int run = 0; for (int b = 45; b < totalBlocks; ++b) { if (peak[b] < 0.02f * gPeak + 1e-5f) { if (++run >= 8) { cmax = b - run; break; } } else run = 0; } *cutAt = cmax; printf ("patch %2d | G peak=%.3f cut-at-block=%s\n", patch, gPeak, cmax >= 250 ? "NONE" : std::to_string (cmax).c_str()); return (int) gPeak; } int main() { long cut = -1; // patch 18 Strings Ens.: C held, G pressed -> does G survive? runScenario (18, true, -1, &cut); // same but release C while G is held runScenario (18, true, 80, &cut); // sanity: a working patch runScenario (1, true, -1, &cut); runScenario (4, true, -1, &cut); runScenario (18, true, -1, &cut, /*hqOn*/ true); // cover more held keys after C+G while they are held (fills all 16 voices) { RMX19AudioProcessor proc; proc.setRateAndBufferSizeDetails (48000.0, 512); proc.prepareToPlay (48000.0, 512); proc.selectPatch (18); const int totalBlocks = 240; juce::AudioBuffer buf (2, 512); std::vector peak (totalBlocks, 0.0f); std::vector perBlock (totalBlocks, 1.0f); for (int b = 0; b < totalBlocks; ++b) { juce::MidiBuffer midi; if (b == 0) midi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0); if (b == 40) midi.addEvent (juce::MidiMessage::noteOn (1, 67, 0.8f), 0); // after G, spread another 14 held notes so all 16 voices are busy if (b >= 45 && b < 59) midi.addEvent (juce::MidiMessage::noteOn (1, (juce::uint8)(36 + (b - 45)), 0.8f), 0); buf.clear(); proc.processBlock (buf, midi); float p = 0.0f; for (int s = 0; s < 512; ++s) p = std::max (p, std::abs (buf.getSample (0, s))); peak[b] = p; } // sustain = peak never collapses below 5% of the pre-collapse max for a long stretch float m50 = 0.0f; for (int b = 41; b < 120; ++b) m50 = std::max (m50, peak[b]); int lowRun = 0, maxLowRun = 0; for (int b = 60; b < totalBlocks; ++b) { if (peak[b] < 0.05f * m50) { ++lowRun; maxLowRun = std::max (maxLowRun, lowRun); } else lowRun = 0; } printf ("16-voice fill: block20max=%.3f maxSilentRun=%d blocks %s\n", m50, maxLowRun, maxLowRun >= 12 ? "<- CUT detected" : "sustained"); proc.releaseResources(); } return 0; }