Compressor problems

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Compressor problems

Martyn Shaw
In a message dated 25/09/2005 08:52:56 GMT Standard Time, [hidden email]  
writes:
...
BTW, I have not given up a release -- I just hoped that the compressor  
would be completely fixed (I think Martyn does this currently). Martyn,  
if you experience unusual problems during the process, let me know, so  
I'll give it a try. I just don't want to get in your way.
...
I appreciate the patience you have all given me but I feel that my C++  
abilities (zero) are holding up release of 1.2.4, for which I can only  apologise.  
I have looked at your (Markus's) idea to make a two pass  process and it
looks good but I am not able to implement it at my current  level of C++ (zero).
 
The only way I can think of to help get this (part of this) release out  
(other then a large learning curve for me, and I'm very busy at work at the  
moment) is to get rid of the check box at the bottom of 'compressor' completely  
and let users figure out what to do next (maybe 'normalise'?) or maybe give them
 a hint instead.  I think I could provide a patch for this.
 
Any help anybody can give would be appreciated.  I'm at a loss to know  where
to go.  I have very little time at present and don't want to hold up  the
release any more.
 
Sorry
Martyn
 


-------------------------------------------------------
SF.Net email is sponsored by:
Tame your development challenges with Apache's Geronimo App Server.
Download it for free - -and be entered to win a 42" plasma tv or your very
own Sony(tm)PSP.  Click here to play: http://sourceforge.net/geronimo.php
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Markus Meyer
[hidden email] schrieb:

>I appreciate the patience you have all given me but I feel that my C++  
>abilities (zero) are holding up release of 1.2.4, for which I can only  apologise.  
>I have looked at your (Markus's) idea to make a two pass  process and it
>looks good but I am not able to implement it at my current  level of C++ (zero).
>  
>
Martyn,

no problem! I'll look at it as soon as I find time. I'm afraid that this
will have to wait another few days because I will be away on business
next week, but the actual programming should be done in one evening or so.


Markus



-------------------------------------------------------
SF.Net email is sponsored by:
Tame your development challenges with Apache's Geronimo App Server.
Download it for free - -and be entered to win a 42" plasma tv or your very
own Sony(tm)PSP.  Click here to play: http://sourceforge.net/geronimo.php
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Martyn Shaw
In reply to this post by Martyn Shaw
Markus
 
I had a chat with a colleague at work (Chris, who teaches C++ and is very  
bright) and he explained quite a lot to me.  With this and referring to  your
example code I have implemented pretty much what you said and got it  working
(although a check over what I've done would be appreciated).  I had  to add a
couple of files to the project however and I guess that will need  including in
the various ports.
 
I am left with a (user-type) question.  If you apply compression to a  number
of tracks and include normalisation, should Audacity apply the same level  of
normalisation to all the tracks or normalise each track individually?  I  
note that 'Amplify' and 'Normalize' use different ideas here - 'Amplify' appears  
to amplify all the tracks by the same amount whereas 'Normalize' appears to  
amplify each one to a max level of -3dB.  I plump for the first option as  it
would not muck-up a mix, but what does anyone else think?  (Also, that  it
what I have implemented.)
 
CVS does not seem to be working at the moment so I am attaching the files I  
have added / changed.  They comprise of...
 
TwoPassSimpleMono.h
which has the declarations of the new Process, FirstPass and SecondPass  
methods.
 
TwoPassSimpleMono.cpp
which has (something similar to) the code you suggested and has dummys for  
InitFirstPass and InitSecondPass (should these return a bool or be void?).
 
Compressor.h
which includes TwoPassSimpleMono.h instead of SimpleMono.h  and contains the
declarations of the (protected) 'pass' and 'mMax'  variables.
 
Compressor.cpp
which includes the over-rides of the FirstPass and SecondPass  methods.
 
If this works, I guess in the long run it would be better to re-write  
Normalize to use EffectTwoPassSimpleMono instead of reiterating code with minor  
variations?
 
I will add some comments to the code and submit it as a patch if you like  
(and if I can figure out how!).
 
Martyn
PS I t wasn't as difficult as I had thought, given the help of my mate  
Chris!  Or have I made a mess again?
PPS A woman goes into a bar and asks for a double entendre, so the landlord  
gave her one.  (Well, I thought it was funny!)
 
In a message dated 26/09/2005 00:18:42 GMT Standard Time, [hidden email]  
writes:
[hidden email] schrieb:

>I appreciate the patience you  have all given me but I feel that my C++  
>abilities (zero) are  holding up release of 1.2.4, for which I can only  
apologise.  
>I have looked at your (Markus's) idea to make a two pass  process  and it
>looks good but I am not able to implement it at my current   level of C++
(zero).
>  
>
Martyn,

no problem! I'll  look at it as soon as I find time. I'm afraid that this
will have to wait  another few days because I will be away on business
next week, but the  actual programming should be done in one evening or  so.


Markus



-------------------------------------------------------
SF.Net  email is sponsored by:
Tame your development challenges with Apache's  Geronimo App Server.
Download it for free - -and be entered to win a 42"  plasma tv or your very
own Sony(tm)PSP.  Click here to play:  http://sourceforge.net/geronimo.php
_______________________________________________
Audacity-devel  mailing  list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
 

/**********************************************************************

  Audacity: A Digital Audio Editor

  Compressor.cpp

  Dominic Mazzoni

  Steve Jolly made it inherit from EffectSimpleMono.
  GUI added and implementation improved by Dominic Mazzoni, 5/11/2003.

**********************************************************************/

#include <math.h>

#include <wx/msgdlg.h>
#include <wx/textdlg.h>
#include <wx/brush.h>
#include <wx/image.h>
#include <wx/dcmemory.h>

#include "Compressor.h"
#include "../Audacity.h" // for rint from configwin.h
#include "../WaveTrack.h"
#include "../widgets/Ruler.h"
#include "../AColor.h"

EffectCompressor::EffectCompressor()
{
   mNormalize = true;
   mFloor = 0.001;
   mAttackTime = 0.2;      // seconds
   mDecayTime = 1.0;       // seconds
   mRatio = 2.0;           // positive number > 1.0
   mThresholdDB = -12.0;
   mCircle = NULL;
        mLevelCircle = NULL;
}

bool EffectCompressor::PromptUser()
{
   CompressorDialog dlog(this, mParent, -1, _("Dynamic Range Compressor"));
   dlog.threshold = mThresholdDB;
   dlog.ratio = mRatio;
   dlog.attack = mAttackTime;
   dlog.useGain = mNormalize;
   dlog.TransferDataToWindow();

   dlog.CentreOnParent();
   dlog.ShowModal();

   if (!dlog.GetReturnCode())
      return false;

   mThresholdDB = dlog.threshold;
   mRatio = dlog.ratio;
   mAttackTime = dlog.attack;
   mNormalize = dlog.useGain;

   return true;
}

bool EffectCompressor::NewTrackSimpleMono()
{
   if (mCircle)
      delete[] mCircle;
   if (mLevelCircle)
      delete[] mLevelCircle;

   mCircleSize = 100;
   mCircle = new double[mCircleSize];
   mLevelCircle = new double[mCircleSize];
   for(int j=0; j<mCircleSize; j++) {
      mCircle[j] = 0.0;
      mLevelCircle[j] = mFloor;
   }
   mCirclePos = 0;
   mRMSSum = 0.0;

   mThreshold = pow(10.0, mThresholdDB/20); // factor of 20 because it's power

   mAttackFactor = exp(-log(mFloor) / (mCurRate * mAttackTime + 0.5));
   mDecayFactor = exp(log(mFloor) / (mCurRate * mDecayTime + 0.5));

   mLastLevel = 0.0;

   return true;
}
bool EffectCompressor::InitFirstPass()
{
   pass=0;
   mMax=0.0;
   return true;
}
bool EffectCompressor::InitSecondPass()
{
   pass=1;
   return true;
}
bool EffectCompressor::ProcessSimpleMono(float *buffer, sampleCount len)
{
   double *follow = new double[len];
   int i;
   if(pass==0) {
                // This makes sure that the initial value is well-chosen
                if (mLastLevel == 0.0) {
                        int preSeed = mCircleSize;
                        if (preSeed > len)
                                preSeed = len;
                        for(i=0; i<preSeed; i++)
                                AvgCircle(buffer[i]);
                }

                for (i = 0; i < len; i++) {
                        Follow(buffer[i], &follow[i], i);
                }

                for (i = 0; i < len; i++) {
                        buffer[i] = DoCompression(buffer[i], follow[i]);
                }

                delete[] follow;
   }
   else { //pass!=0, should be 1 here
                if(mNormalize) //MJS apply normalisation
                {
                        for (i = 0; i < len; i++) {
                                buffer[i] /= mMax;
                        }
                }
   }

   return true;
}

float EffectCompressor::AvgCircle(float value)
{
   float level;

   // Calculate current level from root-mean-squared of
   // circular buffer ("RMS")
   mRMSSum -= mCircle[mCirclePos];
   mCircle[mCirclePos] = value*value;
   mRMSSum += mCircle[mCirclePos];
   level = sqrt(mRMSSum/mCircleSize);
   mLevelCircle[mCirclePos] = level;
   mCirclePos = (mCirclePos+1)%mCircleSize;

#if 0 // Peak instead of RMS
   int j;
   level = 0.0;
   for(j=0; j<mCircleSize; j++)
      if (mCircle[j] > level)
         level = mCircle[j];
#endif

   return level;
}

void EffectCompressor::Follow(float x, double *outEnv, int maxBack)
{
   /*

   "Follow"ing algorithm by Roger B. Dannenberg, taken from
   Nyquist.  His description follows.  -DMM

   Description: this is a sophisticated envelope follower.
    The input is an envelope, e.g. something produced with
    the AVG function. The purpose of this function is to
    generate a smooth envelope that is generally not less
    than the input signal. In other words, we want to "ride"
    the peaks of the signal with a smooth function. The
    algorithm is as follows: keep a current output value
    (called the "value"). The value is allowed to increase
    by at most rise_factor and decrease by at most fall_factor.
    Therefore, the next value should be between
    value * rise_factor and value * fall_factor. If the input
    is in this range, then the next value is simply the input.
    If the input is less than value * fall_factor, then the
    next value is just value * fall_factor, which will be greater
    than the input signal. If the input is greater than value *
    rise_factor, then we compute a rising envelope that meets
    the input value by working bacwards in time, changing the
    previous values to input / rise_factor, input / rise_factor^2,
    input / rise_factor^3, etc. until this new envelope intersects
    the previously computed values. There is only a limited buffer
    in which we can work backwards, so if the new envelope does not
    intersect the old one, then make yet another pass, this time
    from the oldest buffered value forward, increasing on each
    sample by rise_factor to produce a maximal envelope. This will
    still be less than the input.

    The value has a lower limit of floor to make sure value has a
    reasonable positive value from which to begin an attack.
   */

   float level = AvgCircle(x);
   float high = mLastLevel * mAttackFactor;
   float low = mLastLevel * mDecayFactor;

   if (low < mFloor)
      low = mFloor;

   if (level < low)
      *outEnv = low;
   else if (level < high)
      *outEnv = level;
   else {
      // Backtrack
      double attackInverse = 1.0 / mAttackFactor;
      double temp = level * attackInverse;

      int backtrack = 50;
      if (backtrack > maxBack)
         backtrack = maxBack;

      double *ptr = &outEnv[-1];
      int i;
      bool ok = false;
      for(i=0; i<backtrack-2; i++) {
         if (*ptr < temp) {
            *ptr-- = temp;
            temp *= attackInverse;
         }
         else {
            ok = true;
            break;
         }
      }

      if (!ok && backtrack>1 && (*ptr < temp)) {
         temp = *ptr;
         for (i = 0; i < backtrack-1; i++) {
            ptr++;
            temp *= mAttackFactor;
            *ptr = temp;
         }
      }
      else
         *outEnv = level;
   }

   mLastLevel = *outEnv;
}

float EffectCompressor::DoCompression(float value, double env)
{
   float mult;
   float out;

   if (env > mThreshold)
      mult = pow(mThreshold/env, 1.0-1.0/mRatio);
   else
      mult = 1.0;

   out = value * mult;

   if (out > 1.0)
      out = 1.0;

   if (out < -1.0)
      out = -1.0;

   mMax=mMax<fabs(out)?fabs(out):mMax;

   return out;
}

//----------------------------------------------------------------------------
// CompressorPanel
//----------------------------------------------------------------------------

BEGIN_EVENT_TABLE(CompressorPanel, wxPanel)
    EVT_PAINT(CompressorPanel::OnPaint)
END_EVENT_TABLE()

CompressorPanel::CompressorPanel( wxWindow *parent, wxWindowID id,
                          const wxPoint& pos,
                          const wxSize& size):
   wxPanel(parent, id, pos, size)
{
   mBitmap = NULL;
   mWidth = 0;
   mHeight = 0;
}

void CompressorPanel::OnPaint(wxPaintEvent & evt)
{
   wxPaintDC dc(this);

   int width, height;
   GetSize(&width, &height);

   if (!mBitmap || mWidth!=width || mHeight!=height) {
      if (mBitmap)
         delete mBitmap;

      mWidth = width;
      mHeight = height;
      mBitmap = new wxBitmap(mWidth, mHeight);
   }

   wxColour bkgnd = GetBackgroundColour();
   wxBrush bkgndBrush(bkgnd, wxSOLID);

   wxMemoryDC memDC;
   memDC.SelectObject(*mBitmap);

   wxRect bkgndRect;
   bkgndRect.x = 0;
   bkgndRect.y = 0;
   bkgndRect.width = 40;
   bkgndRect.height = mHeight;
   memDC.SetBrush(bkgndBrush);
   memDC.SetPen(*wxTRANSPARENT_PEN);
   memDC.DrawRectangle(bkgndRect);

   bkgndRect.y = mHeight - 20;
   bkgndRect.width = mWidth;
   bkgndRect.height = 20;
   memDC.DrawRectangle(bkgndRect);

   wxRect border;
   border.x = 40;
   border.y = 0;
   border.width = mWidth;
   border.height = mHeight - 20;

   memDC.SetBrush(*wxWHITE_BRUSH);
   memDC.SetPen(*wxBLACK_PEN);
   memDC.DrawRectangle(border);

   mEnvRect.x = 44;
   mEnvRect.width = mWidth - 50;
   mEnvRect.y = 3;
   mEnvRect.height = mHeight - 26;

   double rangeDB = 60;

   int kneeX = (int)rint((rangeDB+threshold)*mEnvRect.width/rangeDB);
   int kneeY = (int)rint((rangeDB+threshold)*mEnvRect.height/rangeDB);

   int finalY = kneeY + (int)rint((-threshold/ratio)*mEnvRect.height/rangeDB);

   // Yellow line for threshold
   memDC.SetPen(wxPen(wxColour(220, 220, 0), 1, wxSOLID));
   memDC.DrawLine(mEnvRect.x,
                  mEnvRect.y + mEnvRect.height - kneeY,
                  mEnvRect.x + mEnvRect.width,
                  mEnvRect.y + mEnvRect.height - kneeY);

   // Was: Nice dark red line for the compression diagram
//   memDC.SetPen(wxPen(wxColour(180, 40, 40), 3, wxSOLID));

   // Nice blue line for compressor, same color as used in the waveform envelope.
   memDC.SetPen( AColor::WideEnvelopePen) ;

   memDC.DrawLine(mEnvRect.x,
                  mEnvRect.y + mEnvRect.height,
                  mEnvRect.x + kneeX,
                  mEnvRect.y + mEnvRect.height - kneeY);

   memDC.DrawLine(mEnvRect.x + kneeX,
                  mEnvRect.y + mEnvRect.height - kneeY,
                  mEnvRect.x + mEnvRect.width,
                  mEnvRect.y + mEnvRect.height - finalY);

   // Paint border again
   memDC.SetBrush(*wxTRANSPARENT_BRUSH);
   memDC.SetPen(*wxBLACK_PEN);
   memDC.DrawRectangle(border);

   // Ruler

   Ruler vRuler;
   vRuler.SetBounds(0, 0, 40, mHeight-21);
   vRuler.SetOrientation(wxVERTICAL);
   vRuler.SetRange(0, -rangeDB);
   vRuler.SetFormat(Ruler::LinearDBFormat);
   vRuler.SetUnits("dB");
   vRuler.Draw(memDC);

   Ruler hRuler;
   hRuler.SetBounds(41, mHeight-20, mWidth, mHeight);
   hRuler.SetOrientation(wxHORIZONTAL);
   hRuler.SetRange(-rangeDB, 0);
   hRuler.SetFormat(Ruler::LinearDBFormat);
   hRuler.SetUnits("dB");
   hRuler.SetFlip(true);
   hRuler.Draw(memDC);

   dc.Blit(0, 0, mWidth, mHeight,
           &memDC, 0, 0, wxCOPY, FALSE);
}

//----------------------------------------------------------------------------
// CompressorDialog
//----------------------------------------------------------------------------

enum {
   ThresholdID = 7100,
   RatioID,
   AttackID,
   PreviewID
};

BEGIN_EVENT_TABLE(CompressorDialog,wxDialog)
   EVT_BUTTON( wxID_OK, CompressorDialog::OnOk )
   EVT_BUTTON( wxID_CANCEL, CompressorDialog::OnCancel )
   EVT_BUTTON( PreviewID, CompressorDialog::OnPreview )
   EVT_SIZE( CompressorDialog::OnSize )
   EVT_SLIDER( ThresholdID, CompressorDialog::OnSlider )
   EVT_SLIDER( RatioID, CompressorDialog::OnSlider )
   EVT_SLIDER( AttackID, CompressorDialog::OnSlider )
END_EVENT_TABLE()

CompressorDialog::CompressorDialog(EffectCompressor *effect,
                                   wxWindow *parent, wxWindowID id,
                                   const wxString &title,
                                   const wxPoint &position, const wxSize& size,
                                   long style ) :
   wxDialog( parent, id, title, position, size, style ),
   mEffect(effect)
{
   wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);

   mainSizer->Add(new wxStaticText(this, -1,
                                   _("Dynamic Range Compressor by Dominic Mazzoni"),
                                   wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE),
                  0, wxALIGN_CENTRE|wxALL, 5);

   mPanel = new CompressorPanel(this, -1);
   mPanel->threshold = threshold;
   mPanel->ratio = ratio;
   mainSizer->Add(mPanel, 1, wxGROW|wxALIGN_CENTRE|wxALL, 5);

   wxFlexGridSizer *gridSizer = new wxFlexGridSizer(2, 0, 0);

   mThresholdText = new wxStaticText(this, -1, wxString(_("Threshold: ")) + "XXX dB");
   gridSizer->Add(mThresholdText, 0, wxALIGN_LEFT|wxALL, 5);

   mThresholdSlider = new wxSlider(this, ThresholdID, -8, -60, -1,
                                   wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL);
   gridSizer->Add(mThresholdSlider, 1, wxEXPAND|wxALL, 5);

   mRatioText = new wxStaticText(this, -1, wxString(_("Ratio: ")) + "XXXX:1");
   gridSizer->Add(mRatioText, 0, wxALIGN_LEFT|wxALL, 5);

   mRatioSlider = new wxSlider(this, RatioID, 4, 3, 20,
                               wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL);
   gridSizer->Add(mRatioSlider, 1, wxEXPAND|wxALL, 5);

   mAttackText = new wxStaticText(this, -1, wxString(_("Attack Time: ")) + "XXXX secs");
   gridSizer->Add(mAttackText, 0, wxALIGN_LEFT|wxALL, 5);

   mAttackSlider = new wxSlider(this, AttackID, 2, 1, 10,
                                wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL);
   gridSizer->Add(mAttackSlider, 1, wxEXPAND|wxALL, 5);

   mainSizer->Add(gridSizer, 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL|wxALL, 5);

   wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL);

   mGainCheckBox = new wxCheckBox(this, -1, _("Apply Normalization (to 0dB) after compressing"));
   mGainCheckBox->SetValue(true);
   hSizer->Add(mGainCheckBox, 0, wxALIGN_LEFT|wxALL, 5);

   mainSizer->Add(hSizer, 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL|wxALL, 5);

   hSizer = new wxBoxSizer(wxHORIZONTAL);

   wxButton *preview = new wxButton(this, PreviewID, mEffect->GetPreviewName());
   hSizer->Add(preview, 0, wxALIGN_CENTRE|wxALL, 5);
   hSizer->Add(40, 10);

   wxButton *cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
   hSizer->Add(cancel, 0, wxALIGN_CENTRE|wxALL, 5);

   wxButton *ok = new wxButton(this, wxID_OK, _("OK"));
   ok->SetDefault();
   ok->SetFocus();
   hSizer->Add(ok, 0, wxALIGN_CENTRE|wxALL, 5);

   mainSizer->Add(hSizer, 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL|wxALL, 5);

   SetAutoLayout(true);
   SetSizer(mainSizer);
   mainSizer->Fit(this);
   mainSizer->SetSizeHints(this);

   SetSizeHints(400, 300, 20000, 20000);
   SetSize(400, 400);
}

bool CompressorDialog::TransferDataToWindow()
{
   mPanel->threshold = threshold;
   mPanel->ratio = ratio;

   mThresholdSlider->SetValue((int)rint(threshold));
   mRatioSlider->SetValue((int)rint(ratio*2));
   mAttackSlider->SetValue((int)rint(attack*10));
   mGainCheckBox->SetValue(useGain);

   TransferDataFromWindow();

   return true;
}

bool CompressorDialog::TransferDataFromWindow()
{
   threshold = (double)mThresholdSlider->GetValue();
   ratio = (double)(mRatioSlider->GetValue() / 2.0);
   attack = (double)(mAttackSlider->GetValue() / 10.0);
   useGain = mGainCheckBox->GetValue();

   mPanel->threshold = threshold;
   mPanel->ratio = ratio;

   mThresholdText->SetLabel(wxString::Format(_("Threshold: %d dB"), (int)threshold));

   if (mRatioSlider->GetValue()%2 == 0)
      mRatioText->SetLabel(wxString::Format(_("Ratio: %.0f:1"), ratio));
   else
      mRatioText->SetLabel(wxString::Format(_("Ratio: %.1f:1"), ratio));

   mAttackText->SetLabel(wxString::Format(_("Attack Time: %.1f secs"), attack));

   mPanel->Refresh(false);

   return true;
}

void CompressorDialog::OnSize(wxSizeEvent &event)
{
   event.Skip();
}

void CompressorDialog::OnOk(wxCommandEvent &event)
{
   TransferDataFromWindow();

   EndModal(true);
}

void CompressorDialog::OnPreview(wxCommandEvent &event)
{
   TransferDataFromWindow();

        // Save & restore parameters around Preview, because we didn't do OK.
   double    oldAttackTime = mEffect->mAttackTime;
   double    oldThresholdDB = mEffect->mThresholdDB;
   double    oldRatio = mEffect->mRatio;
   bool      oldUseGain = mEffect->mNormalize;

   mEffect->mAttackTime = attack;
   mEffect->mThresholdDB = threshold;
   mEffect->mRatio = ratio;
   mEffect->mNormalize = useGain;

   mEffect->Preview();

   mEffect->mAttackTime = oldAttackTime;
   mEffect->mThresholdDB = oldThresholdDB;
   mEffect->mRatio = oldRatio;
   mEffect->mNormalize = oldUseGain;
}

void CompressorDialog::OnCancel(wxCommandEvent &event)
{
   EndModal(false);
}

void CompressorDialog::OnSlider(wxCommandEvent &event)
{
   TransferDataFromWindow();
}

/**********************************************************************

  Audacity: A Digital Audio Editor

  Compressor.h

  Dominic Mazzoni

**********************************************************************/

#ifndef __AUDACITY_EFFECT_COMPRESSOR__
#define __AUDACITY_EFFECT_COMPRESSOR__

class wxString;

#include <wx/defs.h>
#include <wx/bitmap.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/slider.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/intl.h>
#include "TwoPassSimpleMono.h"

class WaveTrack;

class EffectCompressor: public EffectTwoPassSimpleMono {
   
public:
   
   EffectCompressor();
   
   virtual wxString GetEffectName() {
      return wxString(_("Compressor..."));
   }
   
   virtual wxString GetEffectAction() {
      return wxString(_("Applying Dynamic Range Compression..."));
   }
   
   virtual bool PromptUser();

 protected:
   virtual bool ProcessSimpleMono(float *buffer, sampleCount len);
   int       pass; //MJS
   double    mMax; //MJS

 private:

   bool NewTrackSimpleMono();
   bool InitFirstPass();
   bool InitSecondPass();

   float AvgCircle(float x);
   void Follow(float x, double *outEnv, int maxBack);
   float DoCompression(float x, double env);
   
   double    mAttackTime;
   double    mThresholdDB;
   double    mRatio;
   bool      mNormalize; //MJS
   
   double    mDecayTime;
   double    mAttackFactor;
   double    mDecayFactor;
   double    mFloor;
   double    mThreshold;
   double    mGain;
   double    mRMSSum;
   int       mCircleSize;
   int       mCirclePos;
   double   *mCircle;
   double   *mLevelCircle;
   double    mLastLevel;

   friend class CompressorDialog;
};

class CompressorPanel: public wxPanel
{
public:
   CompressorPanel( wxWindow *parent, wxWindowID id,
                    const wxPoint& pos = wxDefaultPosition,
                    const wxSize& size = wxDefaultSize);

   void OnPaint(wxPaintEvent & event);

   double threshold;
   double ratio;

private:

   wxBitmap *mBitmap;
   wxRect mEnvRect;
   int mWidth;
   int mHeight;

   DECLARE_EVENT_TABLE()
};

// WDR: class declarations

//----------------------------------------------------------------------------
// CompressorDialog
//----------------------------------------------------------------------------

class CompressorDialog: public wxDialog
{
public:
   // constructors and destructors
   CompressorDialog( EffectCompressor *effect,
                     wxWindow *parent, wxWindowID id, const wxString &title,
                     const wxPoint& pos = wxDefaultPosition,
                     const wxSize& size = wxDefaultSize,
                     long style = wxDEFAULT_DIALOG_STYLE );

   double threshold;
   double ratio;
   double attack;
   bool useGain;
   
   virtual bool TransferDataToWindow();
   virtual bool TransferDataFromWindow();
   
private:
   void OnOk( wxCommandEvent &event );
   void OnCancel( wxCommandEvent &event );
   void OnSize( wxSizeEvent &event );
   void OnSlider( wxCommandEvent &event );
   void OnPreview( wxCommandEvent &event );

   EffectCompressor *mEffect;
   CompressorPanel *mPanel;
   wxSlider *mThresholdSlider;
   wxSlider *mRatioSlider;
   wxSlider *mAttackSlider;
   wxCheckBox *mGainCheckBox;
   wxStaticText *mThresholdText;
   wxStaticText *mRatioText;
   wxStaticText *mAttackText;
   
private:
   DECLARE_EVENT_TABLE()
};

#endif


#include "TwoPassSimpleMono.h"

bool EffectTwoPassSimpleMono::Process()
{
    InitFirstPass();
    if (!EffectSimpleMono::Process())
        return false;
    InitSecondPass();
    return EffectSimpleMono::Process();
}

bool EffectTwoPassSimpleMono::InitFirstPass()
{
        return true;
}

bool EffectTwoPassSimpleMono::InitSecondPass()
{
        return true;
}


#include "SimpleMono.h"
class EffectTwoPassSimpleMono:public EffectSimpleMono
{
public:
    virtual bool Process();
    virtual bool InitFirstPass();
    virtual bool InitSecondPass();

};
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Richard Ash (audacity-help)
On Mon, 2005-09-26 at 20:23 -0400, [hidden email] wrote:
> I am left with a (user-type) question.  If you apply compression to a  number
> of tracks and include normalisation, should Audacity apply the same level  of
> normalisation to all the tracks or normalise each track individually?  I  
> note that 'Amplify' and 'Normalize' use different ideas here - 'Amplify' appears  
> to amplify all the tracks by the same amount whereas 'Normalize' appears to  
> amplify each one to a max level of -3dB.  I plump for the first option as  it
> would not muck-up a mix, but what does anyone else think?  (Also, that  it
> what I have implemented.)
>From the point of view of compressing stereo files, I would definitely
want both channels to get the same make-up gain applied, assuming it
doesn't clip either channel. If I want something different I can apply
that later by hand.

The perceived balance between stereo channels is down to the RMS level,
so forcing each to have the same peak amplitude is likely to skew the
stereo image.

> CVS does not seem to be working at the moment so I am attaching the files I  
> have added / changed.
Attached is a patch with the same changes and the necessary Makefile.in
change to make it compile on linux.

I've tested it and it seems fine to me - certainly doesn't have the same
problem as before.

Richard

P.S. - diff -u -N includes new files in the patch

compressor-patch-r1.diff.gz (4K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Markus Meyer
In reply to this post by Martyn Shaw
[hidden email] schrieb:

>I am left with a (user-type) question.  If you apply compression to a  number
>of tracks and include normalisation, should Audacity apply the same level  of
>normalisation to all the tracks or normalise each track individually?  I  
>note that 'Amplify' and 'Normalize' use different ideas here - 'Amplify' appears  
>to amplify all the tracks by the same amount whereas 'Normalize' appears to  
>amplify each one to a max level of -3dB.  I plump for the first option as  it
>would not muck-up a mix, but what does anyone else think?  (Also, that  it
>what I have implemented.)
>  
>
What you've done sound great; I will have a closer look at it when I'm
back in town.

Just for the moment: I believe that one problem with the compressor is
currently that the two channels of a stereo track are compressed
independently. Therefore the compressor only really only works for mono
channels....


Markus



-------------------------------------------------------
SF.Net email is sponsored by:
Tame your development challenges with Apache's Geronimo App Server.
Download it for free - -and be entered to win a 42" plasma tv or your very
own Sony(tm)PSP.  Click here to play: http://sourceforge.net/geronimo.php
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

xiphmont
In reply to this post by Richard Ash (audacity-help)



On Tue, Sep 27, 2005 at 12:12:52PM +0100, Richard Ash wrote:
> >From the point of view of compressing stereo files, I would definitely
> want both channels to get the same make-up gain applied, assuming it
> doesn't clip either channel. If I want something different I can apply
> that later by hand.
>
> The perceived balance between stereo channels is down to the RMS level,
> so forcing each to have the same peak amplitude is likely to skew the
> stereo image.

Professional compressors have a 'stereo link' button for exactly this
purpose.  And you're right; compressing the channels independently
will tend to unbalance the stereo image, sometimes severely
(especially in ambient recordings and recordings that use
phase-shifted artificial stereo).

Monty


-------------------------------------------------------
This SF.Net email is sponsored by:
Power Architecture Resource Center: Free content, downloads, discussions,
and more. http://solutions.newsforge.com/ibmarch.tmpl
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Martyn Shaw
In reply to this post by Martyn Shaw
Are there any Audacity effects that handle stereo properly that I can look  
at?  I can't seem to find any.  Normalize doesn't (it does each  channel
separately, it would appear).  From what I can see in  EffectSimpleMono::Process()
each track is treated separately (I guess there is a  clue in the name!).
 
Am I right in thinking that a stereo track is simply two tracks with  mLinked
set in the first (left?) one?  I figured this out from  Track.cpp/Track.h.  
It only seems to be used in TrackPanel, not in any  effects.
 
It seems to me that I would have to modify EffectSimpleMono::Process() to  
detect linked tracks and if it came across any, call a different version of  
ProcessOne (say ProcessTwo) which then in turn would call  ProcessSimpleStereo.  
I could then write a new Compressor envelope detector  to do linked stereo
processing.  This sounds like a big job and not one for  1.2.4 to me.  And I'm
not sure its the best way forward, given the previous  comments on SC4 by Steve
Harris (31/8/5).
 
What do you think?
 
Martyn
 
In a message dated 27/09/2005 12:48:18 GMT Standard Time, [hidden email]  
writes:
...
Just for the moment: I believe that one problem with the  compressor is
currently that the two channels of a stereo track are  compressed
independently. Therefore the compressor only really only works  for mono
channels....


Markus




-------------------------------------------------------
This SF.Net email is sponsored by:
Power Architecture Resource Center: Free content, downloads, discussions,
and more. http://solutions.newsforge.com/ibmarch.tmpl
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel
Reply | Threaded
Open this post in threaded view
|

Re: Compressor problems

Richard Ash (audacity-help)
[hidden email] said:
> Are there any Audacity effects that handle stereo properly that I can look
> at?  I can't seem to find any.
Only the soundtouch ones, which are external library calls. It doesn't
matter for static effects like the equaliser of course, only those that
interact with the audio.

> It seems to me that I would have to modify EffectSimpleMono::Process()
> [...]
> This sounds like a big job and not one for  1.2.4 to me.
I agree. I'll add it to the release notes as a known issue on Friday
(traveling tomorrow) and we can leave it at that.

> And I'm
> not sure its the best way forward, given the previous  comments on SC4 by
> Steve Harris (31/8/5).
Could well be for 1.3. The whole issue of stereo handling probably needs
re-visiting there, as there are potentially lots of stereo-only effects
that could be created (like a spectral-based vocal remover I came across
recently)

This in a sense can wait though.

Richard



-------------------------------------------------------
This SF.Net email is sponsored by:
Power Architecture Resource Center: Free content, downloads, discussions,
and more. http://solutions.newsforge.com/ibmarch.tmpl
_______________________________________________
Audacity-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/audacity-devel