mirror of
https://codeberg.org/armin/rmx-19.git
synced 2026-10-11 16:51:55 +02:00
- Add host-tempo-synced delay: FX screen gains a 4th SYNC row for rhythmic divisions (1/16 .. 1/1) that lock delay time to DAW tempo - Embed rack faceplate background (b2.png) via juce binary data - Add offline dev probe (rmx_probe) for GUI-less MIDI testing - Allow overriding JUCE_ROOT when configuring the build - Write comprehensive README describing the instrument, UI, MIDI, DSP, and build/install steps
121 lines
No EOL
4.4 KiB
C++
121 lines
No EOL
4.4 KiB
C++
// 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;
|
|
} |