Old 06-25-2019, 03:50 AM   #41
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

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
andyp24 is offline   Reply With Quote
Old 06-25-2019, 05:38 PM   #42
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default v1.1 revert

Quote:
Originally Posted by andyp24 View Post
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.
CoreyScogin is offline   Reply With Quote
Old 06-25-2019, 10:37 PM   #43
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by CoreyScogin View Post
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.
CoreyScogin is offline   Reply With Quote
Old 06-25-2019, 10:42 PM   #44
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by andyp24 View Post
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.
CoreyScogin is offline   Reply With Quote
Old 06-25-2019, 11:34 PM   #45
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

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
andyp24 is offline   Reply With Quote
Old 06-26-2019, 05:15 PM   #46
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by andyp24 View Post
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.
CoreyScogin is offline   Reply With Quote
Old 06-26-2019, 06:56 PM   #47
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by andyp24 View Post
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
CoreyScogin is offline   Reply With Quote
Old 06-27-2019, 08:04 AM   #48
EpicSounds
Human being with feelings
 
EpicSounds's Avatar
 
Join Date: Jul 2009
Posts: 7,570
Default

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?
__________________
REAPER Video Tutorials, Tips & Tricks and more at The REAPER Blog
EpicSounds is online now   Reply With Quote
Old 06-27-2019, 09:13 AM   #49
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

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
andyp24 is offline   Reply With Quote
Old 06-27-2019, 02:19 PM   #50
nofish
Human being with feelings
 
nofish's Avatar
 
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
Default

Quote:
Originally Posted by andyp24 View Post
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
nofish is offline   Reply With Quote
Old 06-27-2019, 03:14 PM   #51
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

Thanks
andyp24 is offline   Reply With Quote
Old 06-27-2019, 07:58 PM   #52
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by EpicSounds View Post
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.
CoreyScogin is offline   Reply With Quote
Old 06-28-2019, 03:31 AM   #53
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

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
andyp24 is offline   Reply With Quote
Old 06-29-2019, 11:42 PM   #54
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default 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
CoreyScogin is offline   Reply With Quote
Old 06-30-2019, 06:39 AM   #55
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

Great, will try when I'm back in studio this week.
andyp24 is offline   Reply With Quote
Old 01-18-2020, 02:08 PM   #56
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default 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
WernerT is offline   Reply With Quote
Old 01-18-2020, 03:07 PM   #57
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default 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
WernerT is offline   Reply With Quote
Old 01-18-2020, 08:34 PM   #58
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by WernerT View Post
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?
CoreyScogin is offline   Reply With Quote
Old 01-19-2020, 02:25 AM   #59
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default 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.
WernerT is offline   Reply With Quote
Old 01-19-2020, 02:39 AM   #60
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default

Quote:
Originally Posted by CoreyScogin View Post
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.
WernerT is offline   Reply With Quote
Old 01-19-2020, 03:38 PM   #61
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default

Quote:
Originally Posted by JamesPeters View Post
Your forward-slash in the [/code] tag seems to not work. Try copying and pasting this one: /
Success!
WernerT is offline   Reply With Quote
Old 01-20-2020, 08:19 PM   #62
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default Overlapping ID Check

Quote:
Originally Posted by WernerT View Post

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
CoreyScogin is offline   Reply With Quote
Old 01-22-2020, 03:16 PM   #63
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default

Quote:
Originally Posted by CoreyScogin View Post
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...
WernerT is offline   Reply With Quote
Old 01-23-2020, 10:37 PM   #64
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default I think it is working

Quote:
Originally Posted by WernerT View Post
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 View Post
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 View Post
.... 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.
CoreyScogin is offline   Reply With Quote
Old 01-26-2020, 04:54 PM   #65
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default 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...).
WernerT is offline   Reply With Quote
Old 01-29-2020, 05:04 PM   #66
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default 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
WernerT is offline   Reply With Quote
Old 01-30-2020, 06:11 AM   #67
WernerT
Human being with feelings
 
Join Date: Aug 2012
Location: Antwerp/Belgium
Posts: 26
Default

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.
WernerT is offline   Reply With Quote
Old 07-02-2020, 06:47 PM   #68
soulhar99
Human being with feelings
 
Join Date: Feb 2020
Posts: 4
Default 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?
soulhar99 is offline   Reply With Quote
Old 07-04-2020, 10:32 AM   #69
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by soulhar99 View Post
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?
CoreyScogin is offline   Reply With Quote
Old 07-04-2020, 04:19 PM   #70
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default 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.
CoreyScogin is offline   Reply With Quote
Old 07-06-2020, 08:41 AM   #71
tliski
Human being with feelings
 
Join Date: Jul 2020
Posts: 2
Default 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
tliski is offline   Reply With Quote
Old 07-06-2020, 05:29 PM   #72
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by tliski View Post
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
CoreyScogin is offline   Reply With Quote
Old 07-07-2020, 06:48 AM   #73
tliski
Human being with feelings
 
Join Date: Jul 2020
Posts: 2
Default

Quote:
Originally Posted by CoreyScogin View Post
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.
tliski is offline   Reply With Quote
Old 07-07-2020, 11:43 AM   #74
Delucci
Human being with feelings
 
Join Date: May 2017
Posts: 325
Default

Quote:
Originally Posted by CoreyScogin View Post
@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?
Delucci is offline   Reply With Quote
Old 09-11-2020, 10:32 PM   #75
leafac
Human being with feelings
 
leafac's Avatar
 
Join Date: Sep 2020
Location: Portugal
Posts: 110
Default 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.
leafac is offline   Reply With Quote
Old 09-12-2020, 07:33 PM   #76
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by leafac View Post
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.
CoreyScogin is offline   Reply With Quote
Old 09-19-2020, 03:07 AM   #77
leafac
Human being with feelings
 
leafac's Avatar
 
Join Date: Sep 2020
Location: Portugal
Posts: 110
Default

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
Attached Images
File Type: png automixer-routing.png (61.4 KB, 647 views)

Last edited by leafac; 09-19-2020 at 03:19 AM. Reason: Added one more con
leafac is offline   Reply With Quote
Old 09-19-2020, 06:30 PM   #78
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default

Quote:
Originally Posted by leafac View Post
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 View Post
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.
CoreyScogin is offline   Reply With Quote
Old 09-23-2020, 02:26 PM   #79
leafac
Human being with feelings
 
leafac's Avatar
 
Join Date: Sep 2020
Location: Portugal
Posts: 110
Default

Thanks for taking a look.

Quote:
Originally Posted by CoreyScogin View Post
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 View Post
Those limitations can certainly be overcome.
Oh, yeah: When I say proof of concept, I mean it

Quote:
Originally Posted by CoreyScogin View Post
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.
leafac is offline   Reply With Quote
Old 10-19-2020, 01:09 PM   #80
leafac
Human being with feelings
 
leafac's Avatar
 
Join Date: Sep 2020
Location: Portugal
Posts: 110
Default

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.
leafac is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 02:06 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.