audacia/src/effects/ClickRemoval.cpp

471 lines
13 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ClickRemoval.cpp
Craig DeForest
*******************************************************************//**
\class EffectClickRemoval
\brief An Effect.
Clicks are identified as small regions of high amplitude compared
to the surrounding chunk of sound. Anything sufficiently tall compared
to a large (2048 sample) window around it, and sufficiently narrow,
is considered to be a click.
The structure was largely stolen from Domonic Mazzoni's NoiseRemoval
module, and reworked for the new effect.
This file is intended to become part of Audacity. You may modify
and/or distribute it under the same terms as Audacity itself.
*//****************************************************************//**
\class ClickRemovalDialog
\brief Dialog used with EffectClickRemoval
*//*******************************************************************/
#include "../Audacity.h"
#include <math.h>
#if defined(__WXMSW__) && !defined(__CYGWIN__)
#include <float.h>
#define finite(x) _finite(x)
#endif
#include <wx/msgdlg.h>
#include <wx/textdlg.h>
#include <wx/brush.h>
#include <wx/image.h>
#include <wx/dcmemory.h>
#include <wx/statbox.h>
#include <wx/intl.h>
#include "ClickRemoval.h"
#include "../ShuttleGui.h"
#include "../Envelope.h"
// #include "../FFT.h"
#include "../WaveTrack.h"
#include "../Prefs.h"
#define MIN_THRESHOLD 0
#define MAX_THRESHOLD 900
#define MIN_CLICK_WIDTH 0
#define MAX_CLICK_WIDTH 40
EffectClickRemoval::EffectClickRemoval()
{
windowSize = 8192;
// mThresholdLevel = 200;
// mClickWidth = 20;
sep=2049;
Init();
}
EffectClickRemoval::~EffectClickRemoval()
{
}
bool EffectClickRemoval::Init()
{
#ifdef CLEANSPEECH
mThresholdLevel = gPrefs->Read(wxT("/CsPresets/ClickThresholdLevel"), 200);
if ((mThresholdLevel < MIN_THRESHOLD) || (mThresholdLevel > MAX_THRESHOLD)) { // corrupted Prefs?
mThresholdLevel = 0; //Off-skip
gPrefs->Write(wxT("/CsPresets/ClickThresholdLevel"), mThresholdLevel);
}
mClickWidth = gPrefs->Read(wxT("/CsPresets/ClickWidth"), 20);
if ((mClickWidth < MIN_CLICK_WIDTH) || (mClickWidth > MAX_CLICK_WIDTH)) { // corrupted Prefs?
mClickWidth = 0; //Off-skip
gPrefs->Write(wxT("/CsPresets/ClickWidth"), mClickWidth);
}
#else // CLEANSPEECH
mThresholdLevel = gPrefs->Read(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), 200);
if ((mThresholdLevel < MIN_THRESHOLD) || (mThresholdLevel > MAX_THRESHOLD)) { // corrupted Prefs?
mThresholdLevel = 0; //Off-skip
gPrefs->Write(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), mThresholdLevel);
}
mClickWidth = gPrefs->Read(wxT("/Effects/ClickRemoval/ClickWidth"), 20);
if ((mClickWidth < MIN_CLICK_WIDTH) || (mClickWidth > MAX_CLICK_WIDTH)) { // corrupted Prefs?
mClickWidth = 0; //Off-skip
gPrefs->Write(wxT("/Effects/ClickRemoval/ClickWidth"), mClickWidth);
}
#endif // CLEANSPEECH
return gPrefs->Flush();
}
bool EffectClickRemoval::CheckWhetherSkipEffect()
{
bool rc = ((mClickWidth == 0) || (mThresholdLevel == 0));
return rc;
}
bool EffectClickRemoval::PromptUser()
{
ClickRemovalDialog dlog(this, mParent);
dlog.mThresh = mThresholdLevel;
dlog.mWidth = mClickWidth;
dlog.TransferDataToWindow();
dlog.CentreOnParent();
dlog.ShowModal();
if (dlog.GetReturnCode() == wxID_CANCEL)
return false;
mThresholdLevel = dlog.mThresh;
mClickWidth = dlog.mWidth;
#ifdef CLEANSPEECH
gPrefs->Write(wxT("/CsPresets/ClickThresholdLevel"), mThresholdLevel);
gPrefs->Write(wxT("/CsPresets/ClickWidth"), mClickWidth);
#else // CLEANSPEECH
gPrefs->Write(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), mThresholdLevel);
gPrefs->Write(wxT("/Effects/ClickRemoval/ClickWidth"), mClickWidth);
#endif // CLEANSPEECH
return gPrefs->Flush();
}
bool EffectClickRemoval::TransferParameters( Shuttle & shuttle )
{
shuttle.TransferInt(wxT("Threshold"),mThresholdLevel,0);
shuttle.TransferInt(wxT("Width"),mClickWidth,0);
return true;
}
bool EffectClickRemoval::Process()
{
this->CopyInputTracks(); // Set up mOutputTracks.
bool bGoodResult = true;
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
WaveTrack *track = (WaveTrack *) iter.First();
int count = 0;
while (track) {
double trackStart = track->GetStartTime();
double trackEnd = track->GetEndTime();
double t0 = mT0 < trackStart? trackStart: mT0;
double t1 = mT1 > trackEnd? trackEnd: mT1;
if (t1 > t0) {
sampleCount start = track->TimeToLongSamples(t0);
sampleCount end = track->TimeToLongSamples(t1);
sampleCount len = (sampleCount)(end - start);
if (!ProcessOne(count, track, start, len))
{
bGoodResult = false;
break;
}
}
track = (WaveTrack *) iter.Next();
count++;
}
this->ReplaceProcessedTracks(bGoodResult);
return bGoodResult;
}
bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track,
sampleCount start, sampleCount len)
{
bool rc = true;
sampleCount s = 0;
sampleCount idealBlockLen = track->GetMaxBlockSize() * 4;
if (idealBlockLen % windowSize != 0)
idealBlockLen += (windowSize - (idealBlockLen % windowSize));
float *buffer = new float[idealBlockLen];
float *datawindow = new float[windowSize];
int i;
while((s < len)&&((len-s)>(windowSize/2))) {
sampleCount block = idealBlockLen;
if (s + block > len)
block = len - s;
track->Get((samplePtr) buffer, floatSample, start + s, block);
for(i=0; i<(block-windowSize/2); i+=windowSize/2) {
int wcopy = windowSize;
if (i + wcopy > block)
wcopy = block - i;
int j;
for(j=0; j<wcopy; j++)
datawindow[j] = buffer[i+j];
for(j=wcopy; j<windowSize; j++)
datawindow[j] = 0;
RemoveClicks(windowSize, datawindow);
for(j=0; j<wcopy; j++)
buffer[i+j] = datawindow[j];
}
track->Set((samplePtr) buffer, floatSample, start + s, block);
s += block;
if (TrackProgress(count, s / (double) len)) {
rc = false;
break;
}
}
delete[] buffer;
delete[] datawindow;
return rc;
}
void EffectClickRemoval::RemoveClicks(sampleCount len, float *buffer)
{
int i;
int j;
int left = 0;
float msw;
int ww;
int s2 = sep/2;
float *ms_seq = new float[len];
float *b2 = new float[len];
for( i=0; i<len; i++)
b2[i] = buffer[i]*buffer[i];
/* Shortcut for rms - multiple passes through b2, accumulating
* as we go.
*/
for(i=0;i<len;i++)
ms_seq[i]=b2[i];
for(i=1; i < sep; i *= 2) {
for(j=0;j<len-i; j++)
ms_seq[j] += ms_seq[j+i];
}
/* Cheat by truncating sep to next-lower power of two... */
sep = i;
for( i=0; i<len-sep; i++ ) {
ms_seq[i] /= sep;
}
/* ww runs from about 4 to mClickWidth. wrc is the reciprocal;
* chosen so that integer roundoff doesn't clobber us.
*/
int wrc;
for(wrc=mClickWidth/4; wrc>=1; wrc /= 2) {
ww = mClickWidth/wrc;
for( i=0; i<len-sep; i++ ){
msw = 0;
for( j=0; j<ww; j++) {
msw += b2[i+s2+j];
}
msw /= ww;
if(msw >= mThresholdLevel * ms_seq[i]/10) {
if( left == 0 ) {
left = i+s2;
}
} else {
if(left != 0 && i-left+s2 <= ww*2) {
float lv = buffer[left];
float rv = buffer[i+ww+s2];
for(j=left; j<i+ww+s2; j++) {
buffer[j]= (rv*(j-left) + lv*(i+ww+s2-j))/(float)(i+ww+s2-left);
b2[j] = buffer[j]*buffer[j];
}
left=0;
} else if(left != 0) {
left = 0;
}
}
}
}
delete[] ms_seq;
delete[] b2;
}
// WDR: class implementations
//----------------------------------------------------------------------------
// ClickRemovalDialog
//----------------------------------------------------------------------------
const static wxChar *numbers[] =
{
wxT("0"), wxT("1"), wxT("2"), wxT("3"), wxT("4"),
wxT("5"), wxT("6"), wxT("7"), wxT("8"), wxT("9")
};
// Declare window functions
#define ID_THRESH_TEXT 10001
#define ID_THRESH_SLIDER 10002
#define ID_WIDTH_TEXT 10003
#define ID_WIDTH_SLIDER 10004
// Declare ranges
BEGIN_EVENT_TABLE(ClickRemovalDialog, EffectDialog)
EVT_SLIDER(ID_THRESH_SLIDER, ClickRemovalDialog::OnThreshSlider)
EVT_SLIDER(ID_WIDTH_SLIDER, ClickRemovalDialog::OnWidthSlider)
EVT_TEXT(ID_THRESH_TEXT, ClickRemovalDialog::OnThreshText)
EVT_TEXT(ID_WIDTH_TEXT, ClickRemovalDialog::OnWidthText)
EVT_BUTTON(ID_EFFECT_PREVIEW, ClickRemovalDialog::OnPreview)
END_EVENT_TABLE()
ClickRemovalDialog::ClickRemovalDialog(EffectClickRemoval *effect,
wxWindow *parent)
: EffectDialog(parent, _("Click Removal"), PROCESS_EFFECT),
mEffect(effect)
{
Init();
}
void ClickRemovalDialog::PopulateOrExchange(ShuttleGui & S)
{
wxTextValidator vld(wxFILTER_INCLUDE_CHAR_LIST);
vld.SetIncludes(wxArrayString(10, numbers));
S.StartHorizontalLay(wxCENTER, false);
{
S.AddTitle(_("Click and Pop Removal by Craig DeForest"));
}
S.EndHorizontalLay();
S.StartHorizontalLay(wxCENTER, false);
{
// Add a little space
}
S.EndHorizontalLay();
S.StartMultiColumn(3, wxEXPAND);
S.SetStretchyCol(2);
{
// Threshold
mThreshT = S.Id(ID_THRESH_TEXT).AddTextBox(_("Select threshold (lower is more sensitive):"),
wxT(""),
10);
mThreshT->SetValidator(vld);
S.SetStyle(wxSL_HORIZONTAL);
mThreshS = S.Id(ID_THRESH_SLIDER).AddSlider(wxT(""),
0,
MAX_THRESHOLD);
mThreshS->SetName(_("Select threshold"));
mThreshS->SetRange(MIN_THRESHOLD, MAX_THRESHOLD);
#if defined(__WXGTK__)
// Force a minimum size since wxGTK allows it to go to zero
mThreshS->SetMinSize(wxSize(100, -1));
#endif
// Click width
mWidthT = S.Id(ID_WIDTH_TEXT).AddTextBox(_("Max spike width (higher is more sensitive):"),
wxT(""),
10);
mWidthT->SetValidator(vld);
S.SetStyle(wxSL_HORIZONTAL);
mWidthS = S.Id(ID_WIDTH_SLIDER).AddSlider(wxT(""),
0,
MAX_CLICK_WIDTH);
mWidthS->SetName(_("Max spike width"));
mWidthS->SetRange(MIN_CLICK_WIDTH, MAX_CLICK_WIDTH);
#if defined(__WXGTK__)
// Force a minimum size since wxGTK allows it to go to zero
mWidthS->SetMinSize(wxSize(100, -1));
#endif
}
S.EndMultiColumn();
return;
}
bool ClickRemovalDialog::TransferDataToWindow()
{
mWidthS->SetValue(mWidth);
mThreshS->SetValue(mThresh);
mWidthT->SetValue(wxString::Format(wxT("%d"), mWidth));
mThreshT->SetValue(wxString::Format(wxT("%d"), mThresh));
return true;
}
bool ClickRemovalDialog::TransferDataFromWindow()
{
mWidth = TrapLong(mWidthS->GetValue(), MIN_CLICK_WIDTH, MAX_CLICK_WIDTH);
mThresh = TrapLong(mThreshS->GetValue(), MIN_THRESHOLD, MAX_THRESHOLD);
return true;
}
// WDR: handler implementations for ClickRemovalDialog
void ClickRemovalDialog::OnWidthText(wxCommandEvent & event)
{
long val;
mWidthT->GetValue().ToLong(&val);
mWidthS->SetValue(TrapLong(val, MIN_CLICK_WIDTH, MAX_CLICK_WIDTH));
}
void ClickRemovalDialog::OnThreshText(wxCommandEvent & event)
{
long val;
mThreshT->GetValue().ToLong(&val);
mThreshS->SetValue(TrapLong(val, MIN_THRESHOLD, MAX_THRESHOLD));
}
void ClickRemovalDialog::OnWidthSlider(wxCommandEvent & event)
{
mWidthT->SetValue(wxString::Format(wxT("%d"), mWidthS->GetValue()));
}
void ClickRemovalDialog::OnThreshSlider(wxCommandEvent & event)
{
mThreshT->SetValue(wxString::Format(wxT("%d"), mThreshS->GetValue()));
}
void ClickRemovalDialog::OnPreview(wxCommandEvent & event)
{
TransferDataFromWindow();
mEffect->mThresholdLevel = mThresh;
mEffect->mClickWidth = mWidth;
mEffect->Preview();
}
// WDR: handler implementations for NoiseRemovalDialog
/*
void ClickRemovalDialog::OnPreview(wxCommandEvent &event)
{
// Save & restore parameters around Preview, because we didn't do OK.
int oldLevel = mEffect->mThresholdLevel;
int oldWidth = mEffect->mClickWidth;
int oldSep = mEffect->sep;
mEffect->mThresholdLevel = m_pSlider->GetValue();
mEffect->mClickWidth = m_pSlider_width->GetValue();
// mEffect->sep = m_pSlider_sep->GetValue();
mEffect->Preview();
mEffect->sep = oldSep;
mEffect->mClickWidth = oldWidth;
mEffect->mThresholdLevel = oldLevel;
}
*/