// Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. 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, inputTracks()); WaveTrack *track = (WaveTrack *) iter.First(); if (track) { 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. const size_t windowSize = // windowSize < 256 too inaccurate std::max(256, wxRound(pow(2.0, floor((log(rate / 20.0)/log(2.0)) + 0.5)))); // we want about 0.2 seconds to catch the first note. // number of windows rounded to nearest integer >= 1. const unsigned numWindows = std::max(1, wxRound((double)(rate / (5.0f * windowSize)))); double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; auto start = track->TimeToLongSamples(t0); auto analyzeSize = windowSize * numWindows; Floats buffer{ analyzeSize }; Floats freq{ windowSize / 2 }; Floats freqa{ windowSize / 2, true }; track->Get((samplePtr) buffer.get(), floatSample, start, analyzeSize); for(unsigned i = 0; i < numWindows; i++) { ComputeSpectrum(buffer.get() + i * windowSize, windowSize, windowSize, rate, freq.get(), true); for(size_t j = 0; j < windowSize / 2; j++) freqa[j] += freq[j]; } size_t argmax = 0; for(size_t j = 1; j < windowSize / 2; j++) if (freqa[j] > freqa[argmax]) argmax = j; auto lag = (windowSize / 2 - 1) - argmax; m_dStartFrequency = rate / lag; } double dFromMIDInote = FreqToMIDInote(m_dStartFrequency); double dToMIDInote = dFromMIDInote + m_dSemitonesChange; m_nFromPitch = PitchIndex(dFromMIDInote); m_nFromOctave = PitchOctave(dFromMIDInote); m_nToPitch = PitchIndex(dToMIDInote); m_nToOctave = PitchOctave(dToMIDInote); m_FromFrequency = m_dStartFrequency; Calc_PercentChange(); Calc_ToFrequency(); }
void ChangePitchDialog::OnText_FromFrequency(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pTextCtrl_FromFrequency) { wxString str = m_pTextCtrl_FromFrequency->GetValue(); double newDouble; str.ToDouble(&newDouble); m_FromFrequency = newDouble; m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); this->Calc_ToFrequency(); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); this->Calc_ToPitchIndex(); // Call *after* m_bWantPitchDown is updated. m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); if (m_pTextCtrl_FromFrequency->IsModified()) // See note at implementation of Update_RadioButton_PitchUpDown. m_pTextCtrl_FromFrequency->SetFocus(); this->Update_Choice_ToPitch(); this->Update_Text_ToFrequency(); m_bLoopDetect = false; } }
void EffectChangePitch::OnText_FromFrequency(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; // Empty string causes unpredictable results with ToDouble() and later calculations. // Non-positive frequency makes no sense, but user might still be editing, // so it's not an error, but we do not want to update the values/controls. if (!m_pTextCtrl_FromFrequency->GetValidator()->TransferFromWindow()) { EnableApply(false); return; } double newFromMIDInote = FreqToMIDInote(m_FromFrequency); m_nFromPitch = PitchIndex(newFromMIDInote); m_nFromOctave = PitchOctave(newFromMIDInote); Calc_ToPitch(); Calc_ToFrequency(); Calc_ToOctave(); // Call after Calc_ToFrequency(). m_bLoopDetect = true; { Update_Choice_FromPitch(); Update_Spin_FromOctave(); Update_Choice_ToPitch(); Update_Spin_ToOctave(); Update_Text_ToFrequency(); } m_bLoopDetect = false; // Success. Make sure OK and Preview are enabled, in case we disabled above during editing. EnableApply(true); }
// DeduceFrequencies is Dominic's extremely cool trick (Vaughan sez so!) // to set deduce m_FromFrequency from the samples at the beginning of // the selection. Then we set some other params accordingly. 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) { const int windowSize = 1024; const int analyzeSize = 8192; const int numWindows = analyzeSize / windowSize; double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; sampleCount start = track->TimeToLongSamples(t0); double rate = track->GetRate(); float buffer[analyzeSize]; float freq[windowSize/2]; float freqa[windowSize/2]; 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; lag = (windowSize/2 - 1) - argmax; m_FromFrequency = rate / lag; m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0; // Now we can set the pitch control values. m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_ToPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_ToFrequency)); } }
// PitchName takes pitchNum (as per result from // Freq2Pitch) and returns a standard pitch/note name [C, C#, etc.). // Sharps are the default, unless, bWantFlats is true. char * PitchName(float pitchNum, bool bWantFlats /* = false */) { p_PitchName = gPitchName; switch (PitchIndex(pitchNum)) { case 0: *p_PitchName++ = 'C'; break; case 1: if (bWantFlats) { *p_PitchName++ = 'D'; *p_PitchName++ = 'b'; } else { *p_PitchName++ = 'C'; *p_PitchName++ = '#'; } break; case 2: *p_PitchName++ = 'D'; break; case 3: if (bWantFlats) { *p_PitchName++ = 'E'; *p_PitchName++ = 'b'; } else { *p_PitchName++ = 'D'; *p_PitchName++ = '#'; } break; case 4: *p_PitchName++ = 'E'; break; case 5: *p_PitchName++ = 'F'; break; case 6: if (bWantFlats) { *p_PitchName++ = 'G'; *p_PitchName++ = 'b'; } else { *p_PitchName++ = 'F'; *p_PitchName++ = '#'; } break; case 7: *p_PitchName++ = 'G'; break; case 8: if (bWantFlats) { *p_PitchName++ = 'A'; *p_PitchName++ = 'b'; } else { *p_PitchName++ = 'G'; *p_PitchName++ = '#'; } break; case 9: *p_PitchName++ = 'A'; break; case 10: if (bWantFlats) { *p_PitchName++ = 'B'; *p_PitchName++ = 'b'; } else { *p_PitchName++ = 'A'; *p_PitchName++ = '#'; } break; case 11: *p_PitchName++ = 'B'; break; } *p_PitchName = '\0'; return gPitchName; }
// Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. 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) { 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; double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; sampleCount start = track->TimeToLongSamples(t0); int analyzeSize = windowSize * numWindows; float * buffer; buffer = new float[analyzeSize]; float * freq; freq = new float[windowSize/2]; float * freqa; freqa = new float[windowSize/2]; 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; delete [] freq; delete [] freqa; delete [] buffer; lag = (windowSize/2 - 1) - argmax; m_dStartFrequency = rate / lag; } double dFromMIDInote = FreqToMIDInote(m_dStartFrequency); double dToMIDInote = dFromMIDInote + m_dSemitonesChange; m_nFromPitch = PitchIndex(dFromMIDInote); m_nFromOctave = PitchOctave(dFromMIDInote); m_nToPitch = PitchIndex(dToMIDInote); m_nToOctave = PitchOctave(dToMIDInote); m_FromFrequency = m_dStartFrequency; Calc_PercentChange(); Calc_ToFrequency(); }
// PitchName takes pitchNum (as per result from // Freq2Pitch) and returns a standard pitch/note name [C, C#, etc.). // Sharps are the default, unless, bWantFlats is true. wxChar * PitchName(float pitchNum, bool bWantFlats /* = false */) { p_PitchName = gPitchName; switch (PitchIndex(pitchNum)) { case 0: *p_PitchName++ = wxT('C'); break; case 1: if (bWantFlats) { *p_PitchName++ = wxT('D'); *p_PitchName++ = wxT('b'); } else { *p_PitchName++ = wxT('C'); *p_PitchName++ = wxT('#'); } break; case 2: *p_PitchName++ = wxT('D'); break; case 3: if (bWantFlats) { *p_PitchName++ = wxT('E'); *p_PitchName++ = wxT('b'); } else { *p_PitchName++ = wxT('D'); *p_PitchName++ = wxT('#'); } break; case 4: *p_PitchName++ = wxT('E'); break; case 5: *p_PitchName++ = wxT('F'); break; case 6: if (bWantFlats) { *p_PitchName++ = wxT('G'); *p_PitchName++ = wxT('b'); } else { *p_PitchName++ = wxT('F'); *p_PitchName++ = wxT('#'); } break; case 7: *p_PitchName++ = wxT('G'); break; case 8: if (bWantFlats) { *p_PitchName++ = wxT('A'); *p_PitchName++ = wxT('b'); } else { *p_PitchName++ = wxT('G'); *p_PitchName++ = wxT('#'); } break; case 9: *p_PitchName++ = wxT('A'); break; case 10: if (bWantFlats) { *p_PitchName++ = wxT('B'); *p_PitchName++ = wxT('b'); } else { *p_PitchName++ = wxT('A'); *p_PitchName++ = wxT('#'); } break; case 11: *p_PitchName++ = wxT('B'); break; } *p_PitchName = wxT('\0'); return gPitchName; }