// --------------------------------------------------------------------------- // analyze // --------------------------------------------------------------------------- //! Analyze a range of (mono) samples at the given sample rate //! (in Hz) and store the extracted Partials in the Analyzer's //! PartialList (std::list of Partials). Use the specified envelope //! as a frequency reference for Partial tracking. //! //! \param bufBegin is a pointer to a buffer of floating point samples //! \param bufEnd is (one-past) the end of a buffer of floating point //! samples //! \param srate is the sample rate of the samples in the buffer //! \param reference is an Envelope having the approximate //! frequency contour expected of the resulting Partials. // void Analyzer::analyze( const double * bufBegin, const double * bufEnd, double srate, const Envelope & reference ) { // configure the reassigned spectral analyzer, // always use odd-length windows: // Kaiser window double winshape = KaiserWindow::computeShape( sidelobeLevel() ); long winlen = KaiserWindow::computeLength( windowWidth() / srate, winshape ); if (! (winlen % 2)) { ++winlen; } debugger << "Using Kaiser window of length " << winlen << endl; std::vector< double > window( winlen ); KaiserWindow::buildWindow( window, winshape ); std::vector< double > windowDeriv( winlen ); KaiserWindow::buildTimeDerivativeWindow( windowDeriv, winshape ); ReassignedSpectrum spectrum( window, windowDeriv ); // configure the peak selection and partial formation policies: SpectralPeakSelector selector( srate, m_cropTime ); PartialBuilder builder( m_freqDrift, reference ); // configure bw association policy, unless // bandwidth association is disabled: std::unique_ptr< AssociateBandwidth > bwAssociator; if( m_bwAssocParam > 0 ) { debugger << "Using bandwidth association regions of width " << bwRegionWidth() << " Hz" << endl; bwAssociator.reset( new AssociateBandwidth( bwRegionWidth(), srate ) ); } else { debugger << "Bandwidth association disabled" << endl; } // reset envelope builders: m_ampEnvBuilder->reset(); m_f0Builder->reset(); m_partials.clear(); try { const double * winMiddle = bufBegin; // loop over short-time analysis frames: while ( winMiddle < bufEnd ) { // compute the time of this analysis frame: const double currentFrameTime = long(winMiddle - bufBegin) / srate; // compute reassigned spectrum: // sampsBegin is the position of the first sample to be transformed, // sampsEnd is the position after the last sample to be transformed. // (these computations work for odd length windows only) const double * sampsBegin = std::max( winMiddle - (winlen / 2), bufBegin ); const double * sampsEnd = std::min( winMiddle + (winlen / 2) + 1, bufEnd ); spectrum.transform( sampsBegin, winMiddle, sampsEnd ); // extract peaks from the spectrum, and thin Peaks peaks = selector.selectPeaks( spectrum, m_freqFloor ); Peaks::iterator rejected = thinPeaks( peaks, currentFrameTime ); // fix the stored bandwidth values // KLUDGE: need to do this before the bandwidth // associator tries to do its job, because the mixed // derivative is temporarily stored in the Breakpoint // bandwidth!!! FIX!!!! fixBandwidth( peaks ); if ( m_bwAssocParam > 0 ) { bwAssociator->associateBandwidth( peaks.begin(), rejected, peaks.end() ); } // remove rejected Breakpoints (needed above to // compute bandwidth envelopes): peaks.erase( rejected, peaks.end() ); // estimate the amplitude in this frame: m_ampEnvBuilder->build( peaks, currentFrameTime ); // collect amplitudes and frequencies and try to // estimate the fundamental m_f0Builder->build( peaks, currentFrameTime ); // form Partials from the extracted Breakpoints: builder.buildPartials( peaks, currentFrameTime ); // slide the analysis window: winMiddle += long( m_hopTime * srate ); // hop in samples, truncated } // end of loop over short-time frames // unwarp the Partial frequency envelopes: builder.finishBuilding( m_partials ); // fix the frequencies and phases to be consistent. if ( m_phaseCorrect ) { fixFrequency( m_partials.begin(), m_partials.end() ); } // for debugging: /* if ( ! m_ampEnv.empty() ) { LinearEnvelope::iterator peakpos = std::max_element( m_ampEnv.begin(), m_ampEnv.end(), compare2nd<LinearEnvelope::iterator::value_type> ); notifier << "Analyzer found amp peak at time : " << peakpos->first << " value: " << peakpos->second << endl; } */ } catch ( Exception & ex ) { ex.append( "analysis failed." ); throw; } }
// --------------------------------------------------------------------------- // thinPeaks (HELPER) // --------------------------------------------------------------------------- // Reject peaks that are too quiet (low amplitude). Peaks that are retained, // but are quiet enough to be in the specified fadeRange should be faded. // Peaks having negative times are also rejected. // // This is exactly the same as the basic peak selection strategy, there // is no tracking here. // // Rejected peaks are placed at the end of the peak collection. // Return the first position in the collection containing a rejected peak, // or the end of the collection if no peaks are rejected. // // This used to be part of SpectralPeakSelector, but it really had no place // there. It _should_ remove the rejected peaks, but for now, those are needed // by the bandwidth association strategy. // Peaks::iterator Analyzer::thinPeaks( Peaks & peaks, double frameTime ) { const double ampFloordB = m_ampFloor; // fade quiet peaks out over 10 dB: const double fadeRangedB = 10.0; // compute absolute magnitude thresholds: const double threshold = std::pow( 10., 0.05 * ampFloordB ); const double beginFade = std::pow( 10., 0.05 * (ampFloordB+fadeRangedB) ); // louder peaks are preferred, so consider them // in order of louder magnitude: std::sort( peaks.begin(), peaks.end(), SpectralPeak::sort_greater_amplitude ); // negative times are not real, but still might represent // a noisy part of the spectrum... Peaks::iterator bogusTimes = std::remove_if( peaks.begin(), peaks.end(), negative_time( frameTime ) ); // ...get rid of them anyway peaks.erase( bogusTimes, peaks.end() ); bogusTimes = peaks.end(); Peaks::iterator it = peaks.begin(); Peaks::iterator beginRejected = it; const double freqResolution = std::max( m_freqResolutionEnv->valueAt( frameTime ), 0.0 ); while ( it != peaks.end() ) { SpectralPeak & pk = *it; // keep this peak if it is loud enough and not // too near in frequency to a louder one: double lower = pk.frequency() - freqResolution; double upper = pk.frequency() + freqResolution; if ( pk.amplitude() > threshold && beginRejected == std::find_if( peaks.begin(), beginRejected, can_mask(lower, upper) ) ) { // this peak is a keeper, fade its // amplitude if it is too quiet: if ( pk.amplitude() < beginFade ) { double alpha = (beginFade - pk.amplitude())/(beginFade - threshold); pk.setAmplitude( pk.amplitude() * (1. - alpha) ); } // keep retained peaks at the front of the collection: if ( it != beginRejected ) { std::swap( *it, *beginRejected ); } ++beginRejected; } ++it; } // debugger << "thinPeaks retained " << std::distance( peaks.begin(), beginRejected ) << endl; // remove rejected Breakpoints: //peaks.erase( beginRejected, peaks.end() ); return beginRejected; }