2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
DtmfGen.cpp
|
|
|
|
|
|
|
|
Salvo Ventura - Dec 2006
|
|
|
|
|
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class EffectDtmf
|
2015-04-17 03:53:42 +00:00
|
|
|
\brief An effect that generates DTMF tones
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2018-11-11 17:27:44 +00:00
|
|
|
#include "DtmfGen.h"
|
2019-01-17 23:31:08 +00:00
|
|
|
#include "LoadEffects.h"
|
2018-11-11 02:40:37 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/intl.h>
|
2018-11-11 21:18:23 +00:00
|
|
|
#include <wx/slider.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/valgen.h>
|
|
|
|
#include <wx/valtext.h>
|
2018-11-11 21:41:45 +00:00
|
|
|
#include <wx/stattext.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include "../Prefs.h"
|
2019-02-06 18:44:52 +00:00
|
|
|
#include "../Shuttle.h"
|
2015-06-18 14:24:36 +00:00
|
|
|
#include "../ShuttleGui.h"
|
2019-03-30 19:53:54 +00:00
|
|
|
#include "../widgets/NumericTextCtrl.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../widgets/valnum.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2015-04-23 07:02:34 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
ID_Sequence,
|
|
|
|
ID_Amplitude,
|
|
|
|
ID_Duration,
|
|
|
|
ID_DutyCycle,
|
|
|
|
};
|
|
|
|
|
2017-04-02 22:07:13 +00:00
|
|
|
// DA: DTMF for Audacity uses a different string.
|
|
|
|
#ifdef EXPERIMENTAL_DA
|
|
|
|
#define SHORT_APP_NAME "darkaudacity"
|
|
|
|
#else
|
|
|
|
#define SHORT_APP_NAME "audacity"
|
|
|
|
#endif
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Define keys, defaults, minimums, and maximums for the effect parameters
|
|
|
|
//
|
2017-04-02 22:07:13 +00:00
|
|
|
// Name Type Key Def Min Max Scale
|
2017-10-03 22:07:04 +00:00
|
|
|
Param( Sequence, wxString, wxT("Sequence"), wxT(SHORT_APP_NAME), wxT(""), wxT(""), wxT(""));
|
|
|
|
Param( DutyCycle, double, wxT("Duty Cycle"), 55.0, 0.0, 100.0, 10.0 );
|
|
|
|
Param( Amplitude, double, wxT("Amplitude"), 0.8, 0.001, 1.0, 1 );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
static const double kFadeInOut = 250.0; // used for fadein/out needed to remove clicking noise
|
|
|
|
|
|
|
|
const static wxChar *kSymbols[] =
|
|
|
|
{
|
|
|
|
wxT("0"), wxT("1"), wxT("2"), wxT("3"),
|
|
|
|
wxT("4"), wxT("5"), wxT("6"), wxT("7"),
|
|
|
|
wxT("8"), wxT("9"), wxT("*"), wxT("#"),
|
|
|
|
wxT("A"), wxT("B"), wxT("C"), wxT("D"),
|
|
|
|
wxT("a"), wxT("b"), wxT("c"), wxT("d"),
|
|
|
|
wxT("e"), wxT("f"), wxT("g"), wxT("h"),
|
|
|
|
wxT("i"), wxT("j"), wxT("k"), wxT("l"),
|
|
|
|
wxT("m"), wxT("n"), wxT("o"), wxT("p"),
|
|
|
|
wxT("q"), wxT("r"), wxT("s"), wxT("t"),
|
|
|
|
wxT("u"), wxT("v"), wxT("w"), wxT("x"),
|
|
|
|
wxT("y"), wxT("z")
|
|
|
|
};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// EffectDtmf
|
|
|
|
//
|
|
|
|
|
2019-01-17 22:33:49 +00:00
|
|
|
const ComponentInterfaceSymbol EffectDtmf::Symbol
|
|
|
|
{ XO("DTMF Tones") };
|
|
|
|
|
2019-01-17 23:31:08 +00:00
|
|
|
namespace{ BuiltinEffectsModule::Registration< EffectDtmf > reg; }
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
BEGIN_EVENT_TABLE(EffectDtmf, wxEvtHandler)
|
2015-04-23 07:02:34 +00:00
|
|
|
EVT_TEXT(ID_Sequence, EffectDtmf::OnSequence)
|
|
|
|
EVT_TEXT(ID_DutyCycle, EffectDtmf::OnAmplitude)
|
|
|
|
EVT_TEXT(ID_Duration, EffectDtmf::OnDuration)
|
|
|
|
EVT_SLIDER(ID_DutyCycle, EffectDtmf::OnDutyCycle)
|
2015-04-17 03:53:42 +00:00
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
|
|
EffectDtmf::EffectDtmf()
|
|
|
|
{
|
|
|
|
dtmfDutyCycle = DEF_DutyCycle;
|
|
|
|
dtmfAmplitude = DEF_Amplitude;
|
2015-04-23 07:02:34 +00:00
|
|
|
dtmfSequence = DEF_Sequence;
|
2015-04-17 03:53:42 +00:00
|
|
|
dtmfTone = 0.0;
|
|
|
|
dtmfSilence = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
EffectDtmf::~EffectDtmf()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-11-02 15:31:44 +00:00
|
|
|
// ComponentInterface implementation
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-11-02 15:31:44 +00:00
|
|
|
ComponentInterfaceSymbol EffectDtmf::GetSymbol()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2019-01-17 22:33:49 +00:00
|
|
|
return Symbol;
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 18:53:48 +00:00
|
|
|
TranslatableString EffectDtmf::GetDescription()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2019-12-08 18:53:48 +00:00
|
|
|
return XO("Generates dual-tone multi-frequency (DTMF) tones like those produced by the keypad on telephones");
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 16:18:35 +00:00
|
|
|
ManualPageID EffectDtmf::ManualPage()
|
2017-05-20 13:40:09 +00:00
|
|
|
{
|
2021-06-06 16:18:35 +00:00
|
|
|
return L"DTMF_Tones";
|
2017-05-20 13:40:09 +00:00
|
|
|
}
|
|
|
|
|
Automation: AudacityCommand
This is a squash of 50 commits.
This merges the capabilities of BatchCommands and Effects using a new
AudacityCommand class. AudacityCommand provides one function to specify the
parameters, and then we leverage that one function in automation, whether by chains,
mod-script-pipe or (future) Nyquist.
- Now have AudacityCommand which is using the same mechanism as Effect
- Has configurable parameters
- Has data-entry GUI (built using shuttle GUI)
- Registers with PluginManager.
- Menu commands now provided in chains, and to python batch.
- Tested with Zoom Toggle.
- ShuttleParams now can set, get, set defaults, validate and specify
the parameters.
- Bugfix: Don't overwrite values with defaults first time out.
- Add DefineParams function for all built-in effects.
- Extend CommandContext to carry output channels for results.
We abuse EffectsManager. It handles both Effects and
AudacityCommands now. In time an Effect should become a special case of
AudacityCommand and we'll split and rename the EffectManager class.
- Don't use 'default' as a parameter name.
- Massive renaming for CommandDefinitionInterface
- EffectIdentInterface becomes EffectDefinitionInterface
- EffectAutomationParameters becomes CommandAutomationParameters
- PluginType is now a bit field.
This way we can search for related types at the same time.
- Most old batch commands made into AudacityCommands.
The ones that weren't are for a reason. They are used by mod-script-pipe
to carry commands and responses across from a non-GUI thread to the GUI
thread.
- Major tidy up of ScreenshotCommand
- Reworking of SelectCommand
- GetPreferenceCommand and SetPreferenceCommand
- GetTrackInfo and SetTrackInfo
- GetInfoCommand
- Help, Open, Save, Import and Export commands.
- Removed obsolete commands ExecMenu, GetProjectInfo and SetProjectInfo
which are now better handled by other commands.
- JSONify "GetInfo: Commands" output, i.e. commas in the right places.
- General work on better Doxygen.
- Lyrics -> LyricsPanel
- Meter -> MeterPanel
- Updated Linux makefile.
- Scripting commands added into Extra menu.
- Distinct names for previously duplicated find-clipping parameters.
- Fixed longstanding error with erroneous status field number which
previously caused an ASSERT in debug.
- Sensible formatting of numbers in Chains, 0.1 not 0.1000000000137
2018-01-14 18:51:41 +00:00
|
|
|
// EffectDefinitionInterface implementation
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
EffectType EffectDtmf::GetType()
|
|
|
|
{
|
|
|
|
return EffectTypeGenerate;
|
|
|
|
}
|
|
|
|
|
|
|
|
// EffectClientInterface implementation
|
|
|
|
|
2016-09-02 19:53:09 +00:00
|
|
|
unsigned EffectDtmf::GetAudioOutCount()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectDtmf::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
|
|
|
|
{
|
2020-01-23 12:59:31 +00:00
|
|
|
if (dtmfNTones <= 0) { // Bail if no DTFM sequence.
|
|
|
|
::Effect::MessageBox(
|
|
|
|
XO("DTMF sequence empty.\nCheck ALL settings for this effect."),
|
|
|
|
wxICON_ERROR );
|
|
|
|
|
2020-01-20 21:29:50 +00:00
|
|
|
return false;
|
2020-01-23 12:59:31 +00:00
|
|
|
}
|
2015-04-20 00:44:10 +00:00
|
|
|
double duration = GetDuration();
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// all dtmf sequence durations in samples from seconds
|
|
|
|
// MJS: Note that mDuration is in seconds but will have been quantised to the units of the TTC.
|
|
|
|
// If this was 'samples' and the project rate was lower than the track rate,
|
|
|
|
// extra samples may get created as mDuration may now be > mT1 - mT0;
|
|
|
|
// However we are making our best efforts at creating what was asked for.
|
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto nT0 = (sampleCount)floor(mT0 * mSampleRate + 0.5);
|
|
|
|
auto nT1 = (sampleCount)floor((mT0 + duration) * mSampleRate + 0.5);
|
2015-04-17 03:53:42 +00:00
|
|
|
numSamplesSequence = nT1 - nT0; // needs to be exact number of samples selected
|
|
|
|
|
|
|
|
//make under-estimates if anything, and then redistribute the few remaining samples
|
2016-08-25 12:53:59 +00:00
|
|
|
numSamplesTone = sampleCount( floor(dtmfTone * mSampleRate) );
|
|
|
|
numSamplesSilence = sampleCount( floor(dtmfSilence * mSampleRate) );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
// recalculate the sum, and spread the difference - due to approximations.
|
|
|
|
// Since diff should be in the order of "some" samples, a division (resulting in zero)
|
|
|
|
// is not sufficient, so we add the additional remaining samples in each tone/silence block,
|
|
|
|
// at least until available.
|
|
|
|
diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence;
|
|
|
|
while (diff > 2*dtmfNTones - 1) { // more than one per thingToBeGenerated
|
|
|
|
// in this case, both numSamplesTone and numSamplesSilence would change, so it makes sense
|
|
|
|
// to recalculate diff here, otherwise just keep the value we already have
|
|
|
|
|
|
|
|
// should always be the case that dtmfNTones>1, as if 0, we don't even start processing,
|
|
|
|
// and with 1 there is no difference to spread (no silence slot)...
|
|
|
|
wxASSERT(dtmfNTones > 1);
|
|
|
|
numSamplesTone += (diff/(dtmfNTones));
|
|
|
|
numSamplesSilence += (diff/(dtmfNTones-1));
|
|
|
|
diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence;
|
|
|
|
}
|
|
|
|
wxASSERT(diff >= 0); // should never be negative
|
|
|
|
|
2015-04-23 07:02:34 +00:00
|
|
|
curSeqPos = -1; // pointer to string in dtmfSequence
|
2015-04-17 03:53:42 +00:00
|
|
|
isTone = false;
|
|
|
|
numRemaining = 0;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t EffectDtmf::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, size_t size)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
float *buffer = outbuf[0];
|
2016-08-24 15:24:26 +00:00
|
|
|
decltype(size) processed = 0;
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
// for the whole dtmf sequence, we will be generating either tone or silence
|
|
|
|
// according to a bool value, and this might be done in small chunks of size
|
|
|
|
// 'block', as a single tone might sometimes be larger than the block
|
|
|
|
// tone and silence generally have different duration, thus two generation blocks
|
|
|
|
//
|
|
|
|
// Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to
|
|
|
|
// silence, I added a fade in/out of 1/250th of a second (4ms). This can still be
|
|
|
|
// tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs.
|
|
|
|
// A problem might be if the tone duration is very short (<10ms)... (?)
|
|
|
|
//
|
|
|
|
// One more problem is to deal with the approximations done when calculating the duration
|
|
|
|
// of both tone and silence: in some cases the final sum might not be same as the initial
|
|
|
|
// duration. So, to overcome this, we had a redistribution block up, and now we will spread
|
|
|
|
// the remaining samples in every bin in order to achieve the full duration: test case was
|
|
|
|
// to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation
|
|
|
|
// you ended up with 3.999s or in other units: 3 seconds and 44097 samples.
|
|
|
|
//
|
|
|
|
while (size)
|
|
|
|
{
|
|
|
|
if (numRemaining == 0)
|
|
|
|
{
|
|
|
|
isTone = !isTone;
|
|
|
|
|
|
|
|
if (isTone)
|
|
|
|
{
|
|
|
|
curSeqPos++;
|
|
|
|
numRemaining = numSamplesTone;
|
|
|
|
curTonePos = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
numRemaining = numSamplesSilence;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the statement takes care of extracting one sample from the diff bin and
|
|
|
|
// adding it into the current block until depletion
|
|
|
|
numRemaining += (diff-- > 0 ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
2016-08-21 22:05:43 +00:00
|
|
|
const auto len = limitSampleBufferSize( size, numRemaining );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
if (isTone)
|
|
|
|
{
|
|
|
|
// generate the tone and append
|
2015-04-23 07:02:34 +00:00
|
|
|
MakeDtmfTone(buffer, len, mSampleRate, dtmfSequence[curSeqPos], curTonePos, numSamplesTone, dtmfAmplitude);
|
2015-04-17 03:53:42 +00:00
|
|
|
curTonePos += len;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memset(buffer, 0, sizeof(float) * len);
|
|
|
|
}
|
|
|
|
|
|
|
|
numRemaining -= len;
|
|
|
|
|
|
|
|
buffer += len;
|
|
|
|
size -= len;
|
|
|
|
processed += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return processed;
|
|
|
|
}
|
Automation: AudacityCommand
This is a squash of 50 commits.
This merges the capabilities of BatchCommands and Effects using a new
AudacityCommand class. AudacityCommand provides one function to specify the
parameters, and then we leverage that one function in automation, whether by chains,
mod-script-pipe or (future) Nyquist.
- Now have AudacityCommand which is using the same mechanism as Effect
- Has configurable parameters
- Has data-entry GUI (built using shuttle GUI)
- Registers with PluginManager.
- Menu commands now provided in chains, and to python batch.
- Tested with Zoom Toggle.
- ShuttleParams now can set, get, set defaults, validate and specify
the parameters.
- Bugfix: Don't overwrite values with defaults first time out.
- Add DefineParams function for all built-in effects.
- Extend CommandContext to carry output channels for results.
We abuse EffectsManager. It handles both Effects and
AudacityCommands now. In time an Effect should become a special case of
AudacityCommand and we'll split and rename the EffectManager class.
- Don't use 'default' as a parameter name.
- Massive renaming for CommandDefinitionInterface
- EffectIdentInterface becomes EffectDefinitionInterface
- EffectAutomationParameters becomes CommandAutomationParameters
- PluginType is now a bit field.
This way we can search for related types at the same time.
- Most old batch commands made into AudacityCommands.
The ones that weren't are for a reason. They are used by mod-script-pipe
to carry commands and responses across from a non-GUI thread to the GUI
thread.
- Major tidy up of ScreenshotCommand
- Reworking of SelectCommand
- GetPreferenceCommand and SetPreferenceCommand
- GetTrackInfo and SetTrackInfo
- GetInfoCommand
- Help, Open, Save, Import and Export commands.
- Removed obsolete commands ExecMenu, GetProjectInfo and SetProjectInfo
which are now better handled by other commands.
- JSONify "GetInfo: Commands" output, i.e. commas in the right places.
- General work on better Doxygen.
- Lyrics -> LyricsPanel
- Meter -> MeterPanel
- Updated Linux makefile.
- Scripting commands added into Extra menu.
- Distinct names for previously duplicated find-clipping parameters.
- Fixed longstanding error with erroneous status field number which
previously caused an ASSERT in debug.
- Sensible formatting of numbers in Chains, 0.1 not 0.1000000000137
2018-01-14 18:51:41 +00:00
|
|
|
bool EffectDtmf::DefineParams( ShuttleParams & S ){
|
|
|
|
S.SHUTTLE_PARAM( dtmfSequence, Sequence );
|
|
|
|
S.SHUTTLE_PARAM( dtmfDutyCycle, DutyCycle );
|
|
|
|
S.SHUTTLE_PARAM( dtmfAmplitude, Amplitude );
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectDtmf::GetAutomationParameters(CommandParameters & parms)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2015-04-23 07:02:34 +00:00
|
|
|
parms.Write(KEY_Sequence, dtmfSequence);
|
2015-04-17 03:53:42 +00:00
|
|
|
parms.Write(KEY_DutyCycle, dtmfDutyCycle);
|
|
|
|
parms.Write(KEY_Amplitude, dtmfAmplitude);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectDtmf::SetAutomationParameters(CommandParameters & parms)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
ReadAndVerifyDouble(DutyCycle);
|
|
|
|
ReadAndVerifyDouble(Amplitude);
|
|
|
|
ReadAndVerifyString(Sequence);
|
|
|
|
|
|
|
|
wxString symbols;
|
|
|
|
for (unsigned int i = 0; i < WXSIZEOF(kSymbols); i++)
|
|
|
|
{
|
|
|
|
symbols += kSymbols[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Sequence.find_first_not_of(symbols) != wxString::npos)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
dtmfDutyCycle = DutyCycle;
|
|
|
|
dtmfAmplitude = Amplitude;
|
2015-04-23 07:02:34 +00:00
|
|
|
dtmfSequence = Sequence;
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
Recalculate();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Effect implementation
|
|
|
|
|
|
|
|
bool EffectDtmf::Startup()
|
|
|
|
{
|
|
|
|
wxString base = wxT("/Effects/DtmfGen/");
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
{
|
2017-04-02 22:07:13 +00:00
|
|
|
gPrefs->Read(base + wxT("String"), &dtmfSequence, wxT(SHORT_APP_NAME));
|
2015-04-17 03:53:42 +00:00
|
|
|
gPrefs->Read(base + wxT("DutyCycle"), &dtmfDutyCycle, 550L);
|
|
|
|
gPrefs->Read(base + wxT("Amplitude"), &dtmfAmplitude, 0.8f);
|
|
|
|
|
|
|
|
SaveUserPreset(GetCurrentSettingsGroup());
|
|
|
|
|
|
|
|
// Do not migrate again
|
|
|
|
gPrefs->Write(base + wxT("Migrated"), true);
|
|
|
|
gPrefs->Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-07 12:08:41 +00:00
|
|
|
bool EffectDtmf::Init()
|
|
|
|
{
|
|
|
|
Recalculate();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectDtmf::PopulateOrExchange(ShuttleGui & S)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// dialog will be passed values from effect
|
|
|
|
// Effect retrieves values from saved config
|
|
|
|
// Dialog will take care of using them to initialize controls
|
|
|
|
// If there is a selection, use that duration, otherwise use
|
|
|
|
// value from saved config: this is useful is user wants to
|
|
|
|
// replace selection with dtmf sequence
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
S.AddSpace(0, 5);
|
|
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
|
|
{
|
2017-10-30 16:23:41 +00:00
|
|
|
mDtmfSequenceT = S.Id(ID_Sequence)
|
|
|
|
.Validator([this]{
|
|
|
|
wxTextValidator vldDtmf(wxFILTER_INCLUDE_CHAR_LIST, &dtmfSequence);
|
|
|
|
vldDtmf.SetIncludes(wxArrayString(WXSIZEOF(kSymbols), kSymbols));
|
|
|
|
return vldDtmf;
|
|
|
|
})
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("DTMF &sequence:"), wxT(""), 10);
|
2017-10-30 16:23:41 +00:00
|
|
|
|
|
|
|
S.Id(ID_Amplitude)
|
|
|
|
.Validator<FloatingPointValidator<double>>(
|
|
|
|
3, &dtmfAmplitude, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_Amplitude, MAX_Amplitude)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("&Amplitude (0-1):"), wxT(""), 10);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2020-05-11 15:28:14 +00:00
|
|
|
S.AddPrompt(XXO("&Duration:"));
|
2016-02-14 07:54:25 +00:00
|
|
|
mDtmfDurationT = safenew
|
2017-10-27 23:14:48 +00:00
|
|
|
NumericTextCtrl(S.GetParent(), ID_Duration,
|
|
|
|
NumericConverter::TIME,
|
2015-05-27 13:42:15 +00:00
|
|
|
GetDurationFormat(),
|
|
|
|
GetDuration(),
|
2015-04-17 03:53:42 +00:00
|
|
|
mProjectRate,
|
2017-10-27 23:14:48 +00:00
|
|
|
NumericTextCtrl::Options{}
|
|
|
|
.AutoPos(true));
|
2017-10-29 14:27:23 +00:00
|
|
|
S.Name(XO("Duration"))
|
|
|
|
.AddWindow(mDtmfDurationT);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2020-03-27 14:40:26 +00:00
|
|
|
S.AddFixedText(XO("&Tone/silence ratio:"), false);
|
2017-10-31 18:52:01 +00:00
|
|
|
mDtmfDutyCycleS = S.Id(ID_DutyCycle)
|
|
|
|
.Style(wxSL_HORIZONTAL | wxEXPAND)
|
2018-01-31 20:31:22 +00:00
|
|
|
.MinSize( { -1, -1 } )
|
2017-10-31 18:52:01 +00:00
|
|
|
.AddSlider( {},
|
|
|
|
dtmfDutyCycle * SCL_DutyCycle,
|
|
|
|
MAX_DutyCycle * SCL_DutyCycle,
|
|
|
|
MIN_DutyCycle * SCL_DutyCycle);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
|
|
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
|
|
{
|
2019-12-23 00:55:59 +00:00
|
|
|
S.AddFixedText(XO("Duty cycle:"), false);
|
2019-12-22 22:01:20 +00:00
|
|
|
mDtmfDutyT =
|
|
|
|
S.AddVariableText(XO("%.1f %%").Format( dtmfDutyCycle ), false);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2019-12-23 00:55:59 +00:00
|
|
|
S.AddFixedText(XO("Tone duration:"), false);
|
2019-12-22 22:01:20 +00:00
|
|
|
mDtmfSilenceT =
|
|
|
|
/* i18n-hint milliseconds */
|
|
|
|
S.AddVariableText(XO("%.0f ms").Format( dtmfTone * 1000.0 ), false);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2019-12-23 00:55:59 +00:00
|
|
|
S.AddFixedText(XO("Silence duration:"), false);
|
2019-12-22 22:01:20 +00:00
|
|
|
mDtmfToneT =
|
|
|
|
/* i18n-hint milliseconds */
|
|
|
|
S.AddVariableText(XO("%0.f ms").Format( dtmfSilence * 1000.0 ), false);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
bool EffectDtmf::TransferDataToWindow()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
Recalculate();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
if (!mUIParent->TransferDataToWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-23 07:02:34 +00:00
|
|
|
mDtmfDutyCycleS->SetValue(dtmfDutyCycle * SCL_DutyCycle);
|
2015-04-20 00:44:10 +00:00
|
|
|
|
|
|
|
mDtmfDurationT->SetValue(GetDuration());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
bool EffectDtmf::TransferDataFromWindow()
|
|
|
|
{
|
|
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-05-27 13:42:15 +00:00
|
|
|
dtmfDutyCycle = (double) mDtmfDutyCycleS->GetValue() / SCL_DutyCycle;
|
|
|
|
SetDuration(mDtmfDurationT->GetValue());
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// recalculate to make sure all values are up-to-date. This is especially
|
|
|
|
// important if the user did not change any values in the dialog
|
|
|
|
Recalculate();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// EffectDtmf implementation
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectDtmf::Recalculate()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
// remember that dtmfDutyCycle is in range (0.0-100.0)
|
|
|
|
|
2019-03-06 19:53:39 +00:00
|
|
|
dtmfNTones = (int) dtmfSequence.length();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
if (dtmfNTones==0) {
|
|
|
|
// no tones, all zero: don't do anything
|
|
|
|
// this should take care of the case where user got an empty
|
|
|
|
// dtmf sequence into the generator: track won't be generated
|
2015-04-20 00:44:10 +00:00
|
|
|
SetDuration(0.0);
|
2015-04-17 03:53:42 +00:00
|
|
|
dtmfTone = 0;
|
2015-04-20 00:44:10 +00:00
|
|
|
dtmfSilence = 0;
|
2015-04-17 03:53:42 +00:00
|
|
|
} else {
|
|
|
|
if (dtmfNTones==1) {
|
|
|
|
// single tone, as long as the sequence
|
2015-04-20 00:44:10 +00:00
|
|
|
dtmfTone = GetDuration();
|
2015-04-17 03:53:42 +00:00
|
|
|
dtmfSilence = 0;
|
|
|
|
} else {
|
|
|
|
// Don't be fooled by the fact that you divide the sequence into dtmfNTones:
|
|
|
|
// the last slot will only contain a tone, not ending with silence.
|
|
|
|
// Given this, the right thing to do is to divide the sequence duration
|
|
|
|
// by dtmfNTones tones and (dtmfNTones-1) silences each sized according to the duty
|
|
|
|
// cycle: original division was:
|
|
|
|
// slot=mDuration / (dtmfNTones*(dtmfDutyCycle/MAX_DutyCycle)+(dtmfNTones-1)*(1.0-dtmfDutyCycle/MAX_DutyCycle))
|
|
|
|
// which can be simplified in the one below.
|
|
|
|
// Then just take the part that belongs to tone or silence.
|
|
|
|
//
|
2015-04-20 00:44:10 +00:00
|
|
|
double slot = GetDuration() / ((double)dtmfNTones + (dtmfDutyCycle / 100.0) - 1);
|
2015-04-17 03:53:42 +00:00
|
|
|
dtmfTone = slot * (dtmfDutyCycle / 100.0); // seconds
|
|
|
|
dtmfSilence = slot * (1.0 - (dtmfDutyCycle / 100.0)); // seconds
|
|
|
|
|
|
|
|
// Note that in the extremes we have:
|
|
|
|
// - dutyCycle=100%, this means no silence, so each tone will measure mDuration/dtmfNTones
|
|
|
|
// - dutyCycle=0%, this means no tones, so each silence slot will measure mDuration/(NTones-1)
|
|
|
|
// But we always count:
|
|
|
|
// - dtmfNTones tones
|
|
|
|
// - dtmfNTones-1 silences
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
bool EffectDtmf::MakeDtmfTone(float *buffer, size_t len, float fs, wxChar tone, sampleCount last, sampleCount total, float amplitude)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
--------------------------------------------
|
|
|
|
1209 Hz 1336 Hz 1477 Hz 1633 Hz
|
|
|
|
|
|
|
|
ABC DEF
|
|
|
|
697 Hz 1 2 3 A
|
|
|
|
|
|
|
|
GHI JKL MNO
|
|
|
|
770 Hz 4 5 6 B
|
|
|
|
|
|
|
|
PQRS TUV WXYZ
|
|
|
|
852 Hz 7 8 9 C
|
|
|
|
|
|
|
|
oper
|
|
|
|
941 Hz * 0 # D
|
|
|
|
--------------------------------------------
|
|
|
|
Essentially we need to generate two sin with
|
|
|
|
frequencies according to this table, and sum
|
|
|
|
them up.
|
|
|
|
sin wave is generated by:
|
|
|
|
s(n)=sin(2*pi*n*f/fs)
|
|
|
|
|
|
|
|
We will precalculate:
|
|
|
|
A= 2*pi*f1/fs
|
|
|
|
B= 2*pi*f2/fs
|
|
|
|
|
|
|
|
And use two switch statements to select the frequency
|
|
|
|
|
|
|
|
Note: added support for letters, like those on the keypad
|
|
|
|
This support is only for lowercase letters: uppercase
|
|
|
|
are still considered to be the 'military'/carrier extra
|
|
|
|
tones.
|
|
|
|
*/
|
|
|
|
|
|
|
|
float f1, f2=0.0;
|
|
|
|
double A,B;
|
|
|
|
|
|
|
|
// select low tone: left column
|
|
|
|
switch (tone) {
|
2014-06-03 20:30:19 +00:00
|
|
|
case '1': case '2': case '3': case 'A':
|
2010-01-23 19:44:49 +00:00
|
|
|
case 'a': case 'b': case 'c':
|
|
|
|
case 'd': case 'e': case 'f':
|
|
|
|
f1=697;
|
|
|
|
break;
|
2014-06-03 20:30:19 +00:00
|
|
|
case '4': case '5': case '6': case 'B':
|
2010-01-23 19:44:49 +00:00
|
|
|
case 'g': case 'h': case 'i':
|
|
|
|
case 'j': case 'k': case 'l':
|
|
|
|
case 'm': case 'n': case 'o':
|
|
|
|
f1=770;
|
|
|
|
break;
|
2014-06-03 20:30:19 +00:00
|
|
|
case '7': case '8': case '9': case 'C':
|
2010-01-23 19:44:49 +00:00
|
|
|
case 'p': case 'q': case 'r': case 's':
|
|
|
|
case 't': case 'u': case 'v':
|
|
|
|
case 'w': case 'x': case 'y': case 'z':
|
|
|
|
f1=852;
|
|
|
|
break;
|
2014-06-03 20:30:19 +00:00
|
|
|
case '*': case '0': case '#': case 'D':
|
2010-01-23 19:44:49 +00:00
|
|
|
f1=941;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
f1=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// select high tone: top row
|
|
|
|
switch (tone) {
|
|
|
|
case '1': case '4': case '7': case '*':
|
|
|
|
case 'g': case 'h': case 'i':
|
|
|
|
case 'p': case 'q': case 'r': case 's':
|
|
|
|
f2=1209;
|
|
|
|
break;
|
|
|
|
case '2': case '5': case '8': case '0':
|
|
|
|
case 'a': case 'b': case 'c':
|
|
|
|
case 'j': case 'k': case 'l':
|
|
|
|
case 't': case 'u': case 'v':
|
|
|
|
f2=1336;
|
|
|
|
break;
|
|
|
|
case '3': case '6': case '9': case '#':
|
|
|
|
case 'd': case 'e': case 'f':
|
|
|
|
case 'm': case 'n': case 'o':
|
|
|
|
case 'w': case 'x': case 'y': case 'z':
|
|
|
|
f2=1477;
|
|
|
|
break;
|
|
|
|
case 'A': case 'B': case 'C': case 'D':
|
|
|
|
f2=1633;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
f2=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// precalculations
|
|
|
|
A=B=2*M_PI/fs;
|
|
|
|
A*=f1;
|
|
|
|
B*=f2;
|
|
|
|
|
|
|
|
// now generate the wave: 'last' is used to avoid phase errors
|
|
|
|
// when inside the inner for loop of the Process() function.
|
2016-08-24 15:24:26 +00:00
|
|
|
for(decltype(len) i = 0; i < len; i++) {
|
2016-08-25 12:53:59 +00:00
|
|
|
buffer[i] = amplitude * 0.5 *
|
|
|
|
(sin( A * (i + last).as_double() ) +
|
|
|
|
sin( B * (i + last).as_double() ));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// generate a fade-in of duration 1/250th of second
|
2016-08-22 19:04:38 +00:00
|
|
|
if (last == 0) {
|
2020-02-19 16:23:06 +00:00
|
|
|
A = wxMin(len, (fs / kFadeInOut));
|
2016-08-24 14:43:43 +00:00
|
|
|
for(size_t i = 0; i < A; i++) {
|
2016-08-22 19:04:38 +00:00
|
|
|
buffer[i] *= i/A;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate a fade-out of duration 1/250th of second
|
2020-02-19 16:23:06 +00:00
|
|
|
if (last >= total - len) {
|
2010-01-23 19:44:49 +00:00
|
|
|
// we are at the last buffer of 'len' size, so, offset is to
|
|
|
|
// backup 'A' samples, from 'len'
|
2020-02-19 16:23:06 +00:00
|
|
|
A = wxMin(len, (fs / kFadeInOut));
|
|
|
|
size_t offset = len - A;
|
|
|
|
wxASSERT(offset >= 0);
|
|
|
|
for(size_t i = 0; i < A; i++) {
|
|
|
|
buffer[i + offset] *= (1 - (i / A));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectDtmf::UpdateUI(void)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
mDtmfDutyT->SetLabel(wxString::Format(wxT("%.1f %%"), dtmfDutyCycle));
|
2012-10-13 00:59:14 +00:00
|
|
|
mDtmfDutyT->SetName(mDtmfDutyT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-01-04 01:47:56 +00:00
|
|
|
mDtmfSilenceT->SetLabel(wxString::Format(_("%.0f ms"), dtmfTone * 1000.0));
|
2012-10-13 00:59:14 +00:00
|
|
|
mDtmfSilenceT->SetName(mDtmfSilenceT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-01-04 01:47:56 +00:00
|
|
|
mDtmfToneT->SetLabel(wxString::Format(_("%.0f ms"), dtmfSilence * 1000.0));
|
2015-04-17 03:53:42 +00:00
|
|
|
mDtmfToneT->SetName(mDtmfToneT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-23 07:02:34 +00:00
|
|
|
void EffectDtmf::OnSequence(wxCommandEvent & WXUNUSED(evt))
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2015-04-23 07:02:34 +00:00
|
|
|
dtmfSequence = mDtmfSequenceT->GetValue();
|
2010-01-23 19:44:49 +00:00
|
|
|
Recalculate();
|
2015-04-17 03:53:42 +00:00
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-23 07:02:34 +00:00
|
|
|
void EffectDtmf::OnAmplitude(wxCommandEvent & WXUNUSED(evt))
|
|
|
|
{
|
|
|
|
if (!mDtmfAmplitudeT->GetValidator()->TransferFromWindow())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Recalculate();
|
|
|
|
UpdateUI();
|
|
|
|
}
|
|
|
|
void EffectDtmf::OnDuration(wxCommandEvent & WXUNUSED(evt))
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2015-04-20 00:44:10 +00:00
|
|
|
SetDuration(mDtmfDurationT->GetValue());
|
2015-04-23 07:02:34 +00:00
|
|
|
Recalculate();
|
|
|
|
UpdateUI();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectDtmf::OnDutyCycle(wxCommandEvent & evt)
|
|
|
|
{
|
|
|
|
dtmfDutyCycle = (double) evt.GetInt() / SCL_DutyCycle;
|
2010-01-23 19:44:49 +00:00
|
|
|
Recalculate();
|
2015-04-17 03:53:42 +00:00
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|