Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
333 lines (276 sloc) 9.52 KB
// Correlation Meter
//
// Stereo phase relation scope, Pearson product-moment correlation. Tells
// you how much of a difference there is between L/R channels of an audio
// signal.
//
// May not seem very important, but all over the world there are devices
// that will sum stereo mixes into mono signals, be it radio, club P.A. or
// mobile phone. If the L/R phase relation is too much out of balance, the
// best stereo mix will fall apart and sound like amateur's work once it's
// summed to mono. That's why it's good to keep an eye on phase correlation.
//
// Reading the meter is pretty easy. If no signal is present, there's no bar.
// As soon as a signal is present, the meter will display a green bar which
// informs you of the phase correlation.
//
// +1 is the optimal goal, this means both channels are in absolute harmony.
// 0 means everything is OK, but don't let the phases drift further apart.
// -1 is the worst case scenario, it means the channels are totally out of
// phase and would 100% cancel each other out when summed down to mono.
//
// I added colour coding to the moving indicator; red bad, green good.
//
// That's really all there's to it.
//
// author: chokehold
// url: https://github.com/chkhld/jsfx/
// tags: utility pan panorama stereo channel correlation phase scope meter
//
desc: Correlation Meter
options: no_meter
// Making this a hidden parameter because it add
// any worthwhile control or functionality. It's
// still available as a parameter, so you could
// turn it into a track control, if you must...
slider1:1.0<0.5,2.0,0.01>-Speed
in_pin:left input
in_pin:right input
out_pin:none
@init
// Level below which the indicator stops displaying
// -96 dBfs = 10.0^(-96.0/20.0) = 0.00001584893192
// -80 dBfs = 10.0^(-80.0/20.0) = 0.0001
//
signalThreshold = 0.0001;
// Sample memory
history = 2000;
// Sample history statistics and properties
historyLength = 45; // Default: ~1ms Window at 44.1 kHz
historyDouble = 90; // 2x historyLength, used by methods
historyScaler = 0.02222222222; // For mean calculation
// Correlation result related values
correlation = 0.0; // Raw Pearson result
indicatorPosition = 0.0; // Smoothed position
// Used for level detection
absMeanL = 0.0;
absMeanR = 0.0;
// One-pole smooth movement filter coeffs
slowDownA = 1.0;
slowDownB = 0.0;
// Fast replacement for (1.0 / value) expressions
//
function fastReciprocal (value) local (factor)
(
factor = invsqrt(historyLength);
(factor * factor);
);
// One-pole LP filter to, well, slow down the
// indicator's movement.
//
function slowDown (value) local (state)
(
state = (value * slowDownA) + (state * slowDownB);
state;
);
// The correlation detection window will be 1 ms
// in any project with any samplerate.
//
function resizeHistory () local (samplesPerMS)
(
samplesPerMS = ceil(srate * 0.001); // round up +1 for safety
(historyLength != samplesPerMS) ?
(
historyLength = samplesPerMS;
historyDouble = historyLength * 2;
historyScaler = fastReciprocal(historyLength); // 1.0/historyLength
memset(history, 0.0, historyDouble);
);
);
// Move everything in the history back one sample,
// then import the latest incoming samples.
//
function shuffle (newSampleL, newSampleR)
(
memcpy(history, history+1, historyDouble);
history[historyLength-1] = newSampleL;
history[historyDouble-1] = newSampleR;
);
// This will suck your CPU dry if it runs every
// cycle, so that's why there's a countdown loop.
// Recalculating everything once per Millisecond
// is good enough for productive results, since
// it's being low-pass-filtered later anyway.
//
function pearsonProduct () local (position, sumL, sumR, meanL, meanR, devL, devR, sqrMeanL, sqrMeanR, sumAbove, numBelow)
(
rounds += 1;
rounds %= historyLength;
// Every Millisecond
(rounds == 0) ?
(
// Just in case the sample rate's changed
resizeHistory();
// SUM
sumL = 0;
sumR = 0;
position = 0;
while (position < historyLength)
(
sumL += history[position];
sumR += history[position+historyLength];
position += 1;
);
// MEAN
meanL = (sumL * historyScaler);
meanR = (sumR * historyScaler);
absMeanL = abs(meanL);
absMeanr = abs(meanR);
// DEVIATION
sqrMeanL = 0;
sqrMeanR = 0;
position = 0;
while (position < historyLength)
(
sqrMeanL += sqr(history[position] - meanL);
sqrMeanR += sqr(history[position+historyLength] - meanR);
position += 1;
);
devL = sqrt(sqrMeanL * historyScaler);
devR = sqrt(sqrMeanR * historyScaler);
// SUM ABOVE
sumAbove = 0.0;
position = 0;
while (position < historyLength)
(
sumAbove += (history[position] - meanL) * (history[position+historyLength] - meanR);
position += 1;
);
// NUM BELOW
numBelow = historyLength * devL * devR;
// CORRELATION RESULT
//
// The value this entire processor is built around
//
correlation = sumAbove / numBelow;
);
);
// Draws the user interface
//
function drawGfx () local (halfWidth, centerX, lineX, indicatorHistory0, indicatorHistory1, indicatorHistory2, dim)
(
dim = 0.175;
gfx_mode = 1; // additive
gfx_clear = 0; // black
halfWidth = gfx_w/2;
// Colour gray / seethrough white
gfx_set(1, 1, 1, 0.5);
// Paint center 0 line
centerX = halfWidth-1;
gfx_x = centerX;
gfx_y = 0;
gfx_lineto(centerX, gfx_h);
// Only draw indicator bar if signal present
((abs(spl0) > signalThreshold) || (abs(spl1) > signalThreshold)) ?
(
// Previous position -3
(indicatorHistory2 != indicatorPosition) ?
(
indicatorHistory2 >= 0.0 ?
(
gfx_set(1-indicatorHistory2, 1, 0, 0.3);
):(
gfx_set(1, sqr(1+indicatorHistory2), 0, 0.3);
);
lineX = centerX + (indicatorHistory2 * halfWidth) - (7 * indicatorHistory2);
gfx_rect(lineX-8, 0, 16, gfx_h);
gfx_x = lineX-10;
gfx_y = 0;
gfx_blurto(lineX+10 ,gfx_h);
);
// Previous position -2
(indicatorHistory1 != indicatorPosition) ?
(
indicatorHistory1 >= 0.0 ?
(
gfx_set(1-indicatorHistory1, 1, 0, 0.6);
):(
gfx_set(1, sqr(1+indicatorHistory1), 0, 0.6);
);
lineX = centerX + (indicatorHistory0 * halfWidth) - (5 * indicatorHistory0);
gfx_rect(lineX-7, 0, 14, gfx_h);
gfx_x = lineX-9;
gfx_y = 0;
gfx_blurto(lineX+9, gfx_h);
);
// Previous position -1
(indicatorHistory0 != indicatorPosition) ?
(
indicatorHistory0 >= 0.0 ?
(
gfx_set(1-indicatorHistory0, 1, 0, 0.8);
):(
gfx_set(1, sqr(1+indicatorHistory0), 0, 0.8 );
);
lineX = centerX + (indicatorHistory0 * halfWidth) - (4 * indicatorHistory0);
gfx_rect(lineX-6, 0, 12, gfx_h);
gfx_x = lineX-8;
gfx_y = 0;
gfx_blurto(lineX+8, gfx_h);
);
// Dim background
gfx_muladdrect(0,0,gfx_w,gfx_h, dim, dim, dim);
// Current position
indicatorPosition >= 0.0 ?
( // Fade between green and yellow
gfx_set(1-indicatorPosition, 1, 0, 1.0);
):(
// Fade between yellow and red
gfx_set(1, sqr(1+indicatorPosition), 0, 1.0);
);
lineX = centerX + (indicatorPosition * halfWidth) - (4 * indicatorPosition) + 1;
gfx_rect(lineX-5, 0, 10, gfx_h);
);
// Paint white -1/+1 correlation numbers at top
gfx_set(1, 1, 1, 0.9);
gfx_y = 5;
gfx_x = 10;
gfx_drawnumber(-1,0);
gfx_x = halfWidth-4;
gfx_drawnumber(0,0);
gfx_x = gfx_w-24;
gfx_drawstr("+1");
// Paint gray 0°-180° phase scale numbers at bottom.
// As they're flipped against usual phase scopes and
// not the prime intention of this plugin, they will
// be rendered slightly darker/more transparent.
gfx_set(1, 1, 1, 0.25);
gfx_y = gfx_h-12;
gfx_x = 10;
gfx_drawstr("180 deg");
gfx_x = halfWidth-22;
gfx_drawstr("90 deg");
gfx_x = gfx_w-48;
gfx_drawstr("0 deg");
// Shuffle indicator position history -- don't scale these!
indicatorHistory2 = indicatorHistory1;
indicatorHistory1 = indicatorHistory0;
indicatorHistory0 = indicatorPosition;
);
// Still part of the @init section
resizeHistory();
@slider
// Softens the curve of this parameter a bit
speed = sqr(sqr(0.08 * slider1)); // 0.08 = indicatorSpeed
slowDownA = speed;
slowDownB = 1.0 - speed;
@sample
// History samples needs to be shuffled all the time,
// even if the Pearson product isn't calculated.
shuffle(spl0, spl1);
// Calculates the L/R correlation value
pearsonProduct();
// Raw correlation result slowed down for smooth display
indicatorPosition = slowDown(correlation);
@gfx 600 20
drawGfx();
You can’t perform that action at this time.