|
|
|
06-25-2019, 03:50 AM
|
#41
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Hi -
I've found a problem with this version (stupidly, I didn't keep the original code as a copy, so I can't check whether it was the same with that).
I have a project with just two instances of AutoMix, one on each track obviously.
If I'm playing the project normally, it all seems to work ok. But if I click elsewhere in the timeline to play from a different point without stopping for a second or two in between, then it goes all odd - the levels drop hugely and it sounds "gated" somehow?
Can you help?
Andy
|
|
|
06-25-2019, 05:38 PM
|
#42
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
v1.1 revert
Quote:
Originally Posted by andyp24
Hi -
I've found a problem with this version (stupidly, I didn't keep the original code as a copy, so I can't check whether it was the same with that).
I have a project with just two instances of AutoMix, one on each track obviously.
If I'm playing the project normally, it all seems to work ok. But if I click elsewhere in the timeline to play from a different point without stopping for a second or two in between, then it goes all odd - the levels drop hugely and it sounds "gated" somehow?
Can you help?
Andy
|
Yes. I didn't test that scenario. In my effort to fix the issue when a plugin is bypassed, I created that new bug.
Here's the older version that doesn't have the pause/jump position bug but has the bypass bug. I'll see what I can do about that issue.
Code:
desc:AutoMixer
author: Corey Scogin
version: 1.1
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog:
Removed the linear to dB conversions. All attenuation values calculated directly now.
Clear the buffer when the window changes. Keeping old buffer values would result in unexpected behavior.
Added the freembuf call to indicate how much memory is used.
// Known Bugs:
// -If an instance of a plugin is removed or bypassed, the others do not know and it throws off the mix calculations.
// You may have to close and reopen the project in order to reset the instance count and restore functionality.
// Is there a better way to keep track of the number of plugin instances?
options:gmem=AutoMixer
slider2:300<1,10000,10>RMS Window Length (ms)
slider3:0<0,1,1{Rectangular,Efficient}>RMS Window Type
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
// Options
ext_noinit = 1; // Do not call @init on play else the instance ID gets reset.
// Variables/Properties
gmem[0] += 1; // Create an id for each instance.
// Is there a better way to determine how many instances are running?
thisID = gmem[0]; // note local ID for debug display.
// Global Memory storage descriptions:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
@slider
// Recalculate constants
// Sample window
window = slider2 * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
slider3 == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
gmem[thisID] = rms;
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
// Possible future modifications:
// -Instance grouping
// -Better ID management
Last edited by CoreyScogin; 06-26-2019 at 05:17 PM.
|
|
|
06-25-2019, 10:37 PM
|
#43
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by CoreyScogin
Yes. I didn't test that scenario. In my effort to fix the issue when a plugin is bypassed, I created that new bug.
|
The issue here is that I cannot find a way to track active plugin instances accurately.
If I reset the count on stop, as in version 1.2, the plugin handles bypassed instances better because it resets often, however, pausing or jumping to a time position without stopping throws the instance tracking off and messes up the calculation.
If I only reset tracking on initial instance load as in version 1.1 and one or more instances are bypassed, that also throws off the instance tracking and requires a project reload to clear it up.
Unless someone with more JSFX experience than me can shed some insight on how I might track instances accurately, this plugin may be stuck at one of these two less-than-ideal options.
|
|
|
06-25-2019, 10:42 PM
|
#44
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by andyp24
Is there any way of tweaking the algorithm used for this process to reduce the "not speaking" mics more? In testing, I've found it works pretty well - certainly no noticeable artefacts like you'd get with gating - but for some recordings done in troublesome rooms, I want to attenuate the "off mic" channel by more than it is doing at the moment.
Andy
|
I think this is possible and it should be straightforward to adjust per-instance. I'll try it out when I get a chance, and if it works, post a version to test.
|
|
|
06-25-2019, 11:34 PM
|
#45
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Thanks for the responses Corey, I'll revert to 1.1 for now, as I'm less likely to bypass or remove an instance frequently than to "jump the playhead" around.
I persuaded a mathematician friend of mine to write me a sigmoid (s-shaped) function that I inserted in the code yesterday in place of the linear (rms/sum). Despite never using EEL before, I also managed to add a couple of sliders to control parameters affecting the curve shape, with interesting results! Need to refine it a bit more before it's useful in practice though.
Could I share it here if it produces workable results?
Thanks
Andy
|
|
|
06-26-2019, 05:15 PM
|
#46
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by andyp24
I persuaded a mathematician friend of mine to write me a sigmoid (s-shaped) function that I inserted in the code yesterday in place of the linear (rms/sum). Despite never using EEL before, I also managed to add a couple of sliders to control parameters affecting the curve shape, with interesting results! Need to refine it a bit more before it's useful in practice though.
Could I share it here if it produces workable results?
|
Sure.
It might make sense to add options such as other auto-mixing algorithms or advanced features like you describe. I'll be interested to see what your friend came up with.
|
|
|
06-26-2019, 06:56 PM
|
#47
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by andyp24
I persuaded a mathematician friend of mine to write me a sigmoid (s-shaped) function that I inserted in the code yesterday in place of the linear (rms/sum). Despite never using EEL before, I also managed to add a couple of sliders to control parameters affecting the curve shape, with interesting results! Need to refine it a bit more before it's useful in practice though.
Andy
|
Here's a version with a linear attenuation multiplier.
In practice, this works well for prioritizing one channel over another but when multiple channels have the multiplier set, the overall volume is less.
Maybe the "S" curves work better.
Code:
desc:AutoMixer
author: Corey Scogin
version: AttenuationMultiplierBeta
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog: Added attenuation multiplier
// Known Bugs:
// - Playback must be stopped and started if an instance is bypassed, added, or removed.
// or if playback is paused. The pause issue is due to a bug in the play_state variable.
// Is there a better way to keep track of plugin instances?
options:gmem=AutoMixer
slider2:WindowLength=300.0<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
slider4:AttMult=1.0<0,10,0.1>Attenuation Multiplier
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
// Variables/Properties
gmem[0] += 1; // Create an id for each instance.
// Is there a better way to determine how many instances are running?
thisID = gmem[0]; // store local ID.
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
// Reset the instance count on stop, pause, or error
play_state < 1 || play_state == 2 || play_state == 6 ? (
gmem[0] = 0;
);
count = gmem[0]; // For debug display
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
gmem[thisID] = rms;
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuation Multiplier
attResult = attSpl ^ AttMult;
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attResult;
i += 1;
);
// Possible future modifications:
// -Instance grouping
|
|
|
06-27-2019, 08:04 AM
|
#48
|
Human being with feelings
Join Date: Jul 2009
Posts: 7,570
|
Haven't tried the latest version but just wondering Could this work better if there was 1 plugin that goes at top of a folder track that does the mixing and 1 plugin that goes at the end of each child track fx chain to send the level?
|
|
|
06-27-2019, 09:13 AM
|
#49
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Hi
I've made a version using the Sigmoid function, with a few extra controllable parameters. It seems to be working OK for me.
How do I post the code on this forum so it appears in its own window like yours does?
Andy
|
|
|
06-27-2019, 02:19 PM
|
#50
|
Human being with feelings
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
|
Quote:
Originally Posted by andyp24
How do I post the code on this forum so it appears in its own window like yours does?
|
https://forum.cockos.com/misc.php?do=bbcode#code
|
|
|
06-27-2019, 03:14 PM
|
#51
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Thanks
|
|
|
06-27-2019, 07:58 PM
|
#52
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by EpicSounds
Haven't tried the latest version but just wondering Could this work better if there was 1 plugin that goes at top of a folder track that does the mixing and 1 plugin that goes at the end of each child track fx chain to send the level?
|
Something like that may work but it would require a specific channel routing. Each child track would need to route to a different parent channel so the audio signals themselves could then be measured, attenuated, then summed back to stereo. It would then just require one plugin on the parent track. It seems just as easy to deal with the stop/play or bypass bugs that currently exist.
|
|
|
06-28-2019, 03:31 AM
|
#53
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Here's my modification using the Sigmoid function. I added some extra controls, as this function is less of a "one size fits all" than the Dan Dugan one.
The Sigmoid function produces an S shaped curve, rather than the linear function of the original. Thus the gain reduction starts more gently as the contribution of a channel to the total reduces, but then slopes away more steeply around the "centre point" where volume is equal between channels. It therefore reduces gain for lower contributing sounds MORE than the original, and therefore can be used to clean up spill a bit more aggressively.
"Slope Steepness" will alter how quickly the gain reduces around that centre point. Having a high value of this may lead to more of a "gated" sound, although unlike traditional gates the reduction is based not on a channel's individual level, but how low it is compared to the rest of the mix.
"Threshold" acts in a similar manner to the threshold of a gate, in that it shifts the "centre point" of the slope left or right (if looking at a traditional plot of an S curve). Therefore it can be used to adjust whether the maximum gain reduction slope happens when the channel is contributing at an equal level to the rest of the programme, or whether this should occur at a higher or lower percentage than this.
"Floor" fixes a maximum gain reduction amount for the channel, so for example it can force the function not to reduce the level of a channel by more than 10dB no matter how little it is contributing as a percentage of the total. Setting this higher may help to prevent gating/pumping type artefacts.
Beware, these three controls are quite interactive - in other words, changing the Threshold and Floor will also have an effect on the Slope Steepness and the other way round.
"Gain Make Up" is simply there because aggressive settings of this function can lead to a substantial loss of level overall, and compensates for this.
Have a play with it, and see what you think. I've based it on Corey's v1.1 of the plugin, which still has the bypass/remove instance problem, but doesn't get confused by jumping the playhead from one time to another without stopping playback, which 1.2 does.
I've used this on a podcast edition yesterday recorded in a reverberant room, and with a bit of tweaking I managed to get a cleaner sounding result than with the original, but without obvious artefacts. I think it's a useful option to have - not better than the classic Dugan algorithm, but useful in addition.
Corey: this is your plugin and code, so please feel free to do what you like with it. If you want it to be a separate JS, that's fine. Or perhaps a "switch" could be built into the GUI to select either the classic Dan Dugan function or the Sigmoid function.
If you would like to credit my efforts, please do so with my forum name andyp24 and also Roger McDermott (my mathematician friend who gave me the sigmoid function).
Code:
desc:AutoMixer
author: Corey Scogin, modification by andyp24 with Roger McDermott
version: 1.1b
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog:
Removed the linear to dB conversions. All attenuation values calculated directly now.
Clear the buffer when the window changes. Keeping old buffer values would result in unexpected behavior.
Added the freembuf call to indicate how much memory is used.
// Known Bugs:
// -If an instance of a plugin is removed or bypassed, the others do not know and it throws off the mix calculations.
// You may have to close and reopen the project in order to reset the instance count and restore functionality.
// Is there a better way to keep track of the number of plugin instances?
options:gmem=AutoMixer
slider2:300<1,10000,10>RMS Window Length (ms)
slider3:0<0,1,1{Rectangular,Efficient}>RMS Window Type
slider4:1<0.1,2,0.05>Slope Steepness
slider5:10<10,5,0.25>Threshold
slider6:-15<-20,-3,1>Floor
slider7:0<0,12,0.5>Make Up Gain dB
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
// Options
ext_noinit = 1; // Do not call @init on play else the instance ID gets reset.
// Variables/Properties
gmem[0] += 1; // Create an id for each instance.
// Is there a better way to determine how many instances are running?
thisID = gmem[0]; // note local ID for debug display.
// Global Memory storage descriptions:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
@slider
// Recalculate constants
// Sample window
window = slider2 * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
slider3 == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
gmem[thisID] = rms;
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
steepness = slider4;
threshold = slider5;
floor = 10^(slider6/20);
makeup = 10^(slider7/20);
sigmoid = floor + makeup*((1-floor)/(1 + exp(-steepness*(threshold*attSpl-5))));
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * sigmoid;
i += 1;
);
// Possible future modifications:
// -Instance grouping
|
|
|
06-29-2019, 11:42 PM
|
#54
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
v1.3 in testing
I have a new version that both resets on stop/play and doesn't get confused when the play position jumps around. Bypassing or removing a plugin instance still requires playback to be stopped and started again but that seems to be a reasonable workaround.
andyp24, I'll consider adding your features once I'm confident in these bug fixes.
I updated the code on the original post and here it is also. If y'all are willing, please try it out and let me know if you run into any issues. I'll update ReaPack after some further testing.
Code:
desc:AutoMixer
author: Corey Scogin
version: 1.3
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog: More instance count tracking improvements. Now supports jumping playback position while playing.
// Known Bugs:
// - Playback must be stopped and started if an instance is bypassed, added, or removed.
options:gmem=AutoMixer
slider2:WindowLength=300<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
ext_noinit = 1.0;
function ResetCount()(
gmem[0] = 0;
isInitialized = 0;
);
function InitializeCount()(
gmem[0] += 1; // Create an id for each instance.
// Is there a better way to determine how many instances are running?
thisID = gmem[0]; // store local ID.
isInitialized = 1;
);
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
// Reset the instance count on stop, pause, or error
play_state < 1 || play_state == 2 || play_state == 6 ? (
ResetCount();
);
isInitialized == 0 && (play_state == 1 || play_state == 5) ? (
InitializeCount();
);
count = gmem[0]; // For debug display
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
gmem[thisID] = rms;
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
// Possible future modifications:
// -Instance grouping
|
|
|
06-30-2019, 06:39 AM
|
#55
|
Human being with feelings
Join Date: Mar 2016
Posts: 1,239
|
Great, will try when I'm back in studio this week.
|
|
|
01-18-2020, 02:08 PM
|
#56
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Only take unmuted instances into account
Hi,
Super job on the automixer!!
I wonder if it would be possible to take only the unmuted tracks into account?
Is it in other words possible to get the state of the mute button of the track in the JSFX?
SOLVED: I just activated the 'do not process muted tracks' in preferences
Last edited by WernerT; 01-18-2020 at 03:03 PM.
Reason: solved
|
|
|
01-18-2020, 03:07 PM
|
#57
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Bug found
The number of instances are not always correct. Some instances get the same thisID, and the count is usually or often less than the actual amount. I suspect that this will result in false gains on the 2 instances with the same ID!
I don't know the background, but i suppose the different tracks are processed in different threats, so two of them could be reading/writing the gmem variable at the same time.
Any ideas?
==>
I solved this one by adding another slider so I can give each instance a unique check value.
This check is stored in the global memory, and verified 1 block scan later. if it is the same then the ID is good, otherwise the ID was double, and we get the next one.
It is not beautifully coded, but it works! Never have a faulty number of instances anymore.
If someone is interested, I can post the code if you tell me how .
Last edited by WernerT; 01-18-2020 at 04:34 PM.
Reason: SOLVED
|
|
|
01-18-2020, 08:34 PM
|
#58
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by WernerT
The number of instances are not always correct. Some instances get the same thisID, and the count is usually or often less than the actual amount. I suspect that this will result in false gains on the 2 instances with the same ID!
I don't know the background, but i suppose the different tracks are processed in different threats, so two of them could be reading/writing the gmem variable at the same time.
Any ideas?
==>
I solved this one by adding another slider so I can give each instance a unique check value.
This check is stored in the global memory, and verified 1 block scan later. if it is the same then the ID is good, otherwise the ID was double, and we get the next one.
It is not beautifully coded, but it works! Never have a faulty number of instances anymore.
If someone is interested, I can post the code if you tell me how .
|
Posting a section of code is just a matter of using CODE tags like:
[CODE] your code here [∕CODE]
Can you post it so we can review and test it?
|
|
|
01-19-2020, 02:25 AM
|
#59
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
unique ID's
Here's the code:
Code:
options:gmem=AutoMixer
slider2:WindowLength=300<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
slider4:RandomValue=5000<1,10000,10>Value should be different for all instances
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
ext_noinit = 1.0;
function ResetCount()(
gmem[0] = 0;
isInitialized = 0;
);
function InitializeCount()(
gmem[0] += 1; // Create an id for each instance.
thisID = gmem[0]; // store local ID.
gmem[thisID] = RandomValue; // Check to see if another channel hacked us
test = RandomValue; // Debug purposes
isInitialized = 1;
);
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
// gmem[n] is also used during startup to check if ID is unique
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
// Reset the instance count on stop, pause, or error
play_state < 1 || play_state == 2 || play_state == 6 ? (
ResetCount();
);
// Check the unique 'random' value and validate
isInitialized == 1 && gmem[thisID] == RandomValue && (play_state == 1 || play_state == 5) ? (
isInitialized = 2; // Verified as unique ID
); // If not validated then get next available ID and check again
isInitialized == 1 && gmem[thisID] != RandomValue && (play_state == 1 || play_state == 5) ? (
InitializeCount(); // ID allready used! get another
);
// Rebuild count when playing or recording starts
isInitialized == 0 && (play_state == 1 || play_state == 5) ? (
InitializeCount();
);
count = gmem[0]; // For debug display
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
isInitialized == 2 ? (
gmem[thisID] = rms;
);
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
It would be great if it were possible to get a random value unique to each instance some other way instead of the extra slider...
Because if you forget to change it, it works like before, and there is no check (because they all check out just fine, even the doubles).
I did not yet have the possibility to actually try it with sound data. I wonder if the 1 block delay during initialisation does something nasty...
Last edited by WernerT; 01-19-2020 at 03:49 PM.
|
|
|
01-19-2020, 02:39 AM
|
#60
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Quote:
Originally Posted by CoreyScogin
Posting a section of code is just a matter of using CODE tags like:
[CODE] your code here [∕CODE]
Can you post it so we can review and test it?
|
The CODE stuff didn't work out as expected.
|
|
|
01-19-2020, 03:38 PM
|
#61
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Quote:
Originally Posted by JamesPeters
Your forward-slash in the [/code] tag seems to not work. Try copying and pasting this one: /
|
Success!
|
|
|
01-20-2020, 08:19 PM
|
#62
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Overlapping ID Check
Quote:
Originally Posted by WernerT
It would be great if it were possible to get a random value unique to each instance some other way instead of the extra slider...
Because if you forget to change it, it works like before, and there is no check (because they all check out just fine, even the doubles).
I did not yet have the possibility to actually try it with sound data. I wonder if the 1 block delay during initialisation does something nasty...
|
Werner. I think you might be on to something with the internal "check value" but I don't think it solves all issues.
I added a section to generate a random number. It's random enough that overlap should never occur.
This seems to solve the overlapping ID issue caused by muting a track, starting playback, then unmuting it.
It doesn't solve issues where the instance count is too high such as when a track is muted or an instance removed during playback. The instance count is crucial in the auto-mix algorithm. Stopping and starting playback resets the count properly. I don't think there's a way to get the mute state of the track in order to trigger the re-count during playback.
Here's the updated code with the random number generator instead of the slider.
Code:
desc:AutoMixer
author: Corey Scogin
version: 1.31
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog: Added check that addresses a bug when unmuting a track during playback. (bug fix by WernerT)
// Known Bugs:
// - Playback must be stopped and started if an instance is muted, bypassed, or removed.
options:gmem=AutoMixer
slider2:WindowLength=300<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
ext_noinit = 1.0;
indexOffset_randomVal = 1000; // Index base for storing unique IDs
randomVal = rand(1000000000); // Generate pseudo-unique ID for this instance
function ResetCount()(
gmem[0] = 0;
isInitialized = 0;
);
function InitializeCount()(
gmem[0] += 1; // Create an id for each instance.
thisID = gmem[0]; // store local index.
// Is there a better way to determine how many instances are running?
gmem[thisID + indexOffset_randomVal] = randomVal; // Set this instance's unique ID
isInitialized = 1;
);
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
// gmem[n + indexOffset_randomVal] stores random number to check if ID is unique
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
// Reset the instance count on stop, pause, or error
play_state < 1 || play_state == 2 || play_state == 6 ? (
ResetCount();
);
// Check the unique 'random' value and validate
isInitialized == 1 && gmem[thisID + indexOffset_randomVal] == randomVal && (play_state == 1 || play_state == 5) ? (
isInitialized = 2; // Verified as unique ID
);
// If not validated then get next available ID and check again
isInitialized == 1 && gmem[thisID + indexOffset_randomVal] != randomVal && (play_state == 1 || play_state == 5) ? (
InitializeCount(); // ID already used! Get another.
);
// Rebuild count when playing or recording starts
isInitialized == 0 && (play_state == 1 || play_state == 5) ? (
InitializeCount();
);
count = gmem[0]; // For debug display
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
isInitialized == 2 ? (
gmem[thisID] = rms;
);
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
// Possible future modifications:
// -Instance grouping
|
|
|
01-22-2020, 03:16 PM
|
#63
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Quote:
Originally Posted by CoreyScogin
Werner. I think you might be on to something with the internal "check value" but I don't think it solves all issues.
I added a section to generate a random number. It's random enough that overlap should never occur.
This seems to solve the overlapping ID issue caused by muting a track, starting playback, then unmuting it.
It doesn't solve issues where the instance count is too high such as when a track is muted or an instance removed during playback. The instance count is crucial in the auto-mix algorithm. Stopping and starting playback resets the count properly. I don't think there's a way to get the mute state of the track in order to trigger the re-count during playback.
|
Hey CoreyScogin,
The problem i encountered has nothing to do with muting or unmuting tracks. Just stopping (creating the reset) and restarting playback several times with some time in between on 15 channels (fixed) gave me a count of anything between 9 to 15 instances. So in some case there were multiple channels (instances) with the same ID! using the same gmem!
This is caused in my opinion by the processing of all the instances in several threats, so they could be processed perfectly in parallel, getting the same ID as a result.
The point of using the gmem[thisID] WITHOUT the random value in the index, to store the check (the random value), is that after one block cycle after starting playback, you can check if no other threat has overwritten the check of the particular instance. If the checkvalue is the same, then that ID is fine, if it is not, that ID is in use by another instance, and the instance where the check was wrong should get another ID. After that change, my count was always 15!
I didn't think about the MUTE story, as I don't need it: I work with snapshots that mute and unmute tracks to create different scenes in a theatre performance. I created now an action that stops playback, gets the next snapshot, moves all unmuted channels on top (so my control surface has all the active channels), unselect all tracks (so one fader doesn't change every selected track), and restarts playback. That action is called by a button on my control surface.
Since I enabled the option "don't process muted tracks", the unmuted tracks are not getting an ID after this action, and all unmuted tracks are refreshed and getting a new ID.
Works as a charm. Of course, I don't use recording or playback, otherwise this wouldn't be possible. But I do use it live!
Since it seems to be impossible to get the state of an instance, or get the state of a track (muted e.g.) in the code, it is indeed impossible to react on a state change without stopping and starting playback. I agree on that one.
Maybe the key developers could do something about that?!
Greetings,
Werner
.... Just did the test with the RAND function instead of the slider: sadly it's no good: out of 10 retry's, I got 6 time's a double ID. It is not so bad as doing no check, but the slider (and thereby setting your own unique 'random' value for each instance) is the only waterproof way i'm afraid .
My solution could (didn't test it yet) give problems with 'popping sound', but that could be fixed by using a third gmem as you did, but then using a FIXED offset:
Code:
desc:AutoMixer
author: Corey Scogin
version: 1.311
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog: - More instance count tracking improvements. Now supports jumping playback position while playing.
- extra cheque on unique ID. User has to set unique ID for each instance!!
// Known Bugs:
// - Playback must be stopped and started if an instance is bypassed, added, or removed.
options:gmem=AutoMixer
slider2:WindowLength=300<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
slider4:randomValue=0<1,200,1>Set to unique value for all instances
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
ext_noinit = 1.0;
function ResetCount()(
gmem[0] = 0;
isInitialized = 0;
);
function InitializeCount()(
gmem[0] += 1; // Create an id for each instance.
thisID = gmem[0]; // store local ID.
gmem[thisID + 500] = RandomValue; // Check to see if another channel hacked us
isInitialized = 1;
);
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
// gmem[n+500] is used during startup to check if ID is unique
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
// Reset the instance count on stop, pause, or error
play_state < 1 || play_state == 2 || play_state == 6 ? (
ResetCount();
);
// Check the unique 'random' value and validate
isInitialized == 1 && gmem[thisID + 500] == RandomValue && (play_state == 1 || play_state == 5) ? (
isInitialized = 2; // Verified as unique ID
); // If not validated then get next available ID and check again
isInitialized == 1 && gmem[thisID + 500] != RandomValue && (play_state == 1 || play_state == 5) ? (
InitializeCount(); // ID allready used! get another
);
// Rebuild count when playing or recording starts
isInitialized == 0 && (play_state == 1 || play_state == 5) ? (
InitializeCount();
);
count = gmem[0]; // For debug display
@sample
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
isInitialized == 2 ? ( // Could be reset to 1 I suppose ...
gmem[thisID] = rms;
);
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
// Possible future modifications:
// -Instance grouping
Last edited by WernerT; 01-22-2020 at 03:58 PM.
Reason: test didn't work out...
|
|
|
01-23-2020, 10:37 PM
|
#64
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
I think it is working
Quote:
Originally Posted by WernerT
The problem i encountered has nothing to do with muting or unmuting tracks. Just stopping (creating the reset) and restarting playback several times with some time in between on 15 channels (fixed) gave me a count of anything between 9 to 15 instances. So in some case there were multiple channels (instances) with the same ID! using the same gmem!
|
I didn't know your test case previously. You had mentioned a muting issue so that's the first thing I thought of to test.
I have now tested and reproduced the issue when running several tracks (32 or more) with a few plugins on each and stopping/starting playback.
Quote:
Originally Posted by WernerT
The point of using the gmem[thisID] WITHOUT the random value in the index, to store the check (the random value), is that after one block cycle after starting playback, you can check if no other threat has overwritten the check of the particular instance. If the checkvalue is the same, then that ID is fine, if it is not, that ID is in use by another instance, and the instance where the check was wrong should get another ID. After that change, my count was always 15!
|
Unless I'm misunderstanding you, my version does the same thing, it just shifts the check value out past index 1000 instead of storing a value on top of the channel's last RMS value at gmem[thisID]. I did that to ensure the random value didn't mess with the automix calculation. The indexOffset_randomVal variable is not random but rather the memory index at which the random values are stored (offset by thisID). It's probably not a big deal because it's corrected within one block but better to be safe.
Quote:
Originally Posted by WernerT
.... Just did the test with the RAND function instead of the slider: sadly it's no good: out of 10 retry's, I got 6 time's a double ID. It is not so bad as doing no check, but the slider (and thereby setting your own unique 'random' value for each instance) is the only waterproof way i'm afraid .
|
Did you reload the project after updating the JSFX code?
After reproducing the issue 2 out of every 5 or so stop/start cycles using the original code without the check value, I was unable to reproduce the issue after 50 stop/starts using the code with the random function. It looks to me like your solution with my random function change is working as it should.
If it truly isn't working for you, maybe our configurations are different yielding different results. I'll keep testing and let you know if I'm able to reproduce the issue using the code with the random function. If you don't mind testing again, I'd appreciate it.
Last edited by CoreyScogin; 01-26-2020 at 07:10 PM.
|
|
|
01-26-2020, 04:54 PM
|
#65
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
worked as a charm :)
Hey,
Sorry for the late reply, but I had a busy weekend!
I just did a show 5 times this weekend with the algorithm, and overall it worked great!
I had one serious issue that needed solving fast: in my earlier post, I mentioned that I activated the option "do not process a muted track" which worked fine during testing, however, during the show, I had sends on the muted channel, and guess what: they were processed, and my instance count went up, making the automixer useless. So I had to reprogram every scene with the option effect chain active in my snapshots and deactivating the effect on every muted channel on every scene. Took me one hour, but after that it worked as a charm. So i am REALLY GRATEFUL for the great algorithm.
Sorry I misinterpreted the gmem thing, it is better to use an offset for the check as you propose!
I wonder what could have gone wrong indeed with the random value. I did not restart the project, so I will be glad to test it again in the coming days, because it would make things easier (now I have to give every instance a unique number on the slider...).
|
|
|
01-29-2020, 05:04 PM
|
#66
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Dynamic instance change possible
Hey,
Sorry to keep you waiting on the test with the rand function, but i'm excited to say that I programmed proof of concept tonight for an automixer that automaticaly detects changes of the amount of instances, and can adapt itself accordingly on the fly without using the stop/start to reset. This means as well that everything keeps working when stopped (while monitoring channels).
It is too early to share, because now I still have to give every instance a different fixed ID number, and I want to get rid of that as well. Furthermore I didn't have time yet to comment it.
The idea is to have a seperate 'AutoMixer CONTROL' effect run on a dummy track (doesn't need to be a dummy track I guess...) that monitors all instances and gives them their 'thisID' when they come alive, or redistributes ID's when one or more dissapear.
This could also fairly easy be adaptable to be able to group instances come to think of it...
Werner
|
|
|
01-30-2020, 06:11 AM
|
#67
|
Human being with feelings
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
|
Here is the code for the automixer:
=========================
Code:
desc:AutoMixer
author: Corey Scogin / Werner Truyens
version: 2.0
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
Based on the gain sharing automatic microphone mixing algorithm created by [Dan Dugan](https://www.dandugan.com),
this plugin attenuates channels that are not in use helping to smoothly cut background noise and bleed.
Useful for spoken word content.
## Instructions
***************************************************************************************
THIS PLUGIN NEEDS EXACTLY ONE INSTANCE OF THE PLUGIN "AUTOMIXER CONTROL" TO BE ACTIVE!!
***************************************************************************************
Drop this plugin on each channel in the group.
- Inactive channels will be attenuated helping to decrease background noise.
- Multiple active channels will be attenuated according to the sum of all channels helping to keep the overall volume under control.
See [Wikipedia](https://en.wikipedia.org/wiki/Automixer) for a more thorough description.
changelog: - automatic detection of change of number of instances (more or less) by using the AUTOMIXER CONTROL plugin.
options:gmem=AutoMixer
slider2:WindowLength=300<1,10000,10>RMS Window Length (ms)
slider3:WindowType=0<0,1,1{Rectangular,Efficient}>RMS Window Type
@init
minRmsValue = 10^(-144/20) ; // Minimum RMS value (-144) in linear representation
ext_noinit = 1.0;
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
// gmem[n + 100] the instance ID for the nth channel
// gmem[n + 200] 'alive' counter of the nth channel for the CONTROL instance
// gmem[n + 300] Buffer to store old 'alive' channel value
// gmem[n + 400] Buffer to store check random value
// gmem[n + 500] Counter of number of detected inactive cycles for the channel
// Reset InstanceCount
thisID = 0;
ChannelNumber = 0;
RandomVal = rand(100000);
IsInitialized = 0;
@slider
// Recalculate constants
// Sample window
window = WindowLength * .001 * srate; // number of samples in selected time frame (ms)
// Constants for efficient moving average approximation
b1 = exp(-1 / window);
a0 = 1 - b1;
// reset persisted values to ensure new calculations are clean
buf = 0; // Reset buffer position in case the window gets smaller
memset( buf, 0, window ); // Clear the entire new buffer memory space
rmsSum = 0; // Clear the rmsSum because old buffer values are no longer valid
freembuf(window + 1); // Tell the memory manager how much memory we need or don't need
@block
IsInitialized == 0 ?(
// Find an unused channel
while(
ChannelNumber = ChannelNumber + 1;
ChannelNumber <= 100 && gmem[400+ChannelNumber] = 0;
);
// Found an empty channel?
ChannelNumber < 100 ? (
IsInitialized = 1;
// Check the channel for double use
gmem[400+ChannelNumber] = RandomVal;
);
):(
gmem[400+ChannelNumber] == RandomVal ? (
IsInitialized = 2; // Check was fine : channel is ours!
):(
IsInitialized = 0; // Find another channel : this one is in use
);
);
@sample
IsInitialized == 2 ?(
// Get the ID for this instance
thisID = gmem[100+ChannelNumber];
);
// Update the presence counter
gmem[200+ChannelNumber] = gmem[200+ChannelNumber] + 1;
gmem[200+ChannelNumber] > 100000 ? (
gmem[200+ChannelNumber] = 1;
);
// Only start processing if ID is given
thisID != 0 ? (
// Average all channels in this track (typically 2)
i=0;
trkSum=0;
loop(num_ch, // For each channel
trkSum += spl(i); // Add the sample values
i += 1;
);
trkAvg = trkSum / num_ch; // Calculate the average
// Calculate the new rms value
WindowType == 0 ? ( // Window Type == Rectangular
// Less efficient true rectangular window moving average
splSq = trkAvg ^ 2; // Sample (or average of channel samples) squared for moving average sum.
rmsSum -= buf[0]; // Subtract the oldest value square from the sum
rmsSum += splSq; // add the newest value square to the sum
buf[0] = splSq; // add the newest value square to the buffer
(buf+=1) >= window ? buf=0; // increment buffer position
rms = sqrt( rmsSum / window ) ; // Calculate the new RMS value
):( // Window Type == efficient approximation
// Efficient moving average approximation
fout = a0 * (trkAvg ^ 2) + b1 * fout;
rms = sqrt(fout);
);
// Store current RMS value in shared memory for comparison by other instances of this plugin
gmem[thisID] = rms;
// Calculate total RMS level of all plugin instances
i = 0;
sum = 0;
loop(gmem[0],
i += 1;
sum += gmem[i];
);
// Put a minimum limit on the RMS value...effectively zero
rms < minSplValue ? rms = minSplValue; // -144 dB
// The AutoMixer Algorithm
attSpl = rms / sum; // For reference: subtraction in dB corresponds to division in linear
// Attenuate each channel sample value
i = 0;
loop(num_ch,
spl(i) = spl(i) * attspl;
i += 1;
);
// Possible future modifications:
// -Instance grouping
)
Here is the code for the automixer controller:
=================================
Code:
desc:AutoMixer Control
author: Werner Truyens
version: 1.0
tags: volume, attenuation, automation
link: Forum Thread https://forum.cockos.com/showthread.php?t=173289
about:
# AutoMixer
## Description
This plugin controls the AUTOMIXER plugin.
## Instructions
This plugin should exist exactly once for all on any choosen active track.
// Global Memory locations used:
// gmem[0] stores how many instances of this plugin exist
// gmem[n] stores the current rms value for each of the n instances of the plugin
// gmem[n + 100] the instance ID for the nth channel
// gmem[n + 200] 'alive' counter of the nth channel for the CONTROL instance
// gmem[n + 300] Buffer to store old 'alive' channel value
// gmem[n + 400] Buffer to store check random value
// gmem[n + 500] Counter of number of detected inactive cycles for the channel
options:gmem=AutoMixer
//slider1:0<0,100,1>parameter1
//slider2:0<0,100,1>parameter2
@init
ChannelNumber = 0;
NextInstanceID = 1;
ext_noinit = 1.0;
@slider
@block
ChannelNumber = 0;
while(
ChannelNumber = ChannelNumber + 1;
// Detect instance gone
(gmem[ChannelNumber + 100] != 0) && (gmem[ChannelNumber + 200] != 0) && (gmem[ChannelNumber + 200] == gmem[ChannelNumber + 300]) ? (
gmem[ChannelNumber + 500] = gmem[ChannelNumber + 500] + 1;
gmem[ChannelNumber + 500] > 5 ? (
// instance is gone, reinit
gmem[ChannelNumber + 200] = 0;
gmem[ChannelNumber + 300] = 0;
NextInstanceID = 1;
i = 0;
gmem[0] = 0;
ChannelNumber = 1;
ResetCnt = ResetCnt + 1;
loop(99,
i += 1;
gmem[i + 100] = 0; // RESET ID on all instances
gmem[i + 200] = 0;
gmem[i + 300] = 0;
gmem[i + 500] = 0;
);
);
):(
gmem[ChannelNumber + 500] = 0;
);
// Remember current counter of all channels
gmem[ChannelNumber + 300] = gmem[ChannelNumber + 200];
// Detect new active channel
gmem[ChannelNumber + 200] != 0 && gmem[ChannelNumber + 100] == 0 ? (
// set ID for this new active channel
gmem[ChannelNumber + 100] = NextInstanceID;
// Remember the amount of active channels
gmem[0] = NextInstanceID;
NextInstanceID = NextInstanceID + 1;
// Better to overflow instances then memory... should never happen.
NextInstanceID > 99 ? (
NextInstanceID = 1;
);
);
// For debugging
NrOfInstances = gmem[0];
// Do this for all 99 possible channels
ChannelNumber < 100;
);
@sample
I didn't test it yet with audio, so i have no idea how it behaves during switchovers (channels added / removed).
Werner
Last edited by WernerT; 01-30-2020 at 06:20 AM.
|
|
|
07-02-2020, 06:47 PM
|
#68
|
Human being with feelings
Join Date: Feb 2020
Posts: 4
|
Problem since REAPER Update v6.12 - June 15 2020
REAPER Update - June 15 2020
I have 3 instances of JS Automixer v. 1.3 added on the input FX of 3 recording tracks. I have been doing a podcast with 3 microphones with this exact setup for months and now i have noticed the last 3 recordings have static across the whole first track of my presenter (only while speaking) making them junk!
This REAPER update seems to be causing a problem with JS AutoMixer creating static when i enable more than 2x microphones. When i disable the third track the static created on the first track immediately goes away while recording. I tried disabling the second track with the third track enabled and it did not disable the static on the first track.
Any suggestions?
|
|
|
07-04-2020, 10:32 AM
|
#69
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by soulhar99
REAPER Update - June 15 2020
I have 3 instances of JS Automixer v. 1.3 added on the input FX of 3 recording tracks. I have been doing a podcast with 3 microphones with this exact setup for months and now i have noticed the last 3 recordings have static across the whole first track of my presenter (only while speaking) making them junk!
This REAPER update seems to be causing a problem with JS AutoMixer creating static when i enable more than 2x microphones. When i disable the third track the static created on the first track immediately goes away while recording. I tried disabling the second track with the third track enabled and it did not disable the static on the first track.
Any suggestions?
|
I briefly tried to replicate this in Reaper 6.12 but couldn't.
Are you stopping and restarting recording after enabling or disabling an instance? The plugin has issues re-calibrating when it gains or loses an instance unless the transport is stopped and started again.
I don't doubt there is an issue here but it's not something I can troubleshoot without being able to reproduce it.
It's somewhat beside the point but is there a reason you prefer to record with input effects? Are you recording the master or sending it to a live stream?
|
|
|
07-04-2020, 04:19 PM
|
#70
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Split version
@WernerT, I'm testing your split version now. I may clean up the code a touch and submit it to ReaPack.
Have you been using it? Have you found any issues?
@soulhar99, you may try Werner's version above that requires two parts - controller & worker. Don't use it at the same time you're using the original single-plugin one.
|
|
|
07-06-2020, 08:41 AM
|
#71
|
Human being with feelings
Join Date: Jul 2020
Posts: 2
|
Automixer
Hi Corey,
All this is very interesting to read, since we have just finalized a product called WTAutomixer which can be inserted as a VST3/AU/AAX plug-in to most of the DAW`s and plug-in hosts and have seen how many things needs to be taken into account to make it work well.
More info about our product can be found from https://www.wtautomixer.com and feel free to moderate this as I do work for the manufacturer.
Timo Liski
|
|
|
07-06-2020, 05:29 PM
|
#72
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by tliski
Hi Corey,
All this is very interesting to read, since we have just finalized a product...
Timo Liski
|
I'm aware. You sniped a comment on prosoundweb as well...against their rules.
Last edited by CoreyScogin; 07-06-2020 at 05:29 PM.
Reason: cleanup
|
|
|
07-07-2020, 06:48 AM
|
#73
|
Human being with feelings
Join Date: Jul 2020
Posts: 2
|
Quote:
Originally Posted by CoreyScogin
I'm aware. You sniped a comment on prosoundweb as well...against their rules.
|
Sorry about breaking the rules in PSW. I did fix my username etc as soon as I noticed Mac Kerr’s comment about it.
|
|
|
07-07-2020, 11:43 AM
|
#74
|
Human being with feelings
Join Date: May 2017
Posts: 325
|
Quote:
Originally Posted by CoreyScogin
@WernerT, I'm testing your split version now. I may clean up the code a touch and submit it to ReaPack.
Have you been using it? Have you found any issues?
@soulhar99, you may try Werner's version above that requires two parts - controller & worker. Don't use it at the same time you're using the original single-plugin one.
|
I've been using AutoMixer for the last months while editing podcast and I might say, it's incredible, thanks so much for this.
What's different about the code WernerT posted?
|
|
|
09-11-2020, 10:32 PM
|
#75
|
Human being with feelings
Join Date: Sep 2020
Location: Portugal
Posts: 110
|
I mentioned this in a video
https://youtu.be/sqJs160LSTU (Check the extra tip at the end)
Thanks a lot for creating this.
I’m posting this not only because I mentioned the automixer but also because people who are interested in automixing are probably also interested in truncating silences automatically.
|
|
|
09-12-2020, 07:33 PM
|
#76
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by leafac
https://youtu.be/sqJs160LSTU (Check the extra tip at the end)
Thanks a lot for creating this.
I’m posting this not only because I mentioned the automixer but also because people who are interested in automixing are probably also interested in truncating silences automatically.
|
Great video. I've used Dynamic Split several times before but it never occurred to me to use it on the original tracks by grouping them with the rendered mix track. I'd just apply it after mix down.
|
|
|
09-19-2020, 03:07 AM
|
#77
|
Human being with feelings
Join Date: Sep 2020
Location: Portugal
Posts: 110
|
I have an idea for a different architecture for Automixer and I want to hear your thoughts about it.
My understanding of the current architecture: The Automixer should be added to each child track and works like a gain knob, so the signal comes in and is attenuated on the way out. To know how much attenuation to apply all instances of Automixer communicate through a backdoor (a global buffer).
My proposed different architecture: The Automixer is added to the parent track, all children are routed to it in separate channels, and the Automixer outputs the mixed material.
The routing would look like this:
And here’s a proof of concept:
Code:
desc:A Different Architecture for Automixer
options:no_meter
@init
windowLength = 300;
window = windowLength * .001 * srate;
b1 = exp(-1 / window);
a0 = 1 - b1;
@sample
num_ch;
track0 = (spl0 + spl1) / 2;
track1 = (spl2 + spl3) / 2;
track2 = (spl4 + spl5) / 2;
fout0 = a0 * (track0 ^ 2) + b1 * fout0;
fout1 = a0 * (track1 ^ 2) + b1 * fout1;
fout2 = a0 * (track2 ^ 2) + b1 * fout2;
rms0 = sqrt(fout0);
rms1 = sqrt(fout1);
rms2 = sqrt(fout2);
sum = rms0 + rms1 + rms2;
attenuation0 = rms0 / sum;
attenuation1 = rms1 / sum;
attenuation2 = rms2 / sum;
spl0 = spl0 * attenuation0 + spl2 * attenuation1 + spl4 * attenuation2;
spl1 = spl1 * attenuation0 + spl3 * attenuation1 + spl5 * attenuation2;
spl2 = 0;
spl3 = 0;
spl4 = 0;
spl5 = 0;
Limitations of the proof of concept:
- It’s hard-coded to support three children.
- It’s hard-coded with a RMS Window Length of 300ms.
- It’s hard-coded to use a RMS Window Type of Efficient.
Pros and cons I see on the different architecture:
Pro: There’s a comment at the end of the existing implementation that says “Possible future modifications: Instance grouping”. With this different architecture that just falls out naturally.
Pro: This different architecture is simpler conceptually and would lead to a simpler implementation (I think).
Pro: This different architecture doesn’t require keeping track of FX instances or inter-FX communication through a backdoor. From reading this thread I understand this has caused issues in the past.
Pro: It would be more natural to add configuration that applies to all children. For example, we could let the user select the same RMS Window Length & Type for all children in one place.
Pro: We could add a mixer-like view with meters of the signals coming in and the corresponding attenuation. Think of something similar to the interface on this plugin: https://www.tb-software.com/TBProAudio/amm.html (Why not just use that plugin, then? Well, for one thing as far as I understand it doesn’t solve the instance grouping issue mentioned above, for example.)
Con: It’s more difficult to setup for the user. Instead of just “add the plugin to every child track,” we’d have to talk about more advanced routing. But what’s actually going on becomes more explicit, which may be a benefit.
Con: It’s more awkward to work with children with different channel counts, for example, if one child is stereo and another is quadriphonic. The existing Automixer just works in these cases, but we’d have to add some configuration to support this. Anyway, I think most people are working with children that have a consistent number of channels and are at most stereo, so it shouldn’t be a big deal.
Con: There’s an upper limit on the number of children, because REAPER’s tracks have at most 64 channels. As far as I understand, the current architecture has no such limit.
Con: You can’t add FX after attenuation but before mixing. With the current architecture you can do that by adding a plugin after the Automixer in the child track. I don’t see much use for this, though.
What are your thoughts?
Caveat: I’m new to REAPER and JSFX
Last edited by leafac; 09-19-2020 at 03:19 AM.
Reason: Added one more con
|
|
|
09-19-2020, 06:30 PM
|
#78
|
Human being with feelings
Join Date: Dec 2013
Posts: 31
|
Quote:
Originally Posted by leafac
I have an idea for a different architecture for Automixer and I want to hear your thoughts about it.
|
Your observations are correct. What you propose is possible. I briefly considered that when starting this project.
Quote:
Originally Posted by leafac
Limitations of the proof of concept:
- It’s hard-coded to support three children.
- It’s hard-coded with a RMS Window Length of 300ms.
- It’s hard-coded to use a RMS Window Type of Efficient.
|
Those limitations can certainly be overcome.
It's not something I'm interested in building at the moment primarily due to the difficulty in setting up the project routing.
The way to solve the hard-coding of children is to use a for-loop to iterate over track channel pairs. You might be able to do this while modifying very little of the original code, storing each channel level in internal memory locations rather than "global".
The window and window type in your POC can be handled the same way as in the original code. It's probably not worth it to add the "RMS" version of the window type. The other is close enough.
|
|
|
09-23-2020, 02:26 PM
|
#79
|
Human being with feelings
Join Date: Sep 2020
Location: Portugal
Posts: 110
|
Thanks for taking a look.
Quote:
Originally Posted by CoreyScogin
Your observations are correct. What you propose is possible. I briefly considered that when starting this project.
|
Did you go with the approach you went because of the routing that would be more difficult to explain to users, because of the limitations with multichannel children, or because of something else?
Quote:
Originally Posted by CoreyScogin
Those limitations can certainly be overcome.
|
Oh, yeah: When I say proof of concept, I mean it
Quote:
Originally Posted by CoreyScogin
It's not something I'm interested in building at the moment primarily due to the difficulty in setting up the project routing.
The way to solve the hard-coding of children is to use a for-loop to iterate over track channel pairs. You might be able to do this while modifying very little of the original code, storing each channel level in internal memory locations rather than "global".
The window and window type in your POC can be handled the same way as in the original code. It's probably not worth it to add the "RMS" version of the window type. The other is close enough.
|
Thanks for the advice. I’ll give it a shot and post the results here.
|
|
|
10-19-2020, 01:09 PM
|
#80
|
Human being with feelings
Join Date: Sep 2020
Location: Portugal
Posts: 110
|
I’m back with the results of my journey.
I developed a modification of this plugin that uses the other architecture I mentioned above. Please watch the video below to learn all about it:
https://youtu.be/qi1jQcIaOxo
@andyp24: My modification includes the strength feature you wanted. I used a different sigmoid function, f(x) = x^α ÷ (x^α + (1-x)^α) (where α is the user-controlled parameter), which I found here: https://stackoverflow.com/a/25730573/9415195. I think this function is better than the one you used because f(0) = 0 and f(1) = 1, while in yours I think only f(-∞) = 0 and f(∞) = 1. It probably isn’t something you can hear, but in any case…
@EpicSounds: This is similar to the architecture you proposed, except that we don’t need transmitter plugins; we rely on REAPER’s routing and multichannel tracks instead.
@WernerT: This solves your problem in a natural way: you can mute tracks in REAPER’s mixer and the Automixer should just work.
@CoreyScogin: I tried both methods of calculating RMS and I found that the “efficient” was too slow to disregard old samples. I could change the window size to compensate, but I preferred to just stick with the rectangular window. Also, in the process of developing my version I think I found a bug in the original:
https://github.com/ReaTeam/JSFX/pull/202
Thank you all for this. I learned a lot in the process and had a lot of fun.
Please try it out and let me know what you think.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 02:06 PM.
|