Here is the code of the current version, currently lacking of “surround” and parameter interpolation, so do not automate it. Also this may be what a serious audio engineer would consider as harmful. But actually you could use it to make a synth phasing by purpose:
-- ffx Stereo Expander
--
-- missing: surround
-- with additional basic stereo rotator
require "include/protoplug"
local cbFilter = require "include/dsp/cookbook filters"
local width = 1
local phase = 0
local sur = 0
local apmix = 0
local monofreq = 0
local filter
local filterMono
local filterMono2
local swaplr = "off"
local lSample, rSample, panning, panning0
function stereoWiden(LSample, RSample, myWidth)
local mono = (LSample+RSample) * 0.5
local stereo = LSample - RSample
stereo = stereo * myWidth
return mono+stereo, mono-stereo
end
function phaseRotate(in_left, in_right)
local phasepi = phase * math.pi
out_left = (in_left * math.cos(phasepi)) - (in_right * math.sin(phasepi + math.pi));
out_right = (in_left * -math.sin(phasepi)) - (in_right * math.cos(phasepi + math.pi));
return out_left, out_right
end
function surround(in_left, in_right)
out_left = in_left;
out_right = out_right;
return out_left, out_right
end
stereoFx.init()
function stereoFx.Channel:init()
-- create per-channel fields (filters)
filter = cbFilter
{
-- initialize filters with current param values
type = "ap";
f = params[5].getValue()/2; -- 10 ... 20000
gain = 0; -- -30 ... 30
Q = params[6].getValue(); -- 0.1 ... 30
}
filterMono = cbFilter
{
-- initialize filters with current param values
type = "lp";
f = params[9].getValue()/2; -- 10 ... 20000
gain = 0; -- -30 ... 30
Q = 0.2; -- 0.1 ... 30
}
filterMono2 = cbFilter
{
-- initialize filters with current param values
type = "lp";
f = params[9].getValue()/2; -- 10 ... 20000
gain = 0; -- -30 ... 30
Q = 0.2; -- 0.1 ... 30
}
end
function plugin.processBlock(samples, smax)
for i = 0, smax do
samples[0][i], samples[1][i] =
samples[0][i] * (1-panning0),
samples[1][i] * (1+panning0)
;
samples[0][i], samples[1][i] = stereoWiden(samples[0][i], samples[1][i], width)
samples[0][i], samples[1][i] = phaseRotate(samples[0][i], samples[1][i])
samples[0][i], samples[1][i] = surround(samples[0][i], samples[1][i])
if (apmix >= 0) then
samples[0][i], samples[1][i] = samples[0][i], apmix * filter.process(samples[1][i]) + (1-apmix) * samples[1][i]
else
samples[0][i], samples[1][i] = apmix * filter.process(samples[0][i]) + (1-apmix) * samples[0][i], samples[1][i]
end
samples[0][i], samples[1][i] =
samples[0][i] * (1-panning),
samples[1][i] * (1+panning)
;
if (monofreq >= 20) then
lSample, rSample = filterMono.process(samples[0][i]), filterMono2.process(samples[1][i])
samples[0][i], samples[1][i] = lSample-samples[0][i], rSample-samples[1][i]
lSample, rSample = stereoWiden(lSample, rSample, 0)
samples[0][i], samples[1][i] =
samples[0][i]*0.5+lSample+samples[0][i]*0.5,
samples[1][i]*0.5+rSample+samples[1][i]*0.5
end
if (swaplr == "on") then
samples[0][i], samples[1][i] = samples[1][i], samples[0][i]
end
end
end
params = plugin.manageParams {
{
name = "Pre Pan";
min = -100;
max = 100;
default = 0;
changed = function(val) panning0=val/100 end;
},
{
name = "Width %";
min = 0;
max = 250;
default = 100;
changed = function(val) width=val/200 end;
},
{
name = "(Surround)";
min = 0;
max = 100;
default = 0;
changed = function(val) sr=val/100 end;
},
{
name = "Phase";
min = -90;
max = 90;
default = 0;
changed = function(val) phase=(-val)/180 end;
},
{
name = "AP freq";
min = 10;
max = 20000;
default = 440;
changed = function(val) filter.update{f=val} end;
},
{
name = "AP rez";
min = 0.1;
max = 5;
default = 0.1;
changed = function(val) filter.update{Q=val} end;
},
{
name = "AP mix";
min = -100;
max = 100;
default = 0;
changed = function(val) apmix=val/100 end;
},
{
name = "Pan";
min = -100;
max = 100;
default = 0;
changed = function(val) panning=val/100 end;
},
{
name = "Mono freq";
min = 0;
max = 20000;
default = 440;
changed = function(val) monofreq=val;filterMono.update{f=val};filterMono2.update{f=val} end;
},
{
name = "Swap L/R";
type = "list";
values = {"off"; "on"};
default = "off";
changed = function(val) swaplr = val end;
};
}
Paste it into lua protoplug. I guess you also could highpass the side, but this just is magic!
If you carefully setup it up and are using mono checks in between, then I think you can actually get quite good results with this.