rmx-19/Source/Probe.cpp

121 lines
4.4 KiB
C++
Raw Normal View History

// Offline probe: feed the real RMX19AudioProcessor MIDI and analyze output.
#include "PluginProcessor.h"
#include <cmath>
#include <cstdio>
#include <vector>
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<float> buf (2, 512);
std::vector<float> 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<float> buf (2, 512);
std::vector<float> peak (totalBlocks, 0.0f);
std::vector<float> 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;
}