Audacity 3.2.0
ChangePitchBase.cpp
Go to the documentation of this file.
1#ifdef USE_SOUNDTOUCH
2
3#include "ChangePitchBase.h"
4#include "ShuttleAutomation.h"
5#include "Spectrum.h"
6#include "TimeWarper.h"
7#include "WaveTrack.h"
8#include <wx/math.h>
9#include "PitchName.h"
10
11#if USE_SBSMS
12#include "SBSMSBase.h"
13#endif
14
15// Soundtouch defines these as well, which are also in generated configmac.h
16// and configunix.h, so get rid of them before including,
17// to avoid compiler warnings, and be sure to do this
18// after all other #includes, to avoid any mischief that might result
19// from doing the un-definitions before seeing any wx headers.
20#undef PACKAGE_NAME
21#undef PACKAGE_STRING
22#undef PACKAGE_TARNAME
23#undef PACKAGE_VERSION
24#undef PACKAGE_BUGREPORT
25#undef PACKAGE
26#undef VERSION
27#include "SoundTouch.h"
28
29// Soundtouch is not reasonable below -99% or above 3000%.
30
31const EffectParameterMethods& ChangePitchBase::Parameters() const
32{
33 static CapturedParameters<
34 ChangePitchBase,
35 // Vaughan, 2013-06: Long lost to history, I don't see why
36 // m_dPercentChange was chosen to be shuttled. Only m_dSemitonesChange is
37 // used in Process(). PRL 2022: but that is so only when USE_SBSMS is not
38 // defined
39 Percentage, UseSBSMS>
40 parameters {
41 [](ChangePitchBase&, EffectSettings&, ChangePitchBase& e,
42 bool updating) {
43 if (updating)
44 e.Calc_SemitonesChange_fromPercentChange();
45 return true;
46 },
47 };
48 return parameters;
49}
50
51const ComponentInterfaceSymbol ChangePitchBase::Symbol { XO("Change Pitch") };
52
53ChangePitchBase::ChangePitchBase()
54{
55 // mUseSBSMS always defaults to false and its value is used only if USE_SBSMS
56 // is defined
57 Parameters().Reset(*this);
58
59 m_dSemitonesChange = 0.0;
60 m_dStartFrequency = 0.0; // 0.0 => uninitialized
61 m_bLoopDetect = false;
62
63 SetLinearEffectFlag(true);
64}
65
66ChangePitchBase::~ChangePitchBase()
67{
68}
69
70// ComponentInterface implementation
71
72ComponentInterfaceSymbol ChangePitchBase::GetSymbol() const
73{
74 return Symbol;
75}
76
77TranslatableString ChangePitchBase::GetDescription() const
78{
79 return XO("Changes the pitch of a track without changing its tempo");
80}
81
82ManualPageID ChangePitchBase::ManualPage() const
83{
84 return L"Change_Pitch";
85}
86
87// EffectDefinitionInterface implementation
88
89EffectType ChangePitchBase::GetType() const
90{
91 return EffectTypeProcess;
92}
93
95ChangePitchBase::LoadFactoryDefaults(EffectSettings& settings) const
96{
97 // To do: externalize state so const_cast isn't needed
98 return const_cast<ChangePitchBase&>(*this).DoLoadFactoryDefaults(settings);
99}
100
101OptionalMessage ChangePitchBase::DoLoadFactoryDefaults(EffectSettings& settings)
102{
103 DeduceFrequencies();
104
106}
107
108// Effect implementation
109
110bool ChangePitchBase::Process(EffectInstance&, EffectSettings& settings)
111{
112#if USE_SBSMS
113 if (mUseSBSMS)
114 {
115 double pitchRatio = 1.0 + m_dPercentChange / 100.0;
116 SBSMSBase proxy;
117 proxy.mProxyEffectName = XO("High Quality Pitch Change");
118 proxy.setParameters(1.0, pitchRatio);
120 return Delegate(proxy, settings);
121 }
122 else
123#endif
124 {
125 // Macros save m_dPercentChange and not m_dSemitonesChange, so we must
126 // ensure that m_dSemitonesChange is set.
127 Calc_SemitonesChange_fromPercentChange();
128
129 auto initer = [&](soundtouch::SoundTouch* soundtouch) {
130 soundtouch->setPitchSemiTones((float)(m_dSemitonesChange));
131 };
132 IdentityTimeWarper warper;
133#ifdef USE_MIDI
134 // Pitch shifting note tracks is currently only supported by
135 // SoundTouchBase and non-real-time-preview effects require an audio track
136 // selection.
137 //
138 // Note: m_dSemitonesChange is private to ChangePitch because it only
139 // needs to pass it along to mSoundTouch (above). I added mSemitones
140 // to SoundTouchBase (the super class) to convey this value
141 // to process Note tracks. This approach minimizes changes to existing
142 // code, but it would be cleaner to change all m_dSemitonesChange to
143 // mSemitones, make mSemitones exist with or without USE_MIDI, and
144 // eliminate the next line:
145 mSemitones = m_dSemitonesChange;
146#endif
147 return SoundTouchBase::ProcessWithTimeWarper(initer, warper, true);
148 }
149}
150
151bool ChangePitchBase::CheckWhetherSkipEffect(const EffectSettings&) const
152{
153 return (m_dPercentChange == 0.0);
154}
155
156// ChangePitchBase implementation
157
158// Deduce m_FromFrequency from the samples at the beginning of
159// the selection. Then set some other params accordingly.
160void ChangePitchBase::DeduceFrequencies()
161{
162 auto FirstTrack = [&]() -> const WaveTrack* {
163 if (IsBatchProcessing() || !inputTracks())
164 return nullptr;
165 return *(inputTracks()->Selected<const WaveTrack>()).first;
166 };
167
168 m_dStartFrequency = 261.265; // Middle C.
169
170 // As a neat trick, attempt to get the frequency of the note at the
171 // beginning of the selection.
172 auto track = FirstTrack();
173 if (track)
174 {
175 double rate = track->GetRate();
176
177 // Auto-size window -- high sample rates require larger windowSize.
178 // Aim for around 2048 samples at 44.1 kHz (good down to about 100 Hz).
179 // To detect single notes, analysis period should be about 0.2 seconds.
180 // windowSize must be a power of 2.
181 const size_t windowSize =
182 // windowSize < 256 too inaccurate
183 std::max(
184 256, wxRound(pow(2.0, floor((log(rate / 20.0) / log(2.0)) + 0.5))));
185
186 // we want about 0.2 seconds to catch the first note.
187 // number of windows rounded to nearest integer >= 1.
188 const unsigned numWindows =
189 std::max(1, wxRound((double)(rate / (5.0f * windowSize))));
190
191 double trackStart = track->GetStartTime();
192 double t0 = mT0 < trackStart ? trackStart : mT0;
193 auto start = track->TimeToLongSamples(t0);
194
195 auto analyzeSize = windowSize * numWindows;
196 Floats buffer { analyzeSize };
197
198 Floats freq { windowSize / 2 };
199 Floats freqa { windowSize / 2, true };
200
201 // Always used only the left channel for this deduction of initial pitch
202 (*track->Channels().begin())->GetFloats(buffer.get(), start, analyzeSize);
203 for (unsigned i = 0; i < numWindows; i++)
204 {
206 buffer.get() + i * windowSize, windowSize, windowSize, freq.get(),
207 true);
208 for (size_t j = 0; j < windowSize / 2; j++)
209 freqa[j] += freq[j];
210 }
211 size_t argmax = 0;
212 for (size_t j = 1; j < windowSize / 2; j++)
213 if (freqa[j] > freqa[argmax])
214 argmax = j;
215
216 auto lag = (windowSize / 2 - 1) - argmax;
217 m_dStartFrequency = rate / lag;
218 }
219
220 double dFromMIDInote = FreqToMIDInote(m_dStartFrequency);
221 double dToMIDInote = dFromMIDInote + m_dSemitonesChange;
222 m_nFromPitch = PitchIndex(dFromMIDInote);
223 m_nFromOctave = PitchOctave(dFromMIDInote);
224 m_nToPitch = PitchIndex(dToMIDInote);
225 m_nToOctave = PitchOctave(dToMIDInote);
226
227 m_FromFrequency = m_dStartFrequency;
228 // Calc_PercentChange(); // This will reset m_dPercentChange
229 Calc_ToFrequency();
230}
231
232// calculations
233
234void ChangePitchBase::Calc_ToPitch()
235{
236 int nSemitonesChange =
237 (int)(m_dSemitonesChange + ((m_dSemitonesChange < 0.0) ? -0.5 : 0.5));
238 m_nToPitch = (m_nFromPitch + nSemitonesChange) % 12;
239 if (m_nToPitch < 0)
240 m_nToPitch += 12;
241}
242
243void ChangePitchBase::Calc_ToOctave()
244{
245 m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency));
246}
247
248void ChangePitchBase::Calc_SemitonesChange_fromPitches()
249{
250 m_dSemitonesChange = PitchToMIDInote(m_nToPitch, m_nToOctave) -
251 PitchToMIDInote(m_nFromPitch, m_nFromOctave);
252}
253
254void ChangePitchBase::Calc_SemitonesChange_fromPercentChange()
255{
256 // Use m_dPercentChange rather than m_FromFrequency & m_ToFrequency, because
257 // they start out uninitialized, but m_dPercentChange is always valid.
258 m_dSemitonesChange =
259 (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0);
260}
261
262void ChangePitchBase::Calc_ToFrequency()
263{
264 m_ToFrequency = (m_FromFrequency * (100.0 + m_dPercentChange)) / 100.0;
265}
266
267void ChangePitchBase::Calc_PercentChange()
268{
269 m_dPercentChange = 100.0 * (pow(2.0, (m_dSemitonesChange / 12.0)) - 1.0);
270}
271
272#endif // USE_SOUNDTOUCH
Change Pitch effect provides raising or lowering the pitch without changing the tempo.
EffectType
@ EffectTypeProcess
std::optional< std::unique_ptr< EffectSettingsAccess::Message > > OptionalMessage
XO("Cut/Copy/Paste")
unsigned int PitchIndex(const double dMIDInote)
Definition: PitchName.cpp:34
double PitchToMIDInote(const unsigned int nPitchIndex, const int nPitchOctave)
Definition: PitchName.cpp:155
int PitchOctave(const double dMIDInote)
Definition: PitchName.cpp:47
double FreqToMIDInote(const double freq)
Definition: PitchName.cpp:23
bool ComputeSpectrum(const float *data, size_t width, size_t windowSize, float *output, bool autocorrelation, int windowFunc)
Definition: Spectrum.cpp:22
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
static Settings & settings()
Definition: TrackInfo.cpp:51
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
OptionalMessage LoadFactoryDefaults(EffectSettings &settings) const override
Definition: Effect.cpp:165
Performs effect computation.
Interface for manipulations of an Effect's settings.
No change to time at all.
Definition: TimeWarper.h:69
void setParameters(double rateStart, double rateEnd, double pitchStart, double pitchEnd, SlideType rateSlideType, SlideType pitchSlideType, bool bLinkRatePitch, bool bRateReferenceInput, bool bPitchReferenceInput)
Definition: SBSMSBase.cpp:149
TranslatableString mProxyEffectName
Definition: SBSMSBase.h:41
Holds a msgid for the translation catalog; may also bind format arguments.
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
Externalized state of a plug-in.