1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-17 16:50:26 +02:00

Improvements in Change Pitch effect.

* from and to octave spin controls and cross-updating

* new conversion utilities in PitchName.*

* code simplification, new documentation

* stop a few compiler warnings in FreqWindow.cpp on unused params
This commit is contained in:
v.audacity 2013-06-18 04:35:35 +00:00
parent 741d283276
commit b327e510b0
6 changed files with 299 additions and 167 deletions

View File

@ -440,7 +440,7 @@ void FreqWindow::GetAudio()
//wxLogDebug(wxT("Leaving FreqWindow::GetAudio()")); //wxLogDebug(wxT("Leaving FreqWindow::GetAudio()"));
} }
void FreqWindow::OnSize(wxSizeEvent & event) void FreqWindow::OnSize(wxSizeEvent & WXUNUSED(event))
{ {
Layout(); Layout();
@ -616,7 +616,7 @@ void FreqWindow::PlotMouseEvent(wxMouseEvent & event)
} }
} }
void FreqWindow::OnAlgChoice(wxCommandEvent & event) void FreqWindow::OnAlgChoice(wxCommandEvent & WXUNUSED(event))
{ {
// Log-frequency axis works for spectrum plots only. // Log-frequency axis works for spectrum plots only.
if (mAlgChoice->GetSelection() == 0) { if (mAlgChoice->GetSelection() == 0) {
@ -630,17 +630,17 @@ void FreqWindow::OnAlgChoice(wxCommandEvent & event)
Recalc(); Recalc();
} }
void FreqWindow::OnSizeChoice(wxCommandEvent & event) void FreqWindow::OnSizeChoice(wxCommandEvent & WXUNUSED(event))
{ {
Recalc(); Recalc();
} }
void FreqWindow::OnFuncChoice(wxCommandEvent & event) void FreqWindow::OnFuncChoice(wxCommandEvent & WXUNUSED(event))
{ {
Recalc(); Recalc();
} }
void FreqWindow::OnAxisChoice(wxCommandEvent & event) void FreqWindow::OnAxisChoice(wxCommandEvent & WXUNUSED(event))
{ {
mLogAxis = (mAxisChoice->GetSelection())?true:false; mLogAxis = (mAxisChoice->GetSelection())?true:false;
@ -868,8 +868,8 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
const wxChar *pp; const wxChar *pp;
if (alg == 0) { if (alg == 0) {
xpitch = PitchName_Absolute(FreqToMIDInoteNumber(xPos)); xpitch = PitchName_Absolute(FreqToMIDInote(xPos));
peakpitch = PitchName_Absolute(FreqToMIDInoteNumber(bestpeak)); peakpitch = PitchName_Absolute(FreqToMIDInote(bestpeak));
xp = xpitch.c_str(); xp = xpitch.c_str();
pp = peakpitch.c_str(); pp = peakpitch.c_str();
/* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#*/ /* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#*/
@ -878,8 +878,8 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
int (value + 0.5), int (bestpeak + 0.5), int (value + 0.5), int (bestpeak + 0.5),
pp, bestValue); pp, bestValue);
} else if (xPos > 0.0 && bestpeak > 0.0) { } else if (xPos > 0.0 && bestpeak > 0.0) {
xpitch = PitchName_Absolute(FreqToMIDInoteNumber(1.0 / xPos)); xpitch = PitchName_Absolute(FreqToMIDInote(1.0 / xPos));
peakpitch = PitchName_Absolute(FreqToMIDInoteNumber(1.0 / bestpeak)); peakpitch = PitchName_Absolute(FreqToMIDInote(1.0 / bestpeak));
xp = xpitch.c_str(); xp = xpitch.c_str();
pp = peakpitch.c_str(); pp = peakpitch.c_str();
/* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A# /* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#
@ -1287,7 +1287,7 @@ FreqPlot::FreqPlot(wxWindow * parent, wxWindowID id,
freqWindow = (FreqWindow *) parent; freqWindow = (FreqWindow *) parent;
} }
void FreqPlot::OnErase(wxEraseEvent &evt) void FreqPlot::OnErase(wxEraseEvent & WXUNUSED(event))
{ {
// Ignore it to prevent flashing // Ignore it to prevent flashing
} }

View File

@ -23,42 +23,36 @@
#include "PitchName.h" #include "PitchName.h"
// FreqToMIDInoteNumber takes a frequency in Hz (exponential scale relative to double FreqToMIDInote(const double freq)
// alphabetic pitch names) and returns a pitch ID number (linear
// scale), such that A440 (A4) is 69, middle C (C4) is 60, etc.
// Each register starts with C (e.g., for middle C and A440,
// it's register 4).
double FreqToMIDInoteNumber(double freq)
{ {
// Make the calculation relative to A440 (A4), note number 69. // Make the calculation relative to A440 (A4), note number 69.
return (69.0 + (12.0 * (log(freq / 440.0) / log(2.0)))); return (69.0 + (12.0 * (log(freq / 440.0) / log(2.0))));
} }
// PitchIndex returns the [0,11] index for a double pitchNum, unsigned int PitchIndex(const double dMIDInote)
// as per result from FreqToMIDInoteNumber, corresponding to modulo 12
// of the integer part of (pitchNum + 0.5), so 0=C, 1=C#, etc.
unsigned int PitchIndex(double pitchNum)
{ {
int nPitchIndex = ((int)(pitchNum + 0.5) % 12); int nPitchIndex = ((int)(dMIDInote + 0.5) % 12);
// MIDI numbers (pitchNum) can be negative. // MIDI numbers can be negative.
// Because of the modulo, we know we're within 12 of positive. // Because of the modulo, we know we're within 12 of positive.
if (nPitchIndex < 0) if (nPitchIndex < 0)
nPitchIndex += 12; nPitchIndex += 12;
return nPitchIndex; return nPitchIndex;
} }
int PitchOctave(const double dMIDInote)
{
return ((int)((dMIDInote + 0.5) / 12) - 1);
}
wxChar gPitchName[10]; wxChar gPitchName[10];
wxChar * pPitchName; wxChar * pPitchName;
// PitchName takes pitchNum (as per result from wxChar * PitchName(const double dMIDInote, const bool bWantFlats /* = false */)
// FreqToMIDInoteNumber) and returns a standard pitch/note name [C, C#, etc.).
// Sharps are the default, unless, bWantFlats is true.
wxChar * PitchName(double pitchNum, bool bWantFlats /* = false */)
{ {
pPitchName = gPitchName; pPitchName = gPitchName;
switch (PitchIndex(pitchNum)) { switch (PitchIndex(dMIDInote)) {
case 0: case 0:
*pPitchName++ = wxT('C'); *pPitchName++ = wxT('C');
break; break;
@ -132,20 +126,23 @@ wxChar * PitchName(double pitchNum, bool bWantFlats /* = false */)
return gPitchName; return gPitchName;
} }
// PitchName_Absolute does the same thing as PitchName, but appends wxChar * PitchName_Absolute(const double dMIDInote, const bool bWantFlats /* = false */)
// the octave number, e.g., instead of "C" it will return "C4"
// if the pitchNum corresonds to middle C, i.e., is 60.
// ("Scientific Pitch Notation")
// Sharps are the default, unless, bWantFlats is true.
wxChar * PitchName_Absolute(double pitchNum, bool bWantFlats /* = false */)
{ {
PitchName(pitchNum, bWantFlats); PitchName(dMIDInote, bWantFlats);
// PitchName sets pPitchName to the next available char in gPitchName, // PitchName sets pPitchName to the next available char in gPitchName,
// so it's ready to append the register number. // so it's ready to append the register number.
int octaveNum = ((int)((pitchNum + 0.5) / 12) - 1); wxSnprintf(pPitchName, 8, wxT("%d"), PitchOctave(dMIDInote));
wxSnprintf(pPitchName, 8, wxT("%d"), octaveNum);
return gPitchName; return gPitchName;
} }
double PitchToMIDInote(const unsigned int nPitchIndex, const int nPitchOctave)
{
return ((double)nPitchIndex + (((double)nPitchOctave + 1.0) * 12.0));
}
double PitchToFreq(const unsigned int nPitchIndex, const int nPitchOctave)
{
return (440.0 * pow(2.0, (PitchToMIDInote(nPitchIndex, nPitchOctave) - 69) / 12.0));
}

View File

@ -1,56 +1,55 @@
/********************************************************************** /**********************************************************************
Audacity: A Digital Audio Editor Audacity: A Digital Audio Editor
Audacity(R) is copyright (c) 1999-2013 Audacity Team.
License: GPL v2. See License.txt.
PitchName.h PitchName.h
Vaughan Johnson, Dominic Mazzoni
Copyright 2005-9, Vaughan Johnson and Dominic Mazzoni. ******************************************************************//**
All rights reserved.
Utilities for converting from frequency to pitch utilities for converting among frequency, MIDI note number,
and from pitch to absolute (e.g., C4 for middle C) pitch index, pitch name
or nominal (A through G#) pitch name.
*//*******************************************************************/
**********************************************************************/
#ifndef __AUDACITY_PITCHNAME__ #ifndef __AUDACITY_PITCHNAME__
#define __AUDACITY_PITCHNAME__ #define __AUDACITY_PITCHNAME__
#include <wx/defs.h> #include <wx/defs.h>
// FreqToMIDInoteNumber takes a frequency in Hz (exponential scale relative to // FreqToMIDInote takes a frequency in Hz (exponential scale relative to
// alphabetic pitch names) and returns a pitch ID number (linear // alphabetic pitch names) and returns a pitch ID number (linear
// scale), such that A440 (A4) is 69, middle C (C4) is 60, etc. // scale), such that A440 (A4) is 69, middle C (C4) is 60, etc.
// Each register starts with C (e.g., for middle C and A440, // Each register starts with C (e.g., for middle C and A440,
// it's register 4). // it's register 4).
double FreqToMIDInoteNumber(double freq); // MIDI note number 0 is C-1 in Scientific pitch notation.
double FreqToMIDInote(const double freq);
// PitchIndex returns the [0,11] index for a double pitchNum, // PitchIndex returns the [0,11] index for a double MIDI note number,
// as per result from FreqToMIDInoteNumber, corresponding to modulo 12 // per result from FreqToMIDInote, corresponding to modulo 12
// of the integer part of (pitchNum + 0.5), so 0=C, 1=C#, etc. // of the integer part of (dMIDInote + 0.5), so 0=C, 1=C#, etc.
unsigned int PitchIndex(double pitchNum); unsigned int PitchIndex(const double dMIDInote);
// PitchName takes pitchNum (as per result from // PitchOctave returns the octave index for a double dMIDInote note number,
// FreqToMIDInoteNumber) and returns a standard pitch/note name [C, C#, etc.). // per result from FreqToMIDInote.
// MIDI note number 0 is C-1 in Scientific pitch notation.
int PitchOctave(const double dMIDInote);
// PitchName takes dMIDInote (per result from
// FreqToMIDInote) and returns a standard pitch/note name [C, C#, etc.).
// Sharps are the default, unless, bWantFlats is true. // Sharps are the default, unless, bWantFlats is true.
wxChar * PitchName(double pitchNum, bool bWantFlats = false); wxChar * PitchName(const double dMIDInote, const bool bWantFlats = false);
// PitchName_Absolute does the same thing as PitchName, but appends // PitchName_Absolute does the same thing as PitchName, but appends
// the register number, e.g., instead of "C" it will return "C4" // the octave number, e.g., instead of "C" it will return "C4"
// if the pitchNum corresonds to middle C. // if the dMIDInote corresonds to middle C, i.e., is 60.
// Sharps are the default, unless, bWantFlats is true. wxChar * PitchName_Absolute(const double dMIDInote, const bool bWantFlats = false);
wxChar * PitchName_Absolute(double pitchNum, bool bWantFlats = false);
double PitchToMIDInote(const unsigned int nPitchIndex, const int nPitchOctave);
double PitchToFreq(const unsigned int nPitchIndex, const int nPitchOctave);
#endif // __AUDACITY_PITCHNAME__ #endif // __AUDACITY_PITCHNAME__
// 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: 1cd2b819-63b6-4051-9991-37165f236037

View File

@ -17,12 +17,6 @@
#include "ChangePitch.h" #include "ChangePitch.h"
#include "../ShuttleGui.h"
#include "../PitchName.h"
#include "../Spectrum.h"
#include "../WaveTrack.h"
#include "TimeWarper.h"
#include <float.h> #include <float.h>
#include <math.h> #include <math.h>
@ -34,25 +28,31 @@
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <wx/valtext.h> #include <wx/valtext.h>
// #include "../ShuttleGui.h"
#include "../PitchName.h"
#include "../Spectrum.h"
#include "../WaveTrack.h"
#include "TimeWarper.h"
// EffectChangePitch // EffectChangePitch
//
EffectChangePitch::EffectChangePitch() EffectChangePitch::EffectChangePitch()
{ {
m_FromPitchIndex = -1; // -1 => uninitialized m_nFromPitch = -1; // -1 => uninitialized
m_ToPitchIndex = -1; // -1 => uninitialized m_nToPitch = -1; // -1 => uninitialized
m_SemitonesChange = 0.0; m_SemitonesChange = 0.0;
m_FromFrequency = 0.0; // 0.0 => uninitialized m_FromFrequency = 0.0; // 0.0 => uninitialized
m_ToFrequency = 0.0; // 0.0 => uninitialized m_ToFrequency = 0.0; // 0.0 => uninitialized
m_PercentChange = 0.0; m_PercentChange = 0.0;
} }
wxString EffectChangePitch::GetEffectDescription() { wxString EffectChangePitch::GetEffectDescription()
// Note: This is useful only after change amount has been set. {
// This is useful only after m_SemitonesChange has been set.
return wxString::Format(_("Applied effect: %s %.2f semitones"), return wxString::Format(_("Applied effect: %s %.2f semitones"),
this->GetEffectName().c_str(), this->GetEffectName().c_str(),
m_SemitonesChange); m_SemitonesChange);
@ -64,9 +64,8 @@ bool EffectChangePitch::Init()
return true; return true;
} }
// DeduceFrequencies is Dominic's extremely cool trick (Vaughan sez so!) // Deduce m_FromFrequency from the samples at the beginning of
// to set deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly.
// the selection. Then we set some other params accordingly.
void EffectChangePitch::DeduceFrequencies() void EffectChangePitch::DeduceFrequencies()
{ {
// As a neat trick, attempt to get the frequency of the note at the // As a neat trick, attempt to get the frequency of the note at the
@ -125,8 +124,8 @@ void EffectChangePitch::DeduceFrequencies()
m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0; m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0;
// Now we can set the pitch control values. // Now we can set the pitch control values.
m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); m_nFromPitch = PitchIndex(FreqToMIDInote(m_FromFrequency));
m_ToPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_ToFrequency)); m_nToPitch = PitchIndex(FreqToMIDInote(m_ToFrequency));
} }
} }
@ -135,24 +134,26 @@ bool EffectChangePitch::PromptUser()
this->DeduceFrequencies(); // Set frequency-related control values based on sample. this->DeduceFrequencies(); // Set frequency-related control values based on sample.
ChangePitchDialog dlog(this, mParent); ChangePitchDialog dlog(this, mParent);
dlog.m_FromPitchIndex = m_FromPitchIndex; dlog.m_nFromPitch = m_nFromPitch;
dlog.m_ToPitchIndex = m_ToPitchIndex; dlog.m_nFromOctave = PitchOctave(FreqToMIDInote(m_FromFrequency));
dlog.m_nToPitch = m_nToPitch;
dlog.m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency));
dlog.m_SemitonesChange = m_SemitonesChange; dlog.m_SemitonesChange = m_SemitonesChange;
dlog.m_FromFrequency = m_FromFrequency; dlog.m_FromFrequency = m_FromFrequency;
dlog.m_ToFrequency = m_ToFrequency; dlog.m_ToFrequency = m_ToFrequency;
dlog.m_PercentChange = m_PercentChange; dlog.m_PercentChange = m_PercentChange;
// Don't need to call TransferDataToWindow, although other // Don't need to call TransferDataToWindow, although other
// Audacity dialogs (from which I derived this one) do it, because // Audacity dialogs (from which I derived this one) do it, because
// ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog, // ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog,
// which calls dlog.TransferDataToWindow(); // which calls dlog.TransferDataToWindow();
dlog.CentreOnParent(); dlog.CentreOnParent();
dlog.ShowModal(); dlog.ShowModal();
if (dlog.GetReturnCode() == wxID_CANCEL) if (dlog.GetReturnCode() == wxID_CANCEL)
return false; return false;
m_FromPitchIndex = dlog.m_FromPitchIndex; m_nFromPitch = dlog.m_nFromPitch;
m_ToPitchIndex = dlog.m_ToPitchIndex; m_nToPitch = dlog.m_nToPitch;
m_SemitonesChange = dlog.m_SemitonesChange; m_SemitonesChange = dlog.m_SemitonesChange;
m_FromFrequency = dlog.m_FromFrequency; m_FromFrequency = dlog.m_FromFrequency;
m_ToFrequency = dlog.m_ToFrequency; m_ToFrequency = dlog.m_ToFrequency;
@ -162,6 +163,8 @@ bool EffectChangePitch::PromptUser()
bool EffectChangePitch::TransferParameters( Shuttle & shuttle ) bool EffectChangePitch::TransferParameters( Shuttle & shuttle )
{ {
// Vaughan: Long lost to history, I don't see why m_PercentChange was chosen to be shuttled.
// Only m_SemitonesChange is used in Process().
shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0); shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0);
m_SemitonesChange = (12.0 * log((100.0 + m_PercentChange) / 100.0)) / log(2.0); m_SemitonesChange = (12.0 * log((100.0 + m_PercentChange) / 100.0)) / log(2.0);
return true; return true;
@ -197,7 +200,9 @@ enum {
ID_TEXT_PERCENTCHANGE = 10001, ID_TEXT_PERCENTCHANGE = 10001,
ID_SLIDER_PERCENTCHANGE, ID_SLIDER_PERCENTCHANGE,
ID_CHOICE_FROMPITCH, ID_CHOICE_FROMPITCH,
ID_CHOICE_FROMOCTAVE,
ID_CHOICE_TOPITCH, ID_CHOICE_TOPITCH,
ID_CHOICE_TOOCTAVE,
ID_TEXT_SEMITONESCHANGE, ID_TEXT_SEMITONESCHANGE,
ID_TEXT_FROMFREQUENCY, ID_TEXT_FROMFREQUENCY,
ID_TEXT_TOFREQUENCY ID_TEXT_TOFREQUENCY
@ -207,7 +212,9 @@ enum {
BEGIN_EVENT_TABLE(ChangePitchDialog, EffectDialog) BEGIN_EVENT_TABLE(ChangePitchDialog, EffectDialog)
EVT_CHOICE(ID_CHOICE_FROMPITCH, ChangePitchDialog::OnChoice_FromPitch) EVT_CHOICE(ID_CHOICE_FROMPITCH, ChangePitchDialog::OnChoice_FromPitch)
EVT_TEXT(ID_CHOICE_FROMOCTAVE, ChangePitchDialog::OnSpin_FromOctave)
EVT_CHOICE(ID_CHOICE_TOPITCH, ChangePitchDialog::OnChoice_ToPitch) EVT_CHOICE(ID_CHOICE_TOPITCH, ChangePitchDialog::OnChoice_ToPitch)
EVT_TEXT(ID_CHOICE_TOOCTAVE, ChangePitchDialog::OnSpin_ToOctave)
EVT_TEXT(ID_TEXT_SEMITONESCHANGE, ChangePitchDialog::OnText_SemitonesChange) EVT_TEXT(ID_TEXT_SEMITONESCHANGE, ChangePitchDialog::OnText_SemitonesChange)
@ -224,14 +231,15 @@ ChangePitchDialog::ChangePitchDialog(EffectChangePitch *effect, wxWindow *parent
: EffectDialog(parent, _("Change Pitch"), PROCESS_EFFECT), : EffectDialog(parent, _("Change Pitch"), PROCESS_EFFECT),
mEffect(effect) mEffect(effect)
{ {
m_bLoopDetect = false; m_bLoopDetect = false;
// NULL out these control members because there are some cases where the // NULL out these control members because there are some cases where the
// event table handlers get called during this method, and those handlers that // event table handlers get called during this method, and those handlers that
// can cause trouble check for NULL. // can cause trouble check for NULL.
m_pChoice_FromPitch = NULL; m_pChoice_FromPitch = NULL;
m_pRadioButton_PitchDown = NULL; m_pSpin_FromOctave = NULL;
m_pChoice_ToPitch = NULL; m_pChoice_ToPitch = NULL;
m_pSpin_ToOctave = NULL;
m_pTextCtrl_SemitonesChange = NULL; m_pTextCtrl_SemitonesChange = NULL;
@ -242,13 +250,15 @@ ChangePitchDialog::ChangePitchDialog(EffectChangePitch *effect, wxWindow *parent
m_pSlider_PercentChange = NULL; m_pSlider_PercentChange = NULL;
// effect parameters // effect parameters
m_FromPitchIndex = -1; // -1 => uninitialized m_nFromPitch = -1; // -1 => uninitialized
m_ToPitchIndex = -1; // -1 => uninitialized m_nFromOctave = -1; // -1 => uninitialized
m_nToPitch = -1; // -1 => uninitialized
m_nToOctave = -1; // -1 => uninitialized
m_SemitonesChange = 0.0; m_SemitonesChange = 0.0;
m_FromFrequency = 0.0; // 0.0 => uninitialized m_FromFrequency = 0.0; // 0.0 => uninitialized
m_ToFrequency = 0.0; // 0.0 => uninitialized m_ToFrequency = 0.0; // 0.0 => uninitialized
m_PercentChange = 0.0; m_PercentChange = 0.0;
@ -294,7 +304,7 @@ void ChangePitchDialog::PopulateOrExchange(ShuttleGui & S)
{ {
S.AddTitle(_("Change Pitch without Changing Tempo") + S.AddTitle(_("Change Pitch without Changing Tempo") +
wxString(wxT("\n\n")) + wxString(wxT("\n\n")) +
_("by Vaughan Johnson && Dominic Mazzoni") + _("by Vaughan Johnson, Dominic Mazzoni, && Steve Daulton") +
wxString(wxT("\n")) + wxString(wxT("\n")) +
_("using SoundTouch, by Olli Parviainen")); _("using SoundTouch, by Olli Parviainen"));
} }
@ -312,9 +322,19 @@ void ChangePitchDialog::PopulateOrExchange(ShuttleGui & S)
m_pChoice_FromPitch->SetName(_("From Pitch")); m_pChoice_FromPitch->SetName(_("From Pitch"));
m_pChoice_FromPitch->SetSizeHints(80, -1); m_pChoice_FromPitch->SetSizeHints(80, -1);
m_pSpin_FromOctave =
S.Id(ID_CHOICE_FROMOCTAVE).AddSpinCtrl(wxT(""), m_nFromOctave, INT_MAX, INT_MIN);
m_pSpin_FromOctave->SetName(_("From Octave"));
m_pSpin_FromOctave->SetSizeHints(50, -1);
m_pChoice_ToPitch = S.Id(ID_CHOICE_TOPITCH).AddChoice(_("to"), wxT(""), &pitch); m_pChoice_ToPitch = S.Id(ID_CHOICE_TOPITCH).AddChoice(_("to"), wxT(""), &pitch);
m_pChoice_ToPitch->SetName(_("To Pitch")); m_pChoice_ToPitch->SetName(_("To Pitch"));
m_pChoice_ToPitch->SetSizeHints(80, -1); m_pChoice_ToPitch->SetSizeHints(80, -1);
m_pSpin_ToOctave =
S.Id(ID_CHOICE_TOOCTAVE).AddSpinCtrl(wxT(""), m_nToOctave, INT_MAX, INT_MIN);
m_pSpin_ToOctave->SetName(_("From Octave"));
m_pSpin_ToOctave->SetSizeHints(50, -1);
} }
S.EndHorizontalLay(); S.EndHorizontalLay();
} }
@ -377,15 +397,15 @@ bool ChangePitchDialog::TransferDataToWindow()
// from/to pitch controls // from/to pitch controls
if (m_pChoice_FromPitch) if (m_pChoice_FromPitch)
m_pChoice_FromPitch->SetSelection(m_FromPitchIndex); m_pChoice_FromPitch->SetSelection(m_nFromPitch);
if (m_pSpin_FromOctave)
this->Update_Choice_ToPitch(); m_pSpin_FromOctave->SetValue(m_nFromOctave);
this->Update_Choice_ToPitch();
this->Update_Spin_ToOctave();
// semitones change control // semitones change control
this->Update_Text_SemitonesChange(); this->Update_Text_SemitonesChange();
// from/to frequency controls // from/to frequency controls
if (m_pTextCtrl_FromFrequency) { if (m_pTextCtrl_FromFrequency) {
wxString str; wxString str;
@ -398,12 +418,10 @@ bool ChangePitchDialog::TransferDataToWindow()
this->Update_Text_ToFrequency(); this->Update_Text_ToFrequency();
// percent change controls // percent change controls
this->Update_Text_PercentChange(); this->Update_Text_PercentChange();
this->Update_Slider_PercentChange(); this->Update_Slider_PercentChange();
m_bLoopDetect = false; m_bLoopDetect = false;
return true; return true;
@ -417,10 +435,12 @@ bool ChangePitchDialog::TransferDataFromWindow()
// from/to pitch controls // from/to pitch controls
if (m_pChoice_FromPitch) if (m_pChoice_FromPitch)
m_FromPitchIndex = m_pChoice_FromPitch->GetSelection(); m_nFromPitch = m_pChoice_FromPitch->GetSelection();
if (m_pSpin_FromOctave)
m_nFromOctave = m_pSpin_FromOctave->GetValue();
if (m_pChoice_ToPitch) if (m_pChoice_ToPitch)
m_ToPitchIndex = m_pChoice_ToPitch->GetSelection(); m_nToPitch = m_pChoice_ToPitch->GetSelection();
// semitones change control // semitones change control
@ -462,18 +482,24 @@ bool ChangePitchDialog::TransferDataFromWindow()
// calculations // calculations
void ChangePitchDialog::Calc_ToPitchIndex() void ChangePitchDialog::Calc_ToPitch()
{ {
int nSemitonesChange = int nSemitonesChange =
(int)(m_SemitonesChange + ((m_SemitonesChange < 0.0) ? -0.5 : 0.5)); (int)(m_SemitonesChange + ((m_SemitonesChange < 0.0) ? -0.5 : 0.5));
m_ToPitchIndex = (m_FromPitchIndex + nSemitonesChange) % 12; m_nToPitch = (m_nFromPitch + nSemitonesChange) % 12;
if (m_ToPitchIndex < 0) if (m_nToPitch < 0)
m_ToPitchIndex += 12; m_nToPitch += 12;
}
void ChangePitchDialog::Calc_ToOctave()
{
m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency));
} }
void ChangePitchDialog::Calc_SemitonesChange_fromPitches() void ChangePitchDialog::Calc_SemitonesChange_fromPitches()
{ {
m_SemitonesChange = m_ToPitchIndex - m_FromPitchIndex; m_SemitonesChange =
PitchToMIDInote(m_nToPitch, m_nToOctave) - PitchToMIDInote(m_nFromPitch, m_nFromOctave);
} }
void ChangePitchDialog::Calc_SemitonesChange_fromPercentChange() void ChangePitchDialog::Calc_SemitonesChange_fromPercentChange()
@ -502,12 +528,43 @@ void ChangePitchDialog::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(event))
return; return;
if (m_pChoice_FromPitch) { if (m_pChoice_FromPitch) {
m_FromPitchIndex = m_pChoice_FromPitch->GetSelection(); m_nFromPitch = m_pChoice_FromPitch->GetSelection();
m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
this->Calc_ToPitchIndex(); this->Calc_ToPitch();
this->Calc_ToFrequency();
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_ToPitch(); {
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();
m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
this->Calc_ToFrequency();
this->Calc_ToOctave(); // Call after Calc_ToFrequency().
m_bLoopDetect = true;
{
this->Update_Spin_ToOctave();
this->Update_Text_FromFrequency();
this->Update_Text_ToFrequency();
}
m_bLoopDetect = false; m_bLoopDetect = false;
} }
} }
@ -517,18 +574,55 @@ void ChangePitchDialog::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(event))
if (m_bLoopDetect) if (m_bLoopDetect)
return; return;
if (m_pChoice_ToPitch) { if (m_pChoice_ToPitch)
m_ToPitchIndex = m_pChoice_ToPitch->GetSelection(); {
m_nToPitch = m_pChoice_ToPitch->GetSelection();
this->Calc_SemitonesChange_fromPitches(); this->Calc_SemitonesChange_fromPitches();
this->Calc_PercentChange(); // Call *after* m_SemitonesChange is updated. this->Calc_PercentChange(); // Call *after* m_SemitonesChange is updated.
this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated. this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated.
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Text_SemitonesChange(); {
this->Update_Text_ToFrequency(); this->Update_Text_SemitonesChange();
this->Update_Text_PercentChange(); this->Update_Text_ToFrequency();
this->Update_Slider_PercentChange(); 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
// keeps below 90% m_PercentChange.
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();
this->Calc_PercentChange(); // Call *after* m_SemitonesChange is updated.
m_bLoopDetect = true;
{
this->Update_Text_SemitonesChange();
this->Update_Text_ToFrequency();
this->Update_Text_PercentChange();
this->Update_Slider_PercentChange();
}
m_bLoopDetect = false; m_bLoopDetect = false;
} }
} }
@ -551,13 +645,15 @@ void ChangePitchDialog::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(event))
this->Calc_PercentChange(); this->Calc_PercentChange();
this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated. this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated.
this->Calc_ToPitchIndex(); this->Calc_ToPitch();
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_ToPitch(); {
this->Update_Text_ToFrequency(); this->Update_Choice_ToPitch();
this->Update_Text_PercentChange(); this->Update_Text_ToFrequency();
this->Update_Slider_PercentChange(); this->Update_Text_PercentChange();
this->Update_Slider_PercentChange();
}
m_bLoopDetect = false; m_bLoopDetect = false;
// If m_SemitonesChange is a big enough negative, we can go to or below 0 freq. // If m_SemitonesChange is a big enough negative, we can go to or below 0 freq.
@ -589,14 +685,16 @@ void ChangePitchDialog::OnText_FromFrequency(wxCommandEvent & WXUNUSED(event))
} }
m_FromFrequency = newDouble; m_FromFrequency = newDouble;
m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); m_nFromPitch = PitchIndex(FreqToMIDInote(m_FromFrequency));
this->Calc_ToFrequency(); this->Calc_ToFrequency();
this->Calc_ToPitchIndex(); this->Calc_ToPitch();
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_FromPitch(); {
this->Update_Choice_ToPitch(); this->Update_Choice_FromPitch();
this->Update_Text_ToFrequency(); this->Update_Choice_ToPitch();
this->Update_Text_ToFrequency();
}
m_bLoopDetect = false; m_bLoopDetect = false;
// Success. Make sure OK and Preview are enabled, in case we disabled above during editing. // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
@ -629,13 +727,15 @@ void ChangePitchDialog::OnText_ToFrequency(wxCommandEvent & WXUNUSED(event))
(double)(m_FromFrequency)) - 100.0; (double)(m_FromFrequency)) - 100.0;
this->Calc_SemitonesChange_fromPercentChange(); this->Calc_SemitonesChange_fromPercentChange();
this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. this->Calc_ToPitch(); // Call *after* m_SemitonesChange is updated.
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_ToPitch(); {
this->Update_Text_SemitonesChange(); this->Update_Choice_ToPitch();
this->Update_Text_PercentChange(); this->Update_Text_SemitonesChange();
this->Update_Slider_PercentChange(); this->Update_Text_PercentChange();
this->Update_Slider_PercentChange();
}
m_bLoopDetect = false; m_bLoopDetect = false;
// Success. Make sure OK and Preview are enabled, in case we disabled above during editing. // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
@ -668,14 +768,16 @@ void ChangePitchDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event))
m_PercentChange = newValue; m_PercentChange = newValue;
this->Calc_SemitonesChange_fromPercentChange(); this->Calc_SemitonesChange_fromPercentChange();
this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. this->Calc_ToPitch(); // Call *after* m_SemitonesChange is updated.
this->Calc_ToFrequency(); this->Calc_ToFrequency();
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_ToPitch(); {
this->Update_Text_SemitonesChange(); this->Update_Choice_ToPitch();
this->Update_Text_ToFrequency(); this->Update_Text_SemitonesChange();
this->Update_Slider_PercentChange(); this->Update_Text_ToFrequency();
this->Update_Slider_PercentChange();
}
m_bLoopDetect = false; m_bLoopDetect = false;
// Success. Make sure OK and Preview are enabled, in case we disabled above during editing. // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
@ -696,14 +798,16 @@ void ChangePitchDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event))
m_PercentChange = pow(m_PercentChange, PERCENTCHANGE_SLIDER_WARP); m_PercentChange = pow(m_PercentChange, PERCENTCHANGE_SLIDER_WARP);
this->Calc_SemitonesChange_fromPercentChange(); this->Calc_SemitonesChange_fromPercentChange();
this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. this->Calc_ToPitch(); // Call *after* m_SemitonesChange is updated.
this->Calc_ToFrequency(); this->Calc_ToFrequency();
m_bLoopDetect = true; m_bLoopDetect = true;
this->Update_Choice_ToPitch(); {
this->Update_Text_SemitonesChange(); this->Update_Choice_ToPitch();
this->Update_Text_ToFrequency(); this->Update_Text_SemitonesChange();
this->Update_Text_PercentChange(); this->Update_Text_ToFrequency();
this->Update_Text_PercentChange();
}
m_bLoopDetect = false; m_bLoopDetect = false;
} }
} }
@ -729,13 +833,25 @@ void ChangePitchDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
void ChangePitchDialog::Update_Choice_FromPitch() void ChangePitchDialog::Update_Choice_FromPitch()
{ {
if (m_pChoice_FromPitch) if (m_pChoice_FromPitch)
m_pChoice_FromPitch->SetSelection(m_FromPitchIndex); m_pChoice_FromPitch->SetSelection(m_nFromPitch);
}
void ChangePitchDialog::Update_Spin_FromOctave()
{
if (m_pSpin_FromOctave)
m_pSpin_FromOctave->SetValue(m_nFromOctave);
} }
void ChangePitchDialog::Update_Choice_ToPitch() void ChangePitchDialog::Update_Choice_ToPitch()
{ {
if (m_pChoice_ToPitch) if (m_pChoice_ToPitch)
m_pChoice_ToPitch->SetSelection(m_ToPitchIndex); m_pChoice_ToPitch->SetSelection(m_nToPitch);
}
void ChangePitchDialog::Update_Spin_ToOctave()
{
if (m_pSpin_ToOctave)
m_pSpin_ToOctave->SetValue(m_nToOctave);
} }
void ChangePitchDialog::Update_Text_SemitonesChange() void ChangePitchDialog::Update_Text_SemitonesChange()
@ -747,6 +863,18 @@ void ChangePitchDialog::Update_Text_SemitonesChange()
} }
} }
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);
}
}
void ChangePitchDialog::Update_Text_ToFrequency() void ChangePitchDialog::Update_Text_ToFrequency()
{ {
if (m_pTextCtrl_ToFrequency) { if (m_pTextCtrl_ToFrequency) {

View File

@ -21,6 +21,7 @@
#include <wx/dialog.h> #include <wx/dialog.h>
#include <wx/intl.h> #include <wx/intl.h>
#include <wx/slider.h> #include <wx/slider.h>
#include <wx/spinctrl.h>
class EffectChangePitch : public EffectSoundTouch { class EffectChangePitch : public EffectSoundTouch {
@ -51,9 +52,8 @@ class EffectChangePitch : public EffectSoundTouch {
virtual bool Init(); virtual bool Init();
// DeduceFrequencies is Dominic's extremely cool trick (Vaughan sez so!) // Deduce m_FromFrequency from the samples at the beginning of
// to set deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly.
// the selection. Then we set some other params accordingly.
virtual void DeduceFrequencies(); virtual void DeduceFrequencies();
virtual bool PromptUser(); virtual bool PromptUser();
@ -63,8 +63,8 @@ class EffectChangePitch : public EffectSoundTouch {
virtual bool Process(); virtual bool Process();
private: private:
int m_FromPitchIndex; // pitch index, per PitchIndex int m_nFromPitch; // per PitchIndex()
int m_ToPitchIndex; // pitch index, per PitchIndex int m_nToPitch; // per PitchIndex()
double m_SemitonesChange; // how many semitones to change pitch double m_SemitonesChange; // how many semitones to change pitch
@ -95,15 +95,19 @@ class ChangePitchDialog:public EffectDialog {
private: private:
// calculations // calculations
void Calc_ToPitchIndex(); // Update m_ToPitchIndex from new m_SemitonesChange. void Calc_ToPitch(); // Update m_nToPitch from new m_SemitonesChange.
void Calc_SemitonesChange_fromPitches(); // Update m_SemitonesChange from new m_*PitchIndex-es. void Calc_ToOctave();
void Calc_SemitonesChange_fromPercentChange(); // Update m_SemitonesChange from new m_PercentChange. void Calc_SemitonesChange_fromPitches();
void Calc_SemitonesChange_fromOctaveChange();
void Calc_SemitonesChange_fromPercentChange();
void Calc_ToFrequency(); // Update m_ToFrequency from m_FromFrequency & m_PercentChange. void Calc_ToFrequency(); // Update m_ToFrequency from m_FromFrequency & m_PercentChange.
void Calc_PercentChange(); // Update m_PercentChange based on new m_SemitonesChange. void Calc_PercentChange(); // Update m_PercentChange based on new m_SemitonesChange.
// handlers // handlers
void OnChoice_FromPitch(wxCommandEvent & event); void OnChoice_FromPitch(wxCommandEvent & event);
void OnSpin_FromOctave(wxCommandEvent & event);
void OnChoice_ToPitch(wxCommandEvent & event); void OnChoice_ToPitch(wxCommandEvent & event);
void OnSpin_ToOctave(wxCommandEvent & event);
void OnText_SemitonesChange(wxCommandEvent & event); void OnText_SemitonesChange(wxCommandEvent & event);
@ -117,10 +121,13 @@ class ChangePitchDialog:public EffectDialog {
// helper fns for controls // helper fns for controls
void Update_Choice_FromPitch(); void Update_Choice_FromPitch();
void Update_Spin_FromOctave();
void Update_Choice_ToPitch(); void Update_Choice_ToPitch();
void Update_Spin_ToOctave();
void Update_Text_SemitonesChange(); void Update_Text_SemitonesChange();
void Update_Text_FromFrequency();
void Update_Text_ToFrequency(); void Update_Text_ToFrequency();
void Update_Text_PercentChange(); // Update control per current m_PercentChange. void Update_Text_PercentChange(); // Update control per current m_PercentChange.
@ -128,25 +135,26 @@ class ChangePitchDialog:public EffectDialog {
private: private:
EffectChangePitch * mEffect; EffectChangePitch * mEffect;
bool m_bLoopDetect; bool m_bLoopDetect; // Used to avoid loops in initialization and in event handling.
// controls // controls
wxChoice * m_pChoice_FromPitch; wxChoice * m_pChoice_FromPitch;
wxRadioButton *m_pRadioButton_PitchDown; wxSpinCtrl * m_pSpin_FromOctave;
wxChoice * m_pChoice_ToPitch; wxChoice * m_pChoice_ToPitch;
wxSpinCtrl * m_pSpin_ToOctave;
wxTextCtrl * m_pTextCtrl_SemitonesChange; wxTextCtrl * m_pTextCtrl_SemitonesChange;
wxTextCtrl * m_pTextCtrl_FromFrequency; wxTextCtrl * m_pTextCtrl_FromFrequency;
wxTextCtrl * m_pTextCtrl_ToFrequency; wxTextCtrl * m_pTextCtrl_ToFrequency;
wxTextCtrl * m_pTextCtrl_PercentChange; wxTextCtrl * m_pTextCtrl_PercentChange;
wxSlider * m_pSlider_PercentChange; wxSlider * m_pSlider_PercentChange;
public: public:
// effect parameters // effect parameters
int m_FromPitchIndex; // pitch index, per PitchIndex int m_nFromPitch; // per PitchIndex()
int m_ToPitchIndex; // pitch index, per PitchIndex int m_nFromOctave; // per PitchOctave()
int m_nToPitch; // per PitchIndex()
int m_nToOctave; // per PitchOctave()
double m_SemitonesChange; // how many semitones to change pitch double m_SemitonesChange; // how many semitones to change pitch

View File

@ -82,7 +82,7 @@ void EffectReverb::Delete()
} }
// Most of what follows should really be provided by Audacity framework classes: // Most of what follows should really be provided by Audacity framework classes:
//vvvvv Not sure what you mean by that, Rob. A la EffectSimpleMono, e.g.? //vvv Not sure what you mean by that, Rob. A la EffectSimpleMono, e.g.?
bool EffectReverb::ProcessOneTrack(size_t n, WaveTrack * track, WaveTrack * track2, wxString const & msg) bool EffectReverb::ProcessOneTrack(size_t n, WaveTrack * track, WaveTrack * track2, wxString const & msg)
{ {