-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathcorrelation_meter.jsfx
More file actions
326 lines (264 loc) · 9.14 KB
/
correlation_meter.jsfx
File metadata and controls
326 lines (264 loc) · 9.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
desc: Correlation Meter
version: 1.8.3
author: chokehold
tags: utility pan panorama stereo channel correlation phase scope meter
link: https://github.com/chkhld/jsfx/
screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/correlation_meter.png
about:
# 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.
// ----------------------------------------------------------------------------
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;
// 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 = 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 / see-through 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();