mirror of
https://codeberg.org/armin/rmx-19.git
synced 2026-10-11 16:51:55 +02:00
Add delay tempo sync, rack faceplate, README, docs
- 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
This commit is contained in:
parent
d63db3af8a
commit
2e870380d4
17 changed files with 1413 additions and 324 deletions
121
Source/Probe.cpp
Normal file
121
Source/Probe.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue