2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
Audacity: A Digital Audio Editor
|
2013-06-25 23:43:55 +00:00
|
|
|
Audacity(R) is copyright (c) 1999-2013 Audacity Team.
|
2013-06-19 02:25:21 +00:00
|
|
|
License: GPL v2. See License.txt.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
ChangePitch.cpp
|
2013-06-19 02:25:21 +00:00
|
|
|
Vaughan Johnson, Dominic Mazzoni, Steve Daulton
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
******************************************************************//**
|
|
|
|
|
|
|
|
\file ChangePitch.cpp
|
|
|
|
\brief Change Pitch effect provides raising or lowering
|
|
|
|
the pitch without changing the tempo.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
*//*******************************************************************/
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include "../Audacity.h" // for USE_SOUNDTOUCH
|
|
|
|
|
|
|
|
#if USE_SOUNDTOUCH
|
|
|
|
|
|
|
|
#include "ChangePitch.h"
|
|
|
|
|
2013-05-18 14:23:26 +00:00
|
|
|
#include <float.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include <wx/button.h>
|
|
|
|
#include <wx/choice.h>
|
|
|
|
#include <wx/radiobut.h>
|
|
|
|
#include <wx/sizer.h>
|
|
|
|
#include <wx/stattext.h>
|
|
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <wx/valtext.h>
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
#include "../ShuttleGui.h"
|
|
|
|
#include "../PitchName.h"
|
|
|
|
#include "../Spectrum.h"
|
|
|
|
#include "../WaveTrack.h"
|
|
|
|
#include "TimeWarper.h"
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// EffectChangePitch
|
|
|
|
|
|
|
|
EffectChangePitch::EffectChangePitch()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = 0.0;
|
|
|
|
m_dStartFrequency = 0.0; // 0.0 => uninitialized
|
|
|
|
m_dPercentChange = 0.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
wxString EffectChangePitch::GetEffectDescription()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
// This is useful only after m_dSemitonesChange has been set.
|
2010-01-23 19:44:49 +00:00
|
|
|
return wxString::Format(_("Applied effect: %s %.2f semitones"),
|
|
|
|
this->GetEffectName().c_str(),
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangePitch::Init()
|
|
|
|
{
|
|
|
|
mSoundTouch = NULL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
// Deduce m_FromFrequency from the samples at the beginning of
|
|
|
|
// the selection. Then set some other params accordingly.
|
2010-01-23 19:44:49 +00:00
|
|
|
void EffectChangePitch::DeduceFrequencies()
|
|
|
|
{
|
|
|
|
// As a neat trick, attempt to get the frequency of the note at the
|
|
|
|
// beginning of the selection.
|
|
|
|
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
|
|
|
|
WaveTrack *track = (WaveTrack *) iter.First();
|
|
|
|
if (track) {
|
2013-05-21 01:58:11 +00:00
|
|
|
double rate = track->GetRate();
|
|
|
|
|
|
|
|
// Auto-size window -- high sample rates require larger windowSize.
|
|
|
|
// Aim for around 2048 samples at 44.1 kHz (good down to about 100 Hz).
|
|
|
|
// To detect single notes, analysis period should be about 0.2 seconds.
|
|
|
|
// windowSize must be a power of 2.
|
|
|
|
int windowSize = wxRound(pow(2.0, floor((log(rate / 20.0)/log(2.0)) + 0.5)));
|
|
|
|
// windowSize < 256 too inaccurate
|
|
|
|
windowSize = (windowSize > 256)? windowSize : 256;
|
|
|
|
|
|
|
|
// we want about 0.2 seconds to catch the first note.
|
|
|
|
// number of windows rounded to nearest integer >= 1.
|
|
|
|
int numWindows = wxRound((double)(rate / (5.0f * windowSize)));
|
|
|
|
numWindows = (numWindows > 0)? numWindows : 1;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
double trackStart = track->GetStartTime();
|
|
|
|
double t0 = mT0 < trackStart? trackStart: mT0;
|
|
|
|
sampleCount start = track->TimeToLongSamples(t0);
|
2013-05-21 01:58:11 +00:00
|
|
|
|
|
|
|
int analyzeSize = windowSize * numWindows;
|
|
|
|
float * buffer;
|
|
|
|
buffer = new float[analyzeSize];
|
|
|
|
|
|
|
|
float * freq;
|
|
|
|
freq = new float[windowSize/2];
|
|
|
|
|
|
|
|
float * freqa;
|
|
|
|
freqa = new float[windowSize/2];
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int i, j, argmax;
|
|
|
|
int lag;
|
|
|
|
|
|
|
|
for(j=0; j<windowSize/2; j++)
|
|
|
|
freqa[j] = 0;
|
|
|
|
|
|
|
|
track->Get((samplePtr) buffer, floatSample, start, analyzeSize);
|
|
|
|
for(i=0; i<numWindows; i++) {
|
|
|
|
ComputeSpectrum(buffer+i*windowSize, windowSize,
|
|
|
|
windowSize, rate, freq, true);
|
|
|
|
for(j=0; j<windowSize/2; j++)
|
|
|
|
freqa[j] += freq[j];
|
|
|
|
}
|
|
|
|
argmax=0;
|
|
|
|
for(j=1; j<windowSize/2; j++)
|
|
|
|
if (freqa[j] > freqa[argmax])
|
|
|
|
argmax = j;
|
2013-08-03 00:24:26 +00:00
|
|
|
|
|
|
|
delete [] freq;
|
|
|
|
delete [] freqa;
|
2013-10-27 20:49:58 +00:00
|
|
|
delete [] buffer;
|
2013-08-03 00:24:26 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
lag = (windowSize/2 - 1) - argmax;
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dStartFrequency = rate / lag;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangePitch::PromptUser()
|
|
|
|
{
|
|
|
|
this->DeduceFrequencies(); // Set frequency-related control values based on sample.
|
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
ChangePitchDialog dlog(this, mParent, m_dSemitonesChange, m_dStartFrequency);
|
2010-01-23 19:44:49 +00:00
|
|
|
dlog.CentreOnParent();
|
|
|
|
dlog.ShowModal();
|
|
|
|
|
|
|
|
if (dlog.GetReturnCode() == wxID_CANCEL)
|
|
|
|
return false;
|
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = dlog.m_dSemitonesChange;
|
|
|
|
m_dStartFrequency = dlog.m_FromFrequency;
|
|
|
|
m_dPercentChange = dlog.m_dPercentChange;
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangePitch::TransferParameters( Shuttle & shuttle )
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
// Vaughan, 2013-06: Long lost to history, I don't see why m_dPercentChange was chosen to be shuttled.
|
|
|
|
// Only m_dSemitonesChange is used in Process().
|
|
|
|
shuttle.TransferDouble(wxT("Percentage"),m_dPercentChange,0.0);
|
|
|
|
m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0);
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangePitch::Process()
|
|
|
|
{
|
|
|
|
mSoundTouch = new SoundTouch();
|
|
|
|
SetTimeWarper(new IdentityTimeWarper());
|
2013-06-19 02:25:21 +00:00
|
|
|
mSoundTouch->setPitchSemiTones((float)(m_dSemitonesChange));
|
2010-10-28 17:57:14 +00:00
|
|
|
#ifdef USE_MIDI
|
2013-06-19 02:25:21 +00:00
|
|
|
// Note: m_dSemitonesChange is private to ChangePitch because it only
|
2010-10-28 17:57:14 +00:00
|
|
|
// needs to pass it along to mSoundTouch (above). I added mSemitones
|
|
|
|
// to SoundTouchEffect (the super class) to convey this value
|
|
|
|
// to process Note tracks. This approach minimizes changes to existing
|
2013-06-19 02:25:21 +00:00
|
|
|
// code, but it would be cleaner to change all m_dSemitonesChange to
|
2010-10-28 17:57:14 +00:00
|
|
|
// mSemitones, make mSemitones exist with or without USE_MIDI, and
|
|
|
|
// eliminate the next line:
|
2013-06-19 02:25:21 +00:00
|
|
|
mSemitones = m_dSemitonesChange;
|
2010-10-28 17:57:14 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
return this->EffectSoundTouch::Process();
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// ChangePitchDialog
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
2013-06-25 23:43:55 +00:00
|
|
|
// Soundtouch is not reasonable below -99% or above 3000%.
|
|
|
|
// We warp the slider to go up to 400%, but user can enter up to 3000%
|
|
|
|
#define PERCENTCHANGE_MIN -99.0
|
|
|
|
#define PERCENTCHANGE_MAX_SLIDER 100.0 // warped above zero to actually go up to 400%
|
|
|
|
#define PERCENTCHANGE_MAX_TEXT 3000.0
|
2010-01-23 19:44:49 +00:00
|
|
|
#define PERCENTCHANGE_SLIDER_WARP 1.30105 // warp power takes max from 100 to 400.
|
|
|
|
|
|
|
|
enum {
|
|
|
|
ID_TEXT_PERCENTCHANGE = 10001,
|
|
|
|
ID_SLIDER_PERCENTCHANGE,
|
|
|
|
ID_CHOICE_FROMPITCH,
|
2013-06-22 00:49:13 +00:00
|
|
|
ID_SPIN_FROMOCTAVE,
|
2010-01-23 19:44:49 +00:00
|
|
|
ID_CHOICE_TOPITCH,
|
2013-06-22 00:49:13 +00:00
|
|
|
ID_SPIN_TOOCTAVE,
|
2010-01-23 19:44:49 +00:00
|
|
|
ID_TEXT_SEMITONESCHANGE,
|
|
|
|
ID_TEXT_FROMFREQUENCY,
|
|
|
|
ID_TEXT_TOFREQUENCY
|
|
|
|
};
|
|
|
|
|
|
|
|
// event table for ChangePitchDialog
|
|
|
|
|
|
|
|
BEGIN_EVENT_TABLE(ChangePitchDialog, EffectDialog)
|
|
|
|
EVT_CHOICE(ID_CHOICE_FROMPITCH, ChangePitchDialog::OnChoice_FromPitch)
|
2013-06-22 00:49:13 +00:00
|
|
|
EVT_TEXT(ID_SPIN_FROMOCTAVE, ChangePitchDialog::OnSpin_FromOctave)
|
2010-01-23 19:44:49 +00:00
|
|
|
EVT_CHOICE(ID_CHOICE_TOPITCH, ChangePitchDialog::OnChoice_ToPitch)
|
2013-06-22 00:49:13 +00:00
|
|
|
EVT_TEXT(ID_SPIN_TOOCTAVE, ChangePitchDialog::OnSpin_ToOctave)
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
EVT_TEXT(ID_TEXT_SEMITONESCHANGE, ChangePitchDialog::OnText_SemitonesChange)
|
|
|
|
|
|
|
|
EVT_TEXT(ID_TEXT_FROMFREQUENCY, ChangePitchDialog::OnText_FromFrequency)
|
|
|
|
EVT_TEXT(ID_TEXT_TOFREQUENCY, ChangePitchDialog::OnText_ToFrequency)
|
|
|
|
|
|
|
|
EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangePitchDialog::OnText_PercentChange)
|
|
|
|
EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangePitchDialog::OnSlider_PercentChange)
|
|
|
|
|
|
|
|
EVT_BUTTON(ID_EFFECT_PREVIEW, ChangePitchDialog::OnPreview)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
ChangePitchDialog::ChangePitchDialog(EffectChangePitch *effect, wxWindow *parent,
|
|
|
|
double dSemitonesChange, double dStartFrequency)
|
2010-01-23 19:44:49 +00:00
|
|
|
: EffectDialog(parent, _("Change Pitch"), PROCESS_EFFECT),
|
|
|
|
mEffect(effect)
|
|
|
|
{
|
2013-06-18 04:35:35 +00:00
|
|
|
m_bLoopDetect = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// NULL out these control members because there are some cases where the
|
|
|
|
// event table handlers get called during this method, and those handlers that
|
|
|
|
// can cause trouble check for NULL.
|
|
|
|
m_pChoice_FromPitch = NULL;
|
2013-06-18 04:35:35 +00:00
|
|
|
m_pSpin_FromOctave = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
m_pChoice_ToPitch = NULL;
|
2013-06-18 04:35:35 +00:00
|
|
|
m_pSpin_ToOctave = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_pTextCtrl_SemitonesChange = NULL;
|
|
|
|
|
|
|
|
m_pTextCtrl_FromFrequency = NULL;
|
|
|
|
m_pTextCtrl_ToFrequency = NULL;
|
|
|
|
|
|
|
|
m_pTextCtrl_PercentChange = NULL;
|
|
|
|
m_pSlider_PercentChange = NULL;
|
|
|
|
|
|
|
|
// effect parameters
|
2013-06-19 02:25:21 +00:00
|
|
|
double dFromMIDInote = FreqToMIDInote(dStartFrequency);
|
|
|
|
double dToMIDInote = dFromMIDInote + dSemitonesChange;
|
|
|
|
m_nFromPitch = PitchIndex(dFromMIDInote);
|
|
|
|
m_nFromOctave = PitchOctave(dFromMIDInote);
|
|
|
|
m_nToPitch = PitchIndex(dToMIDInote);
|
|
|
|
m_nToOctave = PitchOctave(dToMIDInote);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = dSemitonesChange;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
m_FromFrequency = dStartFrequency;
|
|
|
|
this->Calc_PercentChange();
|
|
|
|
this->Calc_ToFrequency();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::PopulateOrExchange(ShuttleGui & S)
|
|
|
|
{
|
|
|
|
wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST);
|
|
|
|
wxTextValidator numvld(wxFILTER_NUMERIC);
|
2013-05-12 01:06:09 +00:00
|
|
|
|
|
|
|
wxTextValidator nonNegNumValidator(wxFILTER_INCLUDE_CHAR_LIST); // like wxFILTER_NUMERIC, but disallow negative numbers.
|
|
|
|
wxArrayString aIncludes;
|
|
|
|
aIncludes.Add(wxT("0"));
|
|
|
|
aIncludes.Add(wxT("1"));
|
|
|
|
aIncludes.Add(wxT("2"));
|
|
|
|
aIncludes.Add(wxT("3"));
|
|
|
|
aIncludes.Add(wxT("4"));
|
|
|
|
aIncludes.Add(wxT("5"));
|
|
|
|
aIncludes.Add(wxT("6"));
|
|
|
|
aIncludes.Add(wxT("7"));
|
|
|
|
aIncludes.Add(wxT("8"));
|
|
|
|
aIncludes.Add(wxT("9"));
|
|
|
|
aIncludes.Add(wxT("."));
|
|
|
|
nonNegNumValidator.SetIncludes(aIncludes);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
wxArrayString pitch;
|
2010-01-23 19:44:49 +00:00
|
|
|
pitch.Add(wxT("C"));
|
|
|
|
pitch.Add(wxT("C#/Db"));
|
|
|
|
pitch.Add(wxT("D"));
|
|
|
|
pitch.Add(wxT("D#/Eb"));
|
|
|
|
pitch.Add(wxT("E"));
|
|
|
|
pitch.Add(wxT("F"));
|
|
|
|
pitch.Add(wxT("F#/Gb"));
|
|
|
|
pitch.Add(wxT("G"));
|
|
|
|
pitch.Add(wxT("G#/Ab"));
|
|
|
|
pitch.Add(wxT("A"));
|
|
|
|
pitch.Add(wxT("A#/Bb"));
|
|
|
|
pitch.Add(wxT("B"));
|
|
|
|
|
2013-06-20 02:28:55 +00:00
|
|
|
S.SetBorder(5);
|
|
|
|
|
|
|
|
S.StartVerticalLay();
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-06-21 22:09:20 +00:00
|
|
|
S.AddTitle(_("Change Pitch without Changing Tempo"));
|
2013-06-20 02:28:55 +00:00
|
|
|
S.AddTitle(
|
|
|
|
wxString::Format(_("Estimated Start Pitch: %s%d (%.3f Hz)"),
|
2013-06-21 22:09:20 +00:00
|
|
|
pitch[m_nFromPitch].c_str(), m_nFromOctave, m_FromFrequency));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-06-20 02:28:55 +00:00
|
|
|
S.EndVerticalLay();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-05-13 01:22:14 +00:00
|
|
|
/* i18n-hint: (noun) Musical pitch.*/
|
2013-12-14 02:07:06 +00:00
|
|
|
S.StartStatic(_("Pitch"));
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-08-25 05:20:30 +00:00
|
|
|
S.StartMultiColumn(6, wxALIGN_CENTER); // 6 controls, because each AddChoice adds a wxStaticText and a wxChoice.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pChoice_FromPitch = S.Id(ID_CHOICE_FROMPITCH).AddChoice(_("from"), wxT(""), &pitch);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pChoice_FromPitch->SetName(_("from"));
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pChoice_FromPitch->SetSizeHints(80, -1);
|
|
|
|
|
2013-06-22 00:49:13 +00:00
|
|
|
m_pSpin_FromOctave = S.Id(ID_SPIN_FROMOCTAVE).AddSpinCtrl(wxT(""), m_nFromOctave, INT_MAX, INT_MIN);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pSpin_FromOctave->SetName(_("from Octave"));
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pSpin_FromOctave->SetSizeHints(50, -1);
|
|
|
|
|
|
|
|
m_pChoice_ToPitch = S.Id(ID_CHOICE_TOPITCH).AddChoice(_("to"), wxT(""), &pitch);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pChoice_ToPitch->SetName(_("to"));
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pChoice_ToPitch->SetSizeHints(80, -1);
|
|
|
|
|
|
|
|
m_pSpin_ToOctave =
|
2013-06-22 00:49:13 +00:00
|
|
|
S.Id(ID_SPIN_TOOCTAVE).AddSpinCtrl(wxT(""), m_nToOctave, INT_MAX, INT_MIN);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pSpin_ToOctave->SetName(_("to Octave"));
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pSpin_ToOctave->SetSizeHints(50, -1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-05-13 01:22:14 +00:00
|
|
|
S.EndMultiColumn();
|
|
|
|
|
2013-06-20 02:28:55 +00:00
|
|
|
S.StartHorizontalLay(wxALIGN_CENTER);
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pTextCtrl_SemitonesChange =
|
|
|
|
S.Id(ID_TEXT_SEMITONESCHANGE).AddTextBox(_("Semitones (half-steps):"), wxT(""), 12);
|
|
|
|
m_pTextCtrl_SemitonesChange->SetName(_("Semitones (half-steps)"));
|
|
|
|
m_pTextCtrl_SemitonesChange->SetValidator(numvld);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-06-20 02:28:55 +00:00
|
|
|
S.EndHorizontalLay();
|
2013-05-13 01:22:14 +00:00
|
|
|
}
|
|
|
|
S.EndStatic();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-12-14 02:07:06 +00:00
|
|
|
S.StartStatic(_("Frequency"));
|
2013-05-13 01:22:14 +00:00
|
|
|
{
|
2013-06-20 02:28:55 +00:00
|
|
|
S.StartMultiColumn(5, wxALIGN_CENTER); // 5, because AddTextBox adds a wxStaticText and a wxTextCtrl.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pTextCtrl_FromFrequency = S.Id(ID_TEXT_FROMFREQUENCY).AddTextBox(_("from"), wxT(""), 12);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pTextCtrl_FromFrequency->SetName(_("from (Hz)"));
|
2013-05-13 00:07:01 +00:00
|
|
|
m_pTextCtrl_FromFrequency->SetValidator(nonNegNumValidator);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-20 02:28:55 +00:00
|
|
|
m_pTextCtrl_ToFrequency = S.Id(ID_TEXT_TOFREQUENCY).AddTextBox(_("to"), wxT(""), 12);
|
2013-08-25 05:20:30 +00:00
|
|
|
m_pTextCtrl_ToFrequency->SetName(_("to (Hz)"));
|
2013-05-12 01:06:09 +00:00
|
|
|
m_pTextCtrl_ToFrequency->SetValidator(nonNegNumValidator);
|
2013-06-20 02:28:55 +00:00
|
|
|
|
|
|
|
S.AddUnits(_("Hz"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-06-20 02:28:55 +00:00
|
|
|
S.EndMultiColumn();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-20 02:28:55 +00:00
|
|
|
S.StartHorizontalLay(wxALIGN_CENTER);
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-12-14 02:07:06 +00:00
|
|
|
m_pTextCtrl_PercentChange = S.Id(ID_TEXT_PERCENTCHANGE).AddTextBox(_("Percent Change:"), wxT(""), 12);
|
2010-01-23 19:44:49 +00:00
|
|
|
m_pTextCtrl_PercentChange->SetValidator(numvld);
|
|
|
|
}
|
|
|
|
S.EndHorizontalLay();
|
|
|
|
|
2013-05-13 01:22:14 +00:00
|
|
|
S.StartHorizontalLay(wxEXPAND);
|
|
|
|
{
|
|
|
|
S.SetStyle(wxSL_HORIZONTAL);
|
|
|
|
m_pSlider_PercentChange = S.Id(ID_SLIDER_PERCENTCHANGE)
|
2013-06-25 23:43:55 +00:00
|
|
|
.AddSlider(wxT(""), 0, (int)PERCENTCHANGE_MAX_SLIDER, (int)PERCENTCHANGE_MIN);
|
2013-05-13 01:22:14 +00:00
|
|
|
m_pSlider_PercentChange->SetName(_("Percent Change"));
|
|
|
|
}
|
|
|
|
S.EndHorizontalLay();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-05-13 01:22:14 +00:00
|
|
|
S.EndStatic();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool ChangePitchDialog::TransferDataToWindow()
|
|
|
|
{
|
|
|
|
m_bLoopDetect = true;
|
|
|
|
|
|
|
|
// from/to pitch controls
|
|
|
|
if (m_pChoice_FromPitch)
|
2013-06-18 04:35:35 +00:00
|
|
|
m_pChoice_FromPitch->SetSelection(m_nFromPitch);
|
|
|
|
if (m_pSpin_FromOctave)
|
|
|
|
m_pSpin_FromOctave->SetValue(m_nFromOctave);
|
|
|
|
this->Update_Choice_ToPitch();
|
|
|
|
this->Update_Spin_ToOctave();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// semitones change control
|
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
|
|
|
|
// from/to frequency controls
|
|
|
|
if (m_pTextCtrl_FromFrequency) {
|
|
|
|
wxString str;
|
2013-05-18 00:09:43 +00:00
|
|
|
if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX))
|
2010-01-23 19:44:49 +00:00
|
|
|
str.Printf(wxT("%.3f"), m_FromFrequency);
|
|
|
|
else
|
|
|
|
str = wxT("");
|
|
|
|
m_pTextCtrl_FromFrequency->SetValue(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
|
|
|
|
// percent change controls
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
|
|
|
|
m_bLoopDetect = false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChangePitchDialog::TransferDataFromWindow()
|
|
|
|
{
|
|
|
|
double newDouble;
|
|
|
|
wxString str;
|
|
|
|
|
|
|
|
|
|
|
|
// from/to pitch controls
|
|
|
|
if (m_pChoice_FromPitch)
|
2013-06-18 04:35:35 +00:00
|
|
|
m_nFromPitch = m_pChoice_FromPitch->GetSelection();
|
|
|
|
if (m_pSpin_FromOctave)
|
|
|
|
m_nFromOctave = m_pSpin_FromOctave->GetValue();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (m_pChoice_ToPitch)
|
2013-06-18 04:35:35 +00:00
|
|
|
m_nToPitch = m_pChoice_ToPitch->GetSelection();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
// semitones change control
|
|
|
|
if (m_pTextCtrl_SemitonesChange) {
|
|
|
|
str = m_pTextCtrl_SemitonesChange->GetValue();
|
|
|
|
str.ToDouble(&newDouble);
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = newDouble;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// from/to frequency controls
|
|
|
|
if (m_pTextCtrl_FromFrequency) {
|
|
|
|
str = m_pTextCtrl_FromFrequency->GetValue();
|
|
|
|
str.ToDouble(&newDouble);
|
|
|
|
m_FromFrequency = newDouble;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_pTextCtrl_ToFrequency) {
|
|
|
|
str = m_pTextCtrl_ToFrequency->GetValue();
|
|
|
|
str.ToDouble(&newDouble);
|
|
|
|
m_ToFrequency = newDouble;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// percent change controls
|
|
|
|
if (m_pTextCtrl_PercentChange) {
|
|
|
|
str = m_pTextCtrl_PercentChange->GetValue();
|
|
|
|
str.ToDouble(&newDouble);
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dPercentChange = newDouble;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-05-14 02:49:56 +00:00
|
|
|
// No need to update Slider_PercentChange here because TextCtrl_PercentChange
|
2010-01-23 19:44:49 +00:00
|
|
|
// always tracks it & is more precise (decimal points).
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// calculations
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
void ChangePitchDialog::Calc_ToPitch()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-05-14 02:49:56 +00:00
|
|
|
int nSemitonesChange =
|
2013-06-19 02:25:21 +00:00
|
|
|
(int)(m_dSemitonesChange + ((m_dSemitonesChange < 0.0) ? -0.5 : 0.5));
|
2013-06-18 04:35:35 +00:00
|
|
|
m_nToPitch = (m_nFromPitch + nSemitonesChange) % 12;
|
|
|
|
if (m_nToPitch < 0)
|
|
|
|
m_nToPitch += 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Calc_ToOctave()
|
|
|
|
{
|
|
|
|
m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Calc_SemitonesChange_fromPitches()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange =
|
2013-06-18 04:35:35 +00:00
|
|
|
PitchToMIDInote(m_nToPitch, m_nToOctave) - PitchToMIDInote(m_nFromPitch, m_nFromOctave);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Calc_SemitonesChange_fromPercentChange()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
// Use m_dPercentChange rather than m_FromFrequency & m_ToFrequency, because
|
|
|
|
// they start out uninitialized, but m_dPercentChange is always valid.
|
|
|
|
m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-05-28 01:50:59 +00:00
|
|
|
void ChangePitchDialog::Calc_ToFrequency()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
m_ToFrequency = (m_FromFrequency * (100.0 + m_dPercentChange)) / 100.0;
|
2013-05-28 01:50:59 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void ChangePitchDialog::Calc_PercentChange()
|
|
|
|
{
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dPercentChange = 100.0 * (pow(2.0, (m_dSemitonesChange / 12.0)) - 1.0);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handlers
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pChoice_FromPitch) {
|
2013-06-18 04:35:35 +00:00
|
|
|
m_nFromPitch = m_pChoice_FromPitch->GetSelection();
|
|
|
|
m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Calc_ToPitch();
|
|
|
|
this->Calc_ToFrequency();
|
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
|
|
|
{
|
|
|
|
this->Update_Choice_ToPitch();
|
|
|
|
this->Update_Spin_ToOctave();
|
|
|
|
this->Update_Text_FromFrequency();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
}
|
|
|
|
m_bLoopDetect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::OnSpin_FromOctave(wxCommandEvent & WXUNUSED(event))
|
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pSpin_FromOctave)
|
|
|
|
{
|
|
|
|
m_nFromOctave = m_pSpin_FromOctave->GetValue();
|
2013-06-19 02:25:21 +00:00
|
|
|
//vvv If I change this code to not keep semitones and percent constant,
|
|
|
|
// will need validation code as in OnSpin_ToOctave.
|
2013-06-18 04:35:35 +00:00
|
|
|
m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
|
|
|
|
|
|
|
|
this->Calc_ToFrequency();
|
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Spin_ToOctave();
|
|
|
|
this->Update_Text_FromFrequency();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
if (m_pChoice_ToPitch)
|
|
|
|
{
|
|
|
|
m_nToPitch = m_pChoice_ToPitch->GetSelection();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
this->Calc_SemitonesChange_fromPitches();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
|
|
|
|
this->Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
}
|
|
|
|
m_bLoopDetect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::OnSpin_ToOctave(wxCommandEvent & WXUNUSED(event))
|
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pSpin_ToOctave)
|
|
|
|
{
|
|
|
|
int nNewValue = m_pSpin_ToOctave->GetValue();
|
|
|
|
// Validation: Rather than set a range for octave numbers, enforce a range that
|
2013-06-25 23:43:55 +00:00
|
|
|
// keeps m_dPercentChange above -99%, per Soundtouch constraints.
|
2013-06-18 04:35:35 +00:00
|
|
|
if ((nNewValue + 3) < m_nFromOctave)
|
|
|
|
{
|
|
|
|
::wxBell();
|
|
|
|
m_pSpin_ToOctave->SetValue(m_nFromOctave - 3);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_nToOctave = nNewValue;
|
|
|
|
|
|
|
|
m_ToFrequency = PitchToFreq(m_nToPitch, m_nToOctave);
|
|
|
|
|
|
|
|
this->Calc_SemitonesChange_fromPitches();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
|
2013-06-18 04:35:35 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
|
|
|
{
|
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pTextCtrl_SemitonesChange) {
|
|
|
|
wxString str = m_pTextCtrl_SemitonesChange->GetValue();
|
2013-05-18 00:09:43 +00:00
|
|
|
if (str.IsEmpty())
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = 0.0;
|
2013-05-18 00:09:43 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
double newValue = 0;
|
|
|
|
str.ToDouble(&newValue);
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dSemitonesChange = newValue;
|
2013-05-18 00:09:43 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
this->Calc_PercentChange();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Calc_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Choice_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_ToOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
2013-05-18 00:09:43 +00:00
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
// If m_dSemitonesChange is a big enough negative, we can go to or below 0 freq.
|
|
|
|
// If m_dSemitonesChange is a big enough positive, we can go to 1.#INF (Windows) or inf (Linux).
|
2013-05-22 04:32:43 +00:00
|
|
|
// But practically, these are best limits for Soundtouch.
|
2013-06-19 02:25:21 +00:00
|
|
|
bool bIsGoodValue = (m_dSemitonesChange > -80.0) && (m_dSemitonesChange <= 60.0);
|
2013-05-22 04:32:43 +00:00
|
|
|
this->FindWindow(wxID_OK)->Enable(bIsGoodValue);
|
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnText_FromFrequency(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pTextCtrl_FromFrequency) {
|
|
|
|
wxString str = m_pTextCtrl_FromFrequency->GetValue();
|
|
|
|
double newDouble;
|
|
|
|
str.ToDouble(&newDouble);
|
2013-05-21 02:35:29 +00:00
|
|
|
// Empty string causes unpredictable results with ToDouble() and later calculations.
|
|
|
|
// Non-positive frequency makes no sense, but user might still be editing,
|
2013-05-18 00:09:43 +00:00
|
|
|
// so it's not an error, but we do not want to update the values/controls.
|
2013-05-22 04:32:43 +00:00
|
|
|
if (str.IsEmpty() || (newDouble <= 0.0) || (newDouble > DBL_MAX))
|
2013-05-18 00:09:43 +00:00
|
|
|
{
|
|
|
|
this->FindWindow(wxID_OK)->Disable();
|
2013-05-21 02:35:29 +00:00
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Disable();
|
2013-05-18 00:09:43 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_FromFrequency = newDouble;
|
|
|
|
|
2013-06-20 03:53:57 +00:00
|
|
|
double newFromMIDInote = FreqToMIDInote(m_FromFrequency);
|
|
|
|
m_nFromPitch = PitchIndex(newFromMIDInote);
|
|
|
|
m_nFromOctave = PitchOctave(newFromMIDInote);
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Calc_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Calc_ToFrequency();
|
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Choice_FromPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_FromOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Choice_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_ToOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
2013-05-18 00:09:43 +00:00
|
|
|
|
2013-05-22 04:32:43 +00:00
|
|
|
// Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
|
2013-05-18 00:09:43 +00:00
|
|
|
this->FindWindow(wxID_OK)->Enable();
|
2013-05-22 04:32:43 +00:00
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Enable();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnText_ToFrequency(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pTextCtrl_ToFrequency) {
|
|
|
|
wxString str = m_pTextCtrl_ToFrequency->GetValue();
|
|
|
|
double newDouble;
|
|
|
|
str.ToDouble(&newDouble);
|
2013-05-21 02:35:29 +00:00
|
|
|
// Empty string causes unpredictable results with ToDouble() and later calculations.
|
|
|
|
// Non-positive frequency makes no sense, but user might still be editing,
|
2013-05-18 00:09:43 +00:00
|
|
|
// so it's not an error, but we do not want to update the values/controls.
|
2013-05-21 02:35:29 +00:00
|
|
|
if (str.IsEmpty() || (newDouble <= 0.0) )
|
2013-05-18 00:09:43 +00:00
|
|
|
{
|
|
|
|
this->FindWindow(wxID_OK)->Disable();
|
2013-05-21 02:35:29 +00:00
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Disable();
|
2013-05-18 00:09:43 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_ToFrequency = newDouble;
|
|
|
|
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dPercentChange = (((double)(m_ToFrequency) * 100.0) /
|
2010-01-23 19:44:49 +00:00
|
|
|
(double)(m_FromFrequency)) - 100.0;
|
|
|
|
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
this->Calc_SemitonesChange_fromPercentChange();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Choice_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_ToOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
2013-05-18 00:09:43 +00:00
|
|
|
|
2013-06-25 23:43:55 +00:00
|
|
|
// Success. Make sure OK and Preview are disabled if percent change is out of bounds.
|
|
|
|
// Can happen while editing.
|
|
|
|
// If the value is good, might also need to re-enable because of above clause.
|
|
|
|
bool bIsGoodValue = (m_dPercentChange > PERCENTCHANGE_MIN) && (m_dPercentChange <= PERCENTCHANGE_MAX_TEXT);
|
|
|
|
this->FindWindow(wxID_OK)->Enable(bIsGoodValue);
|
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pTextCtrl_PercentChange) {
|
|
|
|
wxString str = m_pTextCtrl_PercentChange->GetValue();
|
|
|
|
double newValue = 0;
|
|
|
|
str.ToDouble(&newValue);
|
2013-05-22 04:32:43 +00:00
|
|
|
// User might still be editing, so out of bounds is not an error,
|
|
|
|
// but we do not want to update the values/controls.
|
2013-06-25 23:43:55 +00:00
|
|
|
if (str.IsEmpty() || (newValue < PERCENTCHANGE_MIN) || (newValue > PERCENTCHANGE_MAX_TEXT))
|
2013-05-18 00:09:43 +00:00
|
|
|
{
|
|
|
|
this->FindWindow(wxID_OK)->Disable();
|
2013-05-22 04:32:43 +00:00
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Disable();
|
2013-05-18 00:09:43 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dPercentChange = newValue;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
this->Calc_SemitonesChange_fromPercentChange();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
|
2010-01-23 19:44:49 +00:00
|
|
|
this->Calc_ToFrequency();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Choice_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_ToOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
this->Update_Slider_PercentChange();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
|
|
|
|
2013-05-22 04:32:43 +00:00
|
|
|
// Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
|
|
|
|
this->FindWindow(wxID_OK)->Enable();
|
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Enable();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (m_bLoopDetect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_pSlider_PercentChange) {
|
2013-06-19 02:25:21 +00:00
|
|
|
m_dPercentChange = (double)(m_pSlider_PercentChange->GetValue());
|
2010-01-23 19:44:49 +00:00
|
|
|
// Warp positive values to actually go up faster & further than negatives.
|
2013-06-19 02:25:21 +00:00
|
|
|
if (m_dPercentChange > 0.0)
|
|
|
|
m_dPercentChange = pow(m_dPercentChange, PERCENTCHANGE_SLIDER_WARP);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
this->Calc_SemitonesChange_fromPercentChange();
|
2013-06-19 02:25:21 +00:00
|
|
|
this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
|
2010-01-23 19:44:49 +00:00
|
|
|
this->Calc_ToFrequency();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
m_bLoopDetect = true;
|
2013-06-18 04:35:35 +00:00
|
|
|
{
|
|
|
|
this->Update_Choice_ToPitch();
|
2013-06-20 03:53:57 +00:00
|
|
|
this->Update_Spin_ToOctave();
|
2013-06-18 04:35:35 +00:00
|
|
|
this->Update_Text_SemitonesChange();
|
|
|
|
this->Update_Text_ToFrequency();
|
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
m_bLoopDetect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 01:06:09 +00:00
|
|
|
void ChangePitchDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
TransferDataFromWindow();
|
|
|
|
|
|
|
|
// Save & restore parameters around Preview, because we didn't do OK.
|
2013-06-19 02:25:21 +00:00
|
|
|
double oldSemitonesChange = m_dSemitonesChange;
|
2013-06-25 23:43:55 +00:00
|
|
|
if( m_dPercentChange < PERCENTCHANGE_MIN)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-06-25 23:43:55 +00:00
|
|
|
m_dPercentChange = PERCENTCHANGE_MIN;
|
2010-01-23 19:44:49 +00:00
|
|
|
this->Update_Text_PercentChange();
|
|
|
|
}
|
2013-06-19 02:25:21 +00:00
|
|
|
mEffect->m_dSemitonesChange = m_dSemitonesChange;
|
2010-01-23 19:44:49 +00:00
|
|
|
mEffect->Preview();
|
2013-06-19 02:25:21 +00:00
|
|
|
mEffect->m_dSemitonesChange = oldSemitonesChange;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-05-28 01:50:59 +00:00
|
|
|
// helper fns for controls
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Choice_FromPitch()
|
|
|
|
{
|
|
|
|
if (m_pChoice_FromPitch)
|
2013-06-18 04:35:35 +00:00
|
|
|
m_pChoice_FromPitch->SetSelection(m_nFromPitch);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Spin_FromOctave()
|
|
|
|
{
|
|
|
|
if (m_pSpin_FromOctave)
|
|
|
|
m_pSpin_FromOctave->SetValue(m_nFromOctave);
|
2013-05-28 01:50:59 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Choice_ToPitch()
|
|
|
|
{
|
|
|
|
if (m_pChoice_ToPitch)
|
2013-06-18 04:35:35 +00:00
|
|
|
m_pChoice_ToPitch->SetSelection(m_nToPitch);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Spin_ToOctave()
|
|
|
|
{
|
|
|
|
if (m_pSpin_ToOctave)
|
|
|
|
m_pSpin_ToOctave->SetValue(m_nToOctave);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Text_SemitonesChange()
|
|
|
|
{
|
|
|
|
if (m_pTextCtrl_SemitonesChange) {
|
|
|
|
wxString str;
|
2013-06-19 02:25:21 +00:00
|
|
|
str.Printf(wxT("%.2f"), m_dSemitonesChange);
|
2010-01-23 19:44:49 +00:00
|
|
|
m_pTextCtrl_SemitonesChange->SetValue(str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-18 04:35:35 +00:00
|
|
|
void ChangePitchDialog::Update_Text_FromFrequency()
|
|
|
|
{
|
|
|
|
if (m_pTextCtrl_FromFrequency) {
|
|
|
|
wxString str;
|
|
|
|
if ((m_FromFrequency > 0.0) && (m_FromFrequency <= DBL_MAX))
|
|
|
|
str.Printf(wxT("%.3f"), m_FromFrequency);
|
|
|
|
else
|
|
|
|
str = wxT("");
|
|
|
|
m_pTextCtrl_FromFrequency->SetValue(str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void ChangePitchDialog::Update_Text_ToFrequency()
|
|
|
|
{
|
|
|
|
if (m_pTextCtrl_ToFrequency) {
|
|
|
|
wxString str;
|
2013-05-18 00:09:43 +00:00
|
|
|
if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX))
|
2010-01-23 19:44:49 +00:00
|
|
|
str.Printf(wxT("%.3f"), m_ToFrequency);
|
|
|
|
else
|
|
|
|
str = wxT("");
|
|
|
|
m_pTextCtrl_ToFrequency->SetValue(str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Text_PercentChange()
|
|
|
|
{
|
|
|
|
if (m_pTextCtrl_PercentChange) {
|
|
|
|
wxString str;
|
2013-05-18 00:09:43 +00:00
|
|
|
if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX))
|
2013-06-19 02:25:21 +00:00
|
|
|
str.Printf(wxT("%.3f"), m_dPercentChange);
|
2013-05-18 00:09:43 +00:00
|
|
|
else
|
|
|
|
str = wxT("");
|
2010-01-23 19:44:49 +00:00
|
|
|
m_pTextCtrl_PercentChange->SetValue(str);
|
2013-05-22 04:32:43 +00:00
|
|
|
|
2013-06-25 23:43:55 +00:00
|
|
|
bool bIsGoodValue = (m_dPercentChange >= PERCENTCHANGE_MIN) && (m_dPercentChange <= PERCENTCHANGE_MAX_TEXT);
|
2013-05-22 04:32:43 +00:00
|
|
|
this->FindWindow(wxID_OK)->Enable(bIsGoodValue);
|
|
|
|
this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChangePitchDialog::Update_Slider_PercentChange()
|
|
|
|
{
|
|
|
|
if (m_pSlider_PercentChange) {
|
2013-06-19 02:25:21 +00:00
|
|
|
double unwarped = m_dPercentChange;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (unwarped > 0.0)
|
2013-06-25 23:43:55 +00:00
|
|
|
// Un-warp values above zero to actually go up to PERCENTCHANGE_MAX_SLIDER.
|
2013-06-19 02:25:21 +00:00
|
|
|
unwarped = pow(m_dPercentChange, (1.0 / PERCENTCHANGE_SLIDER_WARP));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Add 0.5 to unwarped so trunc -> round.
|
2013-05-18 00:09:43 +00:00
|
|
|
int newSetting = (int)(unwarped + 0.5);
|
|
|
|
if (newSetting < PERCENTCHANGE_MIN)
|
2013-06-25 23:43:55 +00:00
|
|
|
newSetting = (int)PERCENTCHANGE_MIN;
|
|
|
|
if (newSetting > PERCENTCHANGE_MAX_SLIDER)
|
|
|
|
newSetting = (int)PERCENTCHANGE_MAX_SLIDER;
|
2013-05-18 00:09:43 +00:00
|
|
|
m_pSlider_PercentChange->SetValue(newSetting);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif // USE_SOUNDTOUCH
|
|
|
|
|