Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions include/element/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,10 @@ class Processor : public juce::ReferenceCountedObject {
*/
GraphNode* getParentGraph() const;

void setInputRMS (int chan, float val);
float getInputRMS (int chan) const { return (chan < inRMS.size()) ? inRMS.getUnchecked (chan)->get() : 0.0f; }
void setOutputRMS (int chan, float val);
float getOutputRMS (int chan) const { return (chan < outRMS.size()) ? outRMS.getUnchecked (chan)->get() : 0.0f; }
void setInputRMS (int chan, float rms, int numSamples);
float getInputRMS (int chan) const { return (chan < inRMS.size()) ? inRMS.getUnchecked (chan)->value.get() : 0.0f; }
void setOutputRMS (int chan, float rms, int numSamples);
float getOutputRMS (int chan) const { return (chan < outRMS.size()) ? outRMS.getUnchecked (chan)->value.get() : 0.0f; }

//=========================================================================
/** Connect this node's output audio to another node's input audio */
Expand Down Expand Up @@ -553,7 +553,18 @@ class Processor : public juce::ReferenceCountedObject {
PatchParameterArray _patches;

juce::Atomic<float> gain, lastGain, inputGain, lastInputGain;
juce::OwnedArray<AtomicValue<float>> inRMS, outRMS;

/** Per-channel RMS meter integrator. Accumulates the block mean-square with a
fixed time constant so the metered level is independent of the host's
buffer size (see updateRMSMeter). */
struct RMSMeter {
float meanSquare = 0.0f; // running mean-square, audio thread only
AtomicValue<float> value; // smoothed linear RMS handed to the UI thread
};
juce::OwnedArray<RMSMeter> inRMS, outRMS;

/** Integrates a block's RMS into the given meter with a fixed time window. */
void updateRMSMeter (RMSMeter& meter, float blockRMS, int numSamples);

juce::Atomic<int> keyRangeLow { 0 };
juce::Atomic<int> keyRangeHigh { 127 };
Expand Down
4 changes: 2 additions & 2 deletions src/engine/graphbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class ProcessBufferOp : public GraphOp
}

for (int i = numAudioIns; --i >= 0;)
node->setInputRMS (i, buffer.getRMSLevel (i, 0, numSamples));
node->setInputRMS (i, buffer.getRMSLevel (i, 0, numSamples), numSamples);

// Begin MIDI filters
{
Expand Down Expand Up @@ -448,7 +448,7 @@ class ProcessBufferOp : public GraphOp
lastMute = muted;

for (int i = 0; i < numAudioOuts; ++i)
node->setOutputRMS (i, buffer.getRMSLevel (i, 0, numSamples));
node->setOutputRMS (i, buffer.getRMSLevel (i, 0, numSamples), numSamples);
}

const ProcessorPtr node;
Expand Down
50 changes: 26 additions & 24 deletions src/engine/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,34 @@ bool Processor::isMidiDeviceNode() const
int Processor::getNumAudioInputs() const { return ports.size (PortType::Audio, true); }
int Processor::getNumAudioOutputs() const { return ports.size (PortType::Audio, false); }

void Processor::setInputRMS (int chan, float val)
void Processor::updateRMSMeter (RMSMeter& meter, float blockRMS, int numSamples)
{
// Integrate the block mean-square with a one-pole filter whose coefficient
// scales with the block length. This yields a fixed integration window
// regardless of the host buffer size, so the metered level (and its
// ballistics) no longer change when the audio device block size changes.
static constexpr double integrationSeconds = 0.3;

if (numSamples <= 0)
return;

const double fs = sampleRate > 0.0 ? sampleRate : 44100.0;
const float ms = blockRMS * blockRMS;
const float alpha = (float) (1.0 - std::exp (-(double) numSamples / (integrationSeconds * fs)));
meter.meanSquare += (ms - meter.meanSquare) * alpha;
meter.value.set (std::sqrt (meter.meanSquare));
}

void Processor::setInputRMS (int chan, float rms, int numSamples)
{
if (chan < inRMS.size())
inRMS.getUnchecked (chan)->set (val);
updateRMSMeter (*inRMS.getUnchecked (chan), rms, numSamples);
}

void Processor::setOutputRMS (int chan, float val)
void Processor::setOutputRMS (int chan, float rms, int numSamples)
{
if (chan < outRMS.size())
outRMS.getUnchecked (chan)->set (val);
updateRMSMeter (*outRMS.getUnchecked (chan), rms, numSamples);
}

bool Processor::isSuspended() const
Expand Down Expand Up @@ -339,19 +357,11 @@ void Processor::prepare (const double newSampleRate,

inRMS.clearQuick (true);
for (int i = 0; i < getNumAudioInputs(); ++i)
{
AtomicValue<float>* avf = new AtomicValue<float>();
avf->set (0);
inRMS.add (avf);
}
inRMS.add (new RMSMeter());

outRMS.clearQuick (true);
for (int i = 0; i < getNumAudioOutputs(); ++i)
{
AtomicValue<float>* avf = new AtomicValue<float>();
avf->set (0);
outRMS.add (avf);
}
outRMS.add (new RMSMeter());
}
}

Expand Down Expand Up @@ -778,18 +788,10 @@ void Processor::setPorts (const PortList& newPorts)
if (isPrepared)
{
for (int i = inRMS.size(); i < getNumAudioInputs(); ++i)
{
auto* avf = new AtomicValue<float>();
avf->set (0);
inRMS.add (avf);
}
inRMS.add (new RMSMeter());

for (int i = outRMS.size(); i < getNumAudioOutputs(); ++i)
{
auto* avf = new AtomicValue<float>();
avf->set (0);
outRMS.add (avf);
}
outRMS.add (new RMSMeter());
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/ui/simplemeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
// Meter level limits (in dB).
#define DIGITAL_METER_MAX_DB (+4.0f)
#define DIGITAL_METER_MIN_DB (-70.0f)
// AES-17 dBFS calibration: the incoming value is a linear RMS amplitude, whose
// level for a sine sits 3.01 dB below its peak. Adding this offset defines 0 dBFS
// as a full-scale sine, so a -20 dBFS sine reads -20 dB rather than -23 dB.
#define DIGITAL_METER_RMS_CAL_DB (+3.0103f)
// The decay rates (magic goes here :).
// - value decay rate (faster)
#define DIGITAL_METER_DECAY_RATE1 (1.0f - 3E-2f)
Expand Down Expand Up @@ -78,7 +82,7 @@ void SimpleMeterValue::paint (Graphics& g)

float dB = DIGITAL_METER_MIN_DB;
if (value > 0.0f)
dB = 20.0f * log10f (value);
dB = 20.0f * log10f (value) + DIGITAL_METER_RMS_CAL_DB;

if (dB < DIGITAL_METER_MIN_DB)
dB = DIGITAL_METER_MIN_DB;
Expand Down
Loading