410 lines
13 KiB
C++
410 lines
13 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
ToneGen.cpp
|
|
|
|
Steve Jolly
|
|
James Crook (Adapted for 'Chirps')
|
|
|
|
This class implements a tone generator effect.
|
|
|
|
*******************************************************************//**
|
|
|
|
\class EffectToneGen
|
|
\brief An Effect that can generate a sine, square or sawtooth wave.
|
|
An extended mode of EffectToneGen supports 'chirps' where the
|
|
frequency changes smoothly during the tone.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class ToneGenDialog
|
|
\brief Dialog used with EffectToneGen
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "ToneGen.h"
|
|
#include "../FFT.h"
|
|
#include "../Project.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../Prefs.h"
|
|
|
|
#include <wx/choice.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <math.h>
|
|
|
|
//
|
|
// EffectToneGen
|
|
//
|
|
|
|
EffectToneGen::EffectToneGen()
|
|
{
|
|
SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT);
|
|
mbChirp = false;
|
|
mbLogInterpolation = false;
|
|
waveform = 0; //sine
|
|
frequency[0] = float(440.0); //Hz
|
|
frequency[1] = float(1320.0); //Hz
|
|
amplitude[0] = float(0.8);
|
|
amplitude[1] = float(0.1);
|
|
interpolation = 0;
|
|
}
|
|
|
|
wxString EffectToneGen::GetEffectDescription() {
|
|
// Note: This is useful only after values have been set.
|
|
/// \todo update to include *all* chirp parameters??
|
|
const wxChar* waveformNames[] = {wxT("sine"), wxT("square"), wxT("sawtooth"), wxT("square, no alias")};
|
|
//const wxChar* interpolationNames[] = {wxT("linear"), wxT("logarithmic")};
|
|
return wxString::Format(_("Applied effect: Generate %s wave %s, frequency = %.2f Hz, amplitude = %.2f, %.6lf seconds"),
|
|
waveformNames[waveform], mbChirp ? wxT("chirp") : wxT("tone"), frequency[0], amplitude[0], mDuration);
|
|
}
|
|
|
|
bool EffectToneGen::PromptUser()
|
|
{
|
|
wxArrayString waveforms;
|
|
wxArrayString interpolations;
|
|
interpolations.Add(_("Linear"));
|
|
interpolations.Add(_("Logarithmic"));
|
|
ToneGenDialog dlog(this, mParent, mbChirp ? _("Chirp Generator") : _("Tone Generator"));
|
|
waveforms.Add(_("Sine"));
|
|
waveforms.Add(_("Square"));
|
|
waveforms.Add(_("Sawtooth"));
|
|
waveforms.Add(_("Square, no alias"));
|
|
dlog.isSelection= false;
|
|
|
|
if (mT1 > mT0) {
|
|
mDuration = mT1 - mT0;
|
|
dlog.isSelection= true;
|
|
}
|
|
else {
|
|
// Retrieve last used values
|
|
gPrefs->Read(wxT("/Effects/ToneGen/Duration"), &mDuration, 30L);
|
|
}
|
|
|
|
dlog.mbChirp = mbChirp;
|
|
dlog.waveform = waveform;
|
|
dlog.frequency[0] = frequency[0];
|
|
dlog.frequency[1] = frequency[1];
|
|
dlog.amplitude[0] = amplitude[0];
|
|
dlog.amplitude[1] = amplitude[1];
|
|
dlog.mDuration = mDuration;
|
|
dlog.waveforms = &waveforms;
|
|
dlog.interpolation = interpolation;
|
|
dlog.interpolations = &interpolations;
|
|
dlog.Init();
|
|
dlog.TransferDataToWindow();
|
|
dlog.Fit();
|
|
dlog.ShowModal();
|
|
|
|
if (dlog.GetReturnCode() == wxID_CANCEL)
|
|
return false;
|
|
|
|
waveform = dlog.waveform;
|
|
frequency[0] = dlog.frequency[0];
|
|
frequency[1] = dlog.frequency[1];
|
|
amplitude[0] = dlog.amplitude[0];
|
|
amplitude[1] = dlog.amplitude[1];
|
|
interpolation = dlog.interpolation;
|
|
if (interpolation==0)
|
|
mbLogInterpolation = false;
|
|
if (interpolation==1)
|
|
mbLogInterpolation = true;
|
|
if( !mbChirp )
|
|
{
|
|
frequency[1] = frequency[0];
|
|
amplitude[1] = amplitude[0];
|
|
}
|
|
mDuration = dlog.mDuration;
|
|
/* Save last used values.
|
|
Save duration unless value was got from selection, so we save only
|
|
when user explicitly set up a value */
|
|
if (mT1 == mT0)
|
|
gPrefs->Write(wxT("/Effects/ToneGen/Duration"), mDuration);
|
|
return true;
|
|
}
|
|
|
|
bool EffectToneGen::TransferParameters( Shuttle & shuttle )
|
|
{
|
|
/// \todo this should in time be using ShuttleGui too.
|
|
// shuttle.TransferInt("",,0);
|
|
return true;
|
|
}
|
|
|
|
bool EffectToneGen::MakeTone(float *buffer, sampleCount len)
|
|
{
|
|
double throwaway = 0; //passed to modf but never used
|
|
sampleCount i;
|
|
double f = 0.0;
|
|
|
|
double BlendedFrequency;
|
|
double BlendedAmplitude;
|
|
|
|
// calculate delta, and reposition from where we left
|
|
double amplitudeQuantum = (amplitude[1]-amplitude[0]) / numSamples;
|
|
double frequencyQuantum = (frequency[1]-frequency[0]) / numSamples;
|
|
BlendedAmplitude = amplitude[0] + amplitudeQuantum * mSample;
|
|
|
|
// precalculations:
|
|
double pre2PI = 2 * M_PI;
|
|
double pre4divPI = 4. / M_PI;
|
|
|
|
/*
|
|
Duplicate the code for the two cases, log interpolation and linear interpolation
|
|
I hope this is more readable, a bit faster (only one branching in mbLogInterpolation)
|
|
Local variables are declared inside respective branch, globals are declared up.
|
|
*/
|
|
|
|
// this for log interpolation
|
|
if( mbLogInterpolation )
|
|
{
|
|
logFrequency[0] = log10( frequency[0] );
|
|
logFrequency[1] = log10( frequency[1] );
|
|
// calculate delta, and reposition from where we left
|
|
double logfrequencyQuantum = (logFrequency[1]-logFrequency[0]) / numSamples;
|
|
double BlendedLogFrequency = logFrequency[0] + logfrequencyQuantum * mSample;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
BlendedFrequency = pow( 10.0, (double)BlendedLogFrequency );
|
|
switch (waveform) {
|
|
case 0: //sine
|
|
f = (float) sin(pre2PI * mPositionInCycles/mCurRate);
|
|
break;
|
|
case 1: //square
|
|
f = (modf(mPositionInCycles/mCurRate, &throwaway) < 0.5) ? 1.0f :-1.0f;
|
|
break;
|
|
case 2: //sawtooth
|
|
f = (2 * modf(mPositionInCycles/mCurRate+0.5f, &throwaway)) -1.0f;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// insert value in buffer
|
|
buffer[i] = BlendedAmplitude * f;
|
|
// update freq,amplitude
|
|
mPositionInCycles += BlendedFrequency;
|
|
BlendedAmplitude += amplitudeQuantum;
|
|
BlendedLogFrequency += logfrequencyQuantum;
|
|
}
|
|
|
|
} else {
|
|
// this for regular case, linear interpolation
|
|
BlendedFrequency = frequency[0] + frequencyQuantum * mSample;
|
|
double a,b;
|
|
int k;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
switch (waveform) {
|
|
case 0: //sine
|
|
f = (float) sin(pre2PI * mPositionInCycles/mCurRate);
|
|
break;
|
|
case 1: //square
|
|
f = (modf(mPositionInCycles/mCurRate, &throwaway) < 0.5) ? 1.0f :-1.0f;
|
|
break;
|
|
case 2: //sawtooth
|
|
f = (2 * modf(mPositionInCycles/mCurRate+0.5f, &throwaway)) -1.0f;
|
|
break;
|
|
case 3: //square, no alias. Good down to 110Hz @ 44100Hz sampling.
|
|
//do fundamental (k=1) outside loop
|
|
b = (1. + cos((pre2PI * BlendedFrequency)/mCurRate))/pre4divPI; //scaling
|
|
f = (float) pre4divPI * sin(pre2PI * mPositionInCycles/mCurRate);
|
|
for(k=3; (k<200) && (k * BlendedFrequency < mCurRate/2.); k+=2)
|
|
{
|
|
//Hanning Window in freq domain
|
|
a = 1. + cos((pre2PI * k * BlendedFrequency)/mCurRate);
|
|
//calc harmonic, apply window, scale to amplitude of fundamental
|
|
f += (float) a * sin(pre2PI * mPositionInCycles/mCurRate * k)/(b*k);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
// insert value in buffer
|
|
buffer[i] = BlendedAmplitude * f;
|
|
// update freq,amplitude
|
|
mPositionInCycles += BlendedFrequency;
|
|
BlendedFrequency += frequencyQuantum;
|
|
BlendedAmplitude += amplitudeQuantum;
|
|
}
|
|
}
|
|
// update external placeholder
|
|
mSample += len;
|
|
return true;
|
|
}
|
|
|
|
void EffectToneGen::BeforeGenerate()
|
|
{
|
|
mPositionInCycles = 0.0;
|
|
}
|
|
|
|
void EffectToneGen::GenerateBlock(float *data,
|
|
const WaveTrack &track,
|
|
sampleCount block)
|
|
{
|
|
MakeTone(data, block);
|
|
}
|
|
|
|
void EffectToneGen::BeforeTrack(const WaveTrack &track)
|
|
{
|
|
mSample = 0;
|
|
mCurRate = track.GetRate();
|
|
}
|
|
|
|
// WDR: class implementations
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ToneGenDialog
|
|
//----------------------------------------------------------------------------
|
|
|
|
#define FREQ_MIN 1
|
|
#define AMP_MIN 0
|
|
#define AMP_MAX 1
|
|
|
|
BEGIN_EVENT_TABLE(ToneGenDialog, EffectDialog)
|
|
EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, ToneGenDialog::OnTimeCtrlUpdate)
|
|
END_EVENT_TABLE()
|
|
|
|
ToneGenDialog::ToneGenDialog(EffectToneGen * effect, wxWindow * parent, const wxString & title)
|
|
: EffectDialog(parent, title, INSERT_EFFECT),
|
|
mEffect(effect)
|
|
{
|
|
mToneDurationT = NULL;
|
|
mbChirp = false;
|
|
}
|
|
|
|
/// Populates simple dialog that has a single tone.
|
|
void ToneGenDialog::PopulateOrExchangeStandard( ShuttleGui & S )
|
|
{
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
{
|
|
S.TieChoice(_("Waveform") + wxString(wxT(":")), waveform, waveforms);
|
|
S.SetSizeHints(-1, -1);
|
|
|
|
// The added colon to improve visual consistency was placed outside
|
|
// the translatable strings to avoid breaking translations close to 2.0.
|
|
// TODO: Make colon part of the translatable string after 2.0.
|
|
S.TieNumericTextBox(_("Frequency (Hz)") + wxString(wxT(":")), frequency[0], 5);
|
|
S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), amplitude[0], 5);
|
|
S.AddPrompt(_("Duration") + wxString(wxT(":")));
|
|
if (mToneDurationT == NULL)
|
|
{
|
|
mToneDurationT = new
|
|
TimeTextCtrl(this,
|
|
wxID_ANY,
|
|
wxT(""),
|
|
mDuration,
|
|
mEffect->mProjectRate,
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
true);
|
|
mToneDurationT->SetName(_("Duration"));
|
|
mToneDurationT->SetFormatString(mToneDurationT->GetBuiltinFormat(isSelection==true?(_("hh:mm:ss + samples")):(_("seconds"))));
|
|
mToneDurationT->EnableMenu();
|
|
}
|
|
S.AddWindow(mToneDurationT);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
|
|
/// Populates more complex dialog that has a chirp.
|
|
void ToneGenDialog::PopulateOrExchangeExtended( ShuttleGui & S )
|
|
{
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
{
|
|
S.TieChoice(_("Waveform:"), waveform, waveforms);
|
|
S.SetSizeHints(-1, -1);
|
|
}
|
|
S.EndMultiColumn();
|
|
S.StartMultiColumn(3, wxCENTER);
|
|
{
|
|
S.AddFixedText(wxT(""));
|
|
S.AddTitle(_("Start"));
|
|
S.AddTitle(_("End"));
|
|
|
|
// The added colon to improve visual consistency was placed outside
|
|
// the translatable strings to avoid breaking translations close to 2.0.
|
|
// TODO: Make colon part of the translatable string after 2.0.
|
|
S.TieNumericTextBox(_("Frequency (Hz)") + wxString(wxT(":")), frequency[0], 10)->SetName(_("Frequency Hertz Start"));
|
|
S.TieNumericTextBox(wxT(""), frequency[1], 10)->SetName(_("Frequency Hertz End"));
|
|
S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), amplitude[0], 10)->SetName(_("Amplitude Start"));
|
|
S.TieNumericTextBox(wxT(""), amplitude[1], 10)->SetName(_("Amplitude End"));
|
|
}
|
|
S.EndMultiColumn();
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
{
|
|
S.TieChoice(_("Interpolation:"), interpolation, interpolations);
|
|
S.AddPrompt(_("Duration") + wxString(wxT(":")));
|
|
if (mToneDurationT == NULL)
|
|
{
|
|
mToneDurationT = new
|
|
TimeTextCtrl(this,
|
|
wxID_ANY,
|
|
wxT(""),
|
|
mDuration,
|
|
mEffect->mProjectRate,
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
true);
|
|
mToneDurationT->SetName(_("Duration"));
|
|
mToneDurationT->SetFormatString(mToneDurationT->GetBuiltinFormat(isSelection==true?(_("hh:mm:ss + samples")):(_("seconds"))));
|
|
mToneDurationT->EnableMenu();
|
|
}
|
|
S.AddWindow(mToneDurationT);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
|
|
void ToneGenDialog::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
if( !mbChirp )
|
|
PopulateOrExchangeStandard( S );
|
|
else
|
|
PopulateOrExchangeExtended( S );
|
|
}
|
|
|
|
bool ToneGenDialog::TransferDataToWindow()
|
|
{
|
|
EffectDialog::TransferDataToWindow();
|
|
|
|
// Must handle this ourselves since ShuttleGui doesn't know about it
|
|
mToneDurationT->SetTimeValue(mDuration);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ToneGenDialog::TransferDataFromWindow()
|
|
{
|
|
EffectDialog::TransferDataFromWindow();
|
|
|
|
amplitude[0] = TrapDouble(amplitude[0], AMP_MIN, AMP_MAX);
|
|
frequency[0] = TrapDouble(frequency[0], FREQ_MIN, (float)(GetActiveProject()->GetRate())/2.);
|
|
amplitude[1] = TrapDouble(amplitude[1], AMP_MIN, AMP_MAX);
|
|
frequency[1] = TrapDouble(frequency[1], FREQ_MIN, (float)(GetActiveProject()->GetRate())/2.);
|
|
|
|
// Must handle this ourselves since ShuttleGui doesn't know about it
|
|
mDuration = mToneDurationT->GetTimeValue();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ToneGenDialog::OnTimeCtrlUpdate(wxCommandEvent & event) {
|
|
Fit();
|
|
}
|
|
|
|
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
|
|
// version control system. Please do not modify past this point.
|
|
//
|
|
// Local Variables:
|
|
// c-basic-offset: 3
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
//
|
|
// vim: et sts=3 sw=3
|
|
// arch-tag: 04ea2450-8127-45c4-8702-6aaf5b60ed8c
|
|
|