1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-08 15:52:53 +02:00
audacity/src/effects/ClickRemoval.cpp
Steve Daulton d9f3c432d4 Fix for bugs 943, 942, 941, 843 and 775.
Non-linear effects now process tracks before mixing.
This will be slower when multiple tracks are selected
but the preview should now match the applied effect.
SetLinearEffectFlag(true) allows linear effects to
preview more quickly when multiple tracks selected, by
pre-mixing selected tracks.
Simple generators like Tone and Noise may be marked as
'linear' so that they only preview a few seconds.
Generators that vary over time (such as Chirp) must use
the full duration that is set. As this currently
requires calculating the full duration, preview for
'non-linear' generators are not limited to the preview
length.
2015-05-15 12:51:51 +01:00

421 lines
11 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ClickRemoval.cpp
Craig DeForest
*******************************************************************//**
\class EffectClickRemoval
\brief An Effect for removing clicks.
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.
*//*******************************************************************/
#include "../Audacity.h"
#include <math.h>
#include <wx/intl.h>
#include <wx/msgdlg.h>
#include <wx/valgen.h>
#include "../Prefs.h"
#include "../widgets/valnum.h"
#include "ClickRemoval.h"
enum
{
ID_Thresh = 10000,
ID_Width
};
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
Param( Threshold, int, XO("Threshold"), 200, 0, 900, 1 );
Param( Width, int, XO("Width"), 20, 0, 40, 1 );
BEGIN_EVENT_TABLE(EffectClickRemoval, wxEvtHandler)
EVT_SLIDER(ID_Thresh, EffectClickRemoval::OnThreshSlider)
EVT_SLIDER(ID_Width, EffectClickRemoval::OnWidthSlider)
EVT_TEXT(ID_Thresh, EffectClickRemoval::OnThreshText)
EVT_TEXT(ID_Width, EffectClickRemoval::OnWidthText)
END_EVENT_TABLE()
EffectClickRemoval::EffectClickRemoval()
{
mThresholdLevel = DEF_Threshold;
mClickWidth = DEF_Width;
SetLinearEffectFlag(false);
windowSize = 8192;
sep = 2049;
}
EffectClickRemoval::~EffectClickRemoval()
{
}
// IdentInterface implementation
wxString EffectClickRemoval::GetSymbol()
{
return CLICKREMOVAL_PLUGIN_SYMBOL;
}
wxString EffectClickRemoval::GetDescription()
{
return XO("Click Removal is designed to remove clicks on audio tracks");
}
// EffectIdentInterface implementation
EffectType EffectClickRemoval::GetType()
{
return EffectTypeProcess;
}
// EffectClientInterface implementation
bool EffectClickRemoval::GetAutomationParameters(EffectAutomationParameters & parms)
{
parms.Write(KEY_Threshold, mThresholdLevel);
parms.Write(KEY_Width, mClickWidth);
return true;
}
bool EffectClickRemoval::SetAutomationParameters(EffectAutomationParameters & parms)
{
ReadAndVerifyInt(Threshold);
ReadAndVerifyInt(Width);
mThresholdLevel = Threshold;
mClickWidth = Width;
return true;
}
// Effect implementation
bool EffectClickRemoval::CheckWhetherSkipEffect()
{
return ((mClickWidth == 0) || (mThresholdLevel == 0));
}
bool EffectClickRemoval::Startup()
{
wxString base = wxT("/Effects/ClickRemoval/");
// Migrate settings from 2.1.0 or before
// Already migrated, so bail
if (gPrefs->Exists(base + wxT("Migrated")))
{
return true;
}
// Load the old "current" settings
if (gPrefs->Exists(base))
{
mThresholdLevel = gPrefs->Read(base + wxT("ClickThresholdLevel"), 200);
if ((mThresholdLevel < MIN_Threshold) || (mThresholdLevel > MAX_Threshold))
{ // corrupted Prefs?
mThresholdLevel = 0; //Off-skip
}
mClickWidth = gPrefs->Read(base + wxT("ClickWidth"), 20);
if ((mClickWidth < MIN_Width) || (mClickWidth > MAX_Width))
{ // corrupted Prefs?
mClickWidth = 0; //Off-skip
}
SaveUserPreset(GetCurrentSettingsGroup());
// Do not migrate again
gPrefs->Write(base + wxT("Migrated"), true);
gPrefs->Flush();
}
return true;
}
bool EffectClickRemoval::Process()
{
this->CopyInputTracks(); // Set up mOutputTracks.
bool bGoodResult = true;
mbDidSomething = false;
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++;
}
if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective.
wxMessageBox(
wxString::Format(_("Algorithm not effective on this audio. Nothing changed.")),
GetName(),
wxOK | wxICON_ERROR);
this->ReplaceProcessedTracks(bGoodResult && mbDidSomething);
return bGoodResult && mbDidSomething;
}
bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track, sampleCount start, sampleCount len)
{
if (len <= windowSize/2)
{
wxMessageBox(
wxString::Format(_("Selection must be larger than %d samples."), windowSize/2),
GetName(),
wxOK | wxICON_ERROR);
return false;
}
sampleCount idealBlockLen = track->GetMaxBlockSize() * 4;
if (idealBlockLen % windowSize != 0)
idealBlockLen += (windowSize - (idealBlockLen % windowSize));
bool bResult = true;
sampleCount s = 0;
float *buffer = new float[idealBlockLen];
float *datawindow = new float[windowSize];
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 (int 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;
mbDidSomething |= RemoveClicks(windowSize, datawindow);
for(j=0; j<wcopy; j++)
buffer[i+j] = datawindow[j];
}
if (mbDidSomething) // RemoveClicks() actually did something.
track->Set((samplePtr) buffer, floatSample, start + s, block);
s += block;
if (TrackProgress(count, s / (double) len)) {
bResult = false;
break;
}
}
delete[] buffer;
delete[] datawindow;
return bResult;
}
bool EffectClickRemoval::RemoveClicks(sampleCount len, float *buffer)
{
bool bResult = false; // This effect usually does nothing.
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++) {
bResult = true;
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;
return bResult;
}
void EffectClickRemoval::PopulateOrExchange(ShuttleGui & S)
{
S.AddSpace(0, 5);
S.SetBorder(10);
S.StartMultiColumn(3, wxEXPAND);
S.SetStretchyCol(2);
{
// Threshold
IntegerValidator<int> vldThresh(&mThresholdLevel);
vldThresh.SetRange(MIN_Threshold, MAX_Threshold);
mThreshT = S.Id(ID_Thresh).AddTextBox(_("Threshold (lower is more sensitive):"),
wxT(""),
10);
mThreshT->SetValidator(vldThresh);
S.SetStyle(wxSL_HORIZONTAL);
mThreshS = S.Id(ID_Thresh).AddSlider(wxT(""), mThresholdLevel, MAX_Threshold, MIN_Threshold);
mThreshS->SetName(_("Threshold"));
mThreshS->SetValidator(wxGenericValidator(&mThresholdLevel));
#if defined(__WXGTK__)
// Force a minimum size since wxGTK allows it to go to zero
mThreshS->SetMinSize(wxSize(100, -1));
#endif
// Click width
IntegerValidator<int> vldWidth(&mClickWidth);
vldWidth.SetRange(MIN_Width, MAX_Width);
mWidthT = S.Id(ID_Width).AddTextBox(_("Max Spike Width (higher is more sensitive):"),
wxT(""),
10);
mWidthT->SetValidator(vldWidth);
S.SetStyle(wxSL_HORIZONTAL);
mWidthS = S.Id(ID_Width).AddSlider(wxT(""), mClickWidth, MAX_Width, MIN_Width);
mWidthS->SetName(_("Max Spike Width"));
mWidthS->SetValidator(wxGenericValidator(&mClickWidth));
#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 EffectClickRemoval::TransferDataToWindow()
{
if (!mUIParent->TransferDataToWindow())
{
return false;
}
return true;
}
bool EffectClickRemoval::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
return true;
}
void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt))
{
mWidthT->GetValidator()->TransferFromWindow();
mWidthS->GetValidator()->TransferToWindow();
}
void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt))
{
mThreshT->GetValidator()->TransferFromWindow();
mThreshS->GetValidator()->TransferToWindow();
}
void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt))
{
mWidthS->GetValidator()->TransferFromWindow();
mWidthT->GetValidator()->TransferToWindow();
}
void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt))
{
mThreshS->GetValidator()->TransferFromWindow();
mThreshT->GetValidator()->TransferToWindow();
}