mirror of
https://codeberg.org/armin/terion.git
synced 2026-10-11 16:41:53 +02:00
init
This commit is contained in:
parent
7e21684497
commit
5d4134966f
11 changed files with 996 additions and 0 deletions
212
Source/PluginProcessor.cpp
Normal file
212
Source/PluginProcessor.cpp
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
TerionProcessor::TerionProcessor()
|
||||
: AudioProcessor(BusesProperties()
|
||||
.withInput("Input", juce::AudioChannelSet::stereo(), true)
|
||||
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
|
||||
apvts(*this, nullptr, "Parameters", {
|
||||
std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"fadeTime", 1},
|
||||
"Fade Time",
|
||||
juce::NormalisableRange<float>(0.1f, 10.0f, 0.01f, 0.4f),
|
||||
2.0f),
|
||||
std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"sensitivity", 2},
|
||||
"Sensitivity",
|
||||
juce::NormalisableRange<float>(-12.0f, 24.0f, 0.1f, 0.5f),
|
||||
0.0f),
|
||||
std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"dotSize", 3},
|
||||
"Dot Size",
|
||||
juce::NormalisableRange<float>(0.5f, 3.0f, 0.1f, 0.5f),
|
||||
1.0f),
|
||||
std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"smoothing", 4},
|
||||
"Smoothing",
|
||||
juce::NormalisableRange<float>(0.0f, 0.99f, 0.01f, 0.5f),
|
||||
0.0f),
|
||||
std::make_unique<juce::AudioParameterBool>(
|
||||
juce::ParameterID{"freeze", 5},
|
||||
"Freeze",
|
||||
false)
|
||||
})
|
||||
{
|
||||
points.resize(kMaxPoints);
|
||||
}
|
||||
|
||||
TerionProcessor::~TerionProcessor() {}
|
||||
|
||||
void TerionProcessor::prepareToPlay(double, int) {}
|
||||
|
||||
void TerionProcessor::releaseResources() {}
|
||||
|
||||
void TerionProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
|
||||
{
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
|
||||
const int numSamples = buffer.getNumSamples();
|
||||
const float* left = buffer.getReadPointer(0);
|
||||
const float* right = buffer.getNumChannels() > 1 ? buffer.getReadPointer(1) : left;
|
||||
|
||||
const float fadeTime = apvts.getRawParameterValue("fadeTime")->load();
|
||||
const float agePerSample = 1.0f / (fadeTime * static_cast<float>(getSampleRate()));
|
||||
const float blockSize = static_cast<float>(numSamples);
|
||||
const float sensGain = juce::Decibels::decibelsToGain(apvts.getRawParameterValue("sensitivity")->load());
|
||||
const float smoothCoef = apvts.getRawParameterValue("smoothing")->load();
|
||||
const float frozen = apvts.getRawParameterValue("freeze")->load();
|
||||
|
||||
// Reset metrics for this block
|
||||
sumMid = 0.0f;
|
||||
sumSide = 0.0f;
|
||||
sumMidSide = 0.0f;
|
||||
sumMid2 = 0.0f;
|
||||
sumSide2 = 0.0f;
|
||||
peakMid = 0.0f;
|
||||
peakSide = 0.0f;
|
||||
metricsCount = 0;
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
float mid = (left[i] + right[i]) * 0.5f * sensGain;
|
||||
float side = (left[i] - right[i]) * 0.5f * sensGain;
|
||||
|
||||
// Apply smoothing
|
||||
if (smoothCoef > 0.0f)
|
||||
{
|
||||
float alpha = std::exp(-1.0f / (smoothCoef * 0.05f * static_cast<float>(getSampleRate())));
|
||||
midSmoothed += alpha * (mid - midSmoothed);
|
||||
sideSmoothed += alpha * (side - sideSmoothed);
|
||||
mid = midSmoothed;
|
||||
side = sideSmoothed;
|
||||
}
|
||||
|
||||
// Update metrics accumulation
|
||||
updateMetrics(mid, side, left[i], right[i]);
|
||||
|
||||
// Add point if not frozen
|
||||
if (frozen < 0.5f)
|
||||
{
|
||||
addPoint(mid, side, agePerSample);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final metrics for this block
|
||||
if (metricsCount > 0)
|
||||
{
|
||||
float avgMid = sumMid / static_cast<float>(metricsCount);
|
||||
float avgSide = sumSide / static_cast<float>(metricsCount);
|
||||
float avgMidSide = sumMidSide / static_cast<float>(metricsCount);
|
||||
float avgMid2 = sumMid2 / static_cast<float>(metricsCount);
|
||||
float avgSide2 = sumSide2 / static_cast<float>(metricsCount);
|
||||
|
||||
// Correlation (Pearson coefficient)
|
||||
float denom = std::sqrt((avgMid2 - avgMid * avgMid) * (avgSide2 - avgSide * avgSide));
|
||||
metrics.correlation = (denom > 1e-6f) ? (avgMidSide - avgMid * avgSide) / denom : 0.0f;
|
||||
metrics.correlation = juce::jlimit(-1.0f, 1.0f, metrics.correlation);
|
||||
|
||||
// Stereo width (M/S ratio in dB)
|
||||
float midRms = std::sqrt(avgMid2);
|
||||
float sideRms = std::sqrt(avgSide2);
|
||||
metrics.midLevel = juce::Decibels::gainToDecibels(midRms, -100.0f);
|
||||
metrics.sideLevel = juce::Decibels::gainToDecibels(sideRms, -100.0f);
|
||||
metrics.stereoWidth = metrics.sideLevel - metrics.midLevel;
|
||||
|
||||
// Balance (L-R difference)
|
||||
float lRms = std::sqrt((metrics.rmsL * metrics.rmsL)); // Already in dB, need linear
|
||||
float rRms = std::sqrt((metrics.rmsR * metrics.rmsR));
|
||||
metrics.balance = metrics.rmsL - metrics.rmsR;
|
||||
|
||||
// Phase angle (degrees)
|
||||
float phaseRad = std::atan2(sideRms, midRms);
|
||||
metrics.phaseAngle = phaseRad * (180.0f / juce::MathConstants<float>::pi);
|
||||
|
||||
// Crest factor (peak vs RMS)
|
||||
float peakLevel = juce::jmax(peakMid, peakSide);
|
||||
float rmsLevel = std::sqrt((avgMid2 + avgSide2) * 0.5f);
|
||||
metrics.crestFactor = juce::Decibels::gainToDecibels(peakLevel / (rmsLevel + 1e-6f), 0.0f);
|
||||
}
|
||||
|
||||
// Age all points (only if not frozen)
|
||||
if (frozen < 0.5f)
|
||||
{
|
||||
for (auto& p : points)
|
||||
p.age += agePerSample * blockSize;
|
||||
}
|
||||
}
|
||||
|
||||
void TerionProcessor::updateMetrics(float mid, float side, float left, float right)
|
||||
{
|
||||
sumMid += mid;
|
||||
sumSide += side;
|
||||
sumMidSide += mid * side;
|
||||
sumMid2 += mid * mid;
|
||||
sumSide2 += side * side;
|
||||
|
||||
float absMid = std::abs(mid);
|
||||
float absSide = std::abs(side);
|
||||
peakMid = juce::jmax(peakMid, absMid);
|
||||
peakSide = juce::jmax(peakSide, absSide);
|
||||
|
||||
// Peak and RMS for L/R
|
||||
float absL = std::abs(left);
|
||||
float absR = std::abs(right);
|
||||
metrics.peakL = juce::jmax(metrics.peakL, absL);
|
||||
metrics.peakR = juce::jmax(metrics.peakR, absR);
|
||||
|
||||
// RMS accumulation (using running sum of squares)
|
||||
static thread_local float sumL2 = 0.0f;
|
||||
static thread_local float sumR2 = 0.0f;
|
||||
static thread_local int count = 0;
|
||||
|
||||
sumL2 += left * left;
|
||||
sumR2 += right * right;
|
||||
count++;
|
||||
|
||||
if (count >= 1024) // Update RMS every 1024 samples
|
||||
{
|
||||
metrics.rmsL = juce::Decibels::gainToDecibels(std::sqrt(sumL2 / static_cast<float>(count)), -100.0f);
|
||||
metrics.rmsR = juce::Decibels::gainToDecibels(std::sqrt(sumR2 / static_cast<float>(count)), -100.0f);
|
||||
sumL2 = 0.0f;
|
||||
sumR2 = 0.0f;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
metricsCount++;
|
||||
}
|
||||
|
||||
void TerionProcessor::addPoint(float mid, float side, float age)
|
||||
{
|
||||
auto& p = points[writePos];
|
||||
p.mid = mid;
|
||||
p.side = side;
|
||||
p.age = age;
|
||||
|
||||
writePos = (writePos + 1) % kMaxPoints;
|
||||
if (writePos == 0)
|
||||
filled = true;
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* TerionProcessor::createEditor()
|
||||
{
|
||||
return new TerionProcessorEditor(*this);
|
||||
}
|
||||
|
||||
void TerionProcessor::getStateInformation(juce::MemoryBlock& destData)
|
||||
{
|
||||
auto state = apvts.copyState();
|
||||
std::unique_ptr<juce::XmlElement> xml(state.createXml());
|
||||
copyXmlToBinary(*xml, destData);
|
||||
}
|
||||
|
||||
void TerionProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||||
{
|
||||
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
|
||||
if (xml && xml->hasTagName(apvts.state.getType()))
|
||||
apvts.replaceState(juce::ValueTree::fromXml(*xml));
|
||||
}
|
||||
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new TerionProcessor();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue