Prev Previous Post   Next Post Next
Old 02-23-2016, 09:34 PM   #1
CoreyScogin
Human being with feelings
 
CoreyScogin's Avatar
 
Join Date: Dec 2013
Posts: 31
Default JS AutoMixer: Has this been done before?

Hello everyone. This is my first post on the Reaper forums but I've been a user for 4+ years. Greetings to you all.

I have recently been editing a weekly podcast. For one episode, the host set up 2 microphones and conducted a round-table discussion with 5 or so people. This caused some issues with my typical 1 person per mic workflow. Primarily, since the volume of the person speaking was very inconsistent throughout the recording, it was difficult to set gates in such a way that attenuated the mic not in use without chopping the audio of the quieter participants. The same difficulty would be had with a downward expander.

This prompted me to consider using a Dugan style automixer to attenuate the weaker channel(s) without using a fixed threshold. I heard they work quite well for multiple channels of dialog. A couple days of searching yielded no offline plugin solution. The Waves plugin for SoundGrid was the only thing I could find and it's real-time only.

Since I could not find an AutoMixer plugin, I created one in JSFX. I am a software developer by trade but I was completely unfamiliar with programming DSP and the syntax of JSFX so there may be plenty of errors in this code. Some of the code was borrowed from other plugins or snippets posted elsewhere (thanks).

Usage: Drop this plugin on every track that you want to include in the auto-mix group. The plugin will attenuate itself according to the auto-mixing calculation (attenuation = Total input level of all channels - input level of this channel).

If any of you find this plugin useful or are interested in reviewing the code, I welcome your comments / improvements. I have commented where there are things I think could be improved or wasn't sure of something. The plugin functions as expected but there may be glaring mistakes that I am not seeing in the code itself.

Sorry for the long post.
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();
  );
  // Rebuild instance count tracking at start of play or record
  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
** Updates:
2016-02-24
- Changed all of the calculations to use linear values instead of logarithmic ones. This eliminates a number of linear to log conversions.
- The buffer is now cleared when the window changes. There was previously some unexpected behavior as invalid samples were removed from the sum-of-squares value after the window changed size.
2019-06-22
- Improved how the plugin handles instances being bypassed or removed by resetting the count on playback stop. The improves the reliability somewhat.
2019-06-30
- Further improvements on instance bypass and play/pause handling. This fixes the issue where jumping the play position would mess up the instance tracking.

Last edited by CoreyScogin; 06-29-2019 at 11:23 PM. Reason: updated code
CoreyScogin is offline   Reply With Quote
 

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 11:52 PM.


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