#ifndef INCLUDE_ANALYZER_H #define INCLUDE_ANALYZER_H /* * This is the Loris C++ Class Library, implementing analysis, * manipulation, and synthesis of digitized sounds using the Reassigned * Bandwidth-Enhanced Additive Sound Model. * * Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY, without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Analyzer.h * * Definition of class Loris::Analyzer. * * Kelly Fitz, 5 Dec 99 * loris@cerlsoundgroup.org * * http://www.cerlsoundgroup.org/Loris/ * */ #include #include #include #include "LinearEnvelope.h" #include "Partial.h" #include "PartialList.h" #include "SpectralPeaks.h" // begin namespace namespace Loris { class Envelope; // --------------------------------------------------------------------------- // LinearEnvelopeBuilder // --------------------------------------------------------------------------- // Base class for envelope builders that add a point (possibly) at each // analysis frame. // // TODO: make a dictionary of these things and allow clients to add their // own envelope builders and builder functions, and retrieve them after // analysis. class LinearEnvelopeBuilder { public: virtual ~LinearEnvelopeBuilder( void ) {} virtual LinearEnvelopeBuilder * clone( void ) const = 0; virtual void build( const Peaks & peaks, double frameTime ) = 0; const LinearEnvelope & envelope( void ) const { return mEnvelope; } // reset (clear) envelope, override if necesssary: virtual void reset( void ) { mEnvelope.clear(); } protected: LinearEnvelope mEnvelope; // build this }; // --------------------------------------------------------------------------- // FundamentalBuilder - for constructing an F0 envelope during analysis // --------------------------------------------------------------------------- class FundamentalBuilder : public LinearEnvelopeBuilder { std::auto_ptr< Envelope > mFminEnv; std::auto_ptr< Envelope > mFmaxEnv; double mAmpThresh, mFreqThresh; std::vector< double > amplitudes, frequencies; const double mMinConfidence; // 0.9, this could be made a parameter, // or raised to make estimates smoother public: FundamentalBuilder( double fmin, double fmax, double threshDb = -60, double threshHz = 8000 ) : mFminEnv( new LinearEnvelope( fmin ) ), mFmaxEnv( new LinearEnvelope( fmax ) ), mAmpThresh( std::pow( 10., 0.05*(threshDb) ) ), mFreqThresh( threshHz ), mMinConfidence( 0.9 ) {} FundamentalBuilder( const Envelope & fmin, const Envelope & fmax, double threshDb = -60, double threshHz = 8000 ) : mFminEnv( fmin.clone() ), mFmaxEnv( fmax.clone() ), mAmpThresh( std::pow( 10., 0.05*(threshDb) ) ), mFreqThresh( threshHz ), mMinConfidence( 0.9 ) {} FundamentalBuilder( const FundamentalBuilder & rhs ) : mFminEnv( rhs.mFminEnv->clone() ), mFmaxEnv( rhs.mFmaxEnv->clone() ), mAmpThresh( rhs.mAmpThresh ), mFreqThresh( rhs.mFreqThresh ), mMinConfidence( rhs.mMinConfidence ) {} FundamentalBuilder * clone( void ) const { return new FundamentalBuilder(*this); } void build( const Peaks & peaks, double frameTime ); }; // --------------------------------------------------------------------------- // AmpEnvBuilder - for constructing an amplitude envelope during analysis // --------------------------------------------------------------------------- class AmpEnvBuilder : public LinearEnvelopeBuilder { public: AmpEnvBuilder( void ) {} AmpEnvBuilder * clone( void ) const { return new AmpEnvBuilder(*this); } void build( const Peaks & peaks, double frameTime ); }; // --------------------------------------------------------------------------- // class Analyzer // //! Class Analyzer represents a configuration of parameters for //! performing Reassigned Bandwidth-Enhanced Additive Analysis //! of sampled sounds. The analysis process yields a collection //! of Partials, each having a trio of synchronous, non-uniformly- //! sampled breakpoint envelopes representing the time-varying //! frequency, amplitude, and noisiness of a single bandwidth- //! enhanced sinusoid. These Partials are accumulated in the //! Analyzer. //! //! The core analysis parameter is the frequency resolution, the //! minimum instantaneous frequency spacing between partials. Most //! other parameters are initially configured according to this //! parameter (and the analysis window width, if specified). //! Subsequent parameter mutations are independent. //! //! Bandwidth enhancement: //! Two different strategies are available for computing bandwidth //! (or noisiness) envelope: //! //! One strategy is to construct bandwidth envelopes during analysis //! by associating residual energy in the spectrum (after peak //! extraction) with the selected spectral peaks that are used //! to construct Partials. This is the original bandwidth enhancement //! algorithm, and bandwidth envelopes constructed in this way may //! be suitable for use in bandwidth-enhanced synthesis. //! //! Another stategy is to construct bandwidth envelopes during //! analysis by storing the mixed derivative of short-time phase, //! scaled and shifted so that a value of 0 corresponds //! to a pure sinusoid, and a value of 1 corresponds to a //! bandwidth-enhanced sinusoid with maximal energy spread //! (minimum convergence in frequency). These bandwidth envelopes //! are not suitable for bandwidth-enhanced synthesis, be sure //! to set the bandwidth to 0, or to disable bandwidth enhancement //! before rendering. //! //! The Analyzer may be configured to use either of these two //! strategies for bandwidth-enhanced analysis, or to construct //! no bandwidth envelopes at all. If unspecified, the default //! Analyzer configuration uses spectral residue to construct //! bandwidth envelopes. //! //! \sa storeResidueBandwidth, storeConvergenceBandwidth, storeNoBandwidth //! //! For more information about Reassigned Bandwidth-Enhanced //! Analysis and the Reassigned Bandwidth-Enhanced Additive Sound //! Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. // class Analyzer { // -- public interface -- public: // -- construction -- //! Construct a new Analyzer configured with the given //! frequency resolution (minimum instantaneous frequency //! difference between Partials). All other Analyzer parameters //! are computed from the specified frequency resolution. //! //! \param resolutionHz is the frequency resolution in Hz. explicit Analyzer( double resolutionHz ); //! Construct a new Analyzer configured with the given //! frequency resolution (minimum instantaneous frequency //! difference between Partials) and analysis window width //! (main lobe, zero-to-zero). All other Analyzer parameters //! are computed from the specified resolution and window width. //! //! \param resolutionHz is the frequency resolution in Hz. //! \param windowWidthHz is the main lobe width of the Kaiser //! analysis window in Hz. Analyzer( double resolutionHz, double windowWidthHz ); //! Construct a new Analyzer configured with the given time-varying //! frequency resolution (minimum instantaneous frequency //! difference between Partials) and analysis window width //! (main lobe, zero-to-zero). All other Analyzer parameters //! are computed from the specified resolution and window width. //! //! \param resolutionHz is the frequency resolution in Hz. //! \param windowWidthHz is the main lobe width of the Kaiser //! analysis window in Hz. Analyzer( const Envelope & resolutionEnv, double windowWidthHz ); //! Construct a new Analyzer having identical //! parameter configuration to another Analyzer. //! The list of collected Partials is not copied. //! //! \param other is the Analyzer to copy. Analyzer( const Analyzer & other ); //! Destroy this Analyzer. ~Analyzer( void ); //! Construct a new Analyzer having identical //! parameter configuration to another Analyzer. //! The list of collected Partials is not copied. //! //! \param rhs is the Analyzer to copy. Analyzer & operator=( const Analyzer & rhs ); // -- configuration -- //! Configure this Analyzer with the given frequency resolution //! (minimum instantaneous frequency difference between Partials, //! in Hz). All other Analyzer parameters are (re-)computed from the //! frequency resolution, including the window width, which is //! twice the resolution. //! //! \param resolutionHz is the frequency resolution in Hz. void configure( double resolutionHz ); //! Configure this Analyzer with the given frequency resolution //! (minimum instantaneous frequency difference between Partials) //! and analysis window width (main lobe, zero-to-zero, in Hz). //! All other Analyzer parameters are (re-)computed from the //! frequency resolution and window width. //! //! \param resolutionHz is the frequency resolution in Hz. //! \param windowWidthHz is the main lobe width of the Kaiser //! analysis window in Hz. //! //! There are three categories of analysis parameters: //! - the resolution, and params that are usually related to (or //! identical to) the resolution (frequency floor and drift) //! - the window width and params that are usually related to (or //! identical to) the window width (hop and crop times) //! - independent parameters (bw region width and amp floor) void configure( double resolutionHz, double windowWidthHz ); //! Configure this Analyzer with the given time-varying frequency resolution //! (minimum instantaneous frequency difference between Partials) //! and analysis window width (main lobe, zero-to-zero, in Hz). //! All other Analyzer parameters are (re-)computed from the //! frequency resolution and window width. //! //! \param resolutionEnv is the time-varying frequency resolution //! in Hz. //! \param windowWidthHz is the main lobe width of the Kaiser //! analysis window in Hz. //! //! There are three categories of analysis parameters: //! - the resolution, and params that are usually related to (or //! identical to) the resolution (frequency floor and drift) //! - the window width and params that are usually related to (or //! identical to) the window width (hop and crop times) //! - independent parameters (bw region width and amp floor) // void configure( const Envelope & resolutionEnv, double windowWidthHz ); // -- analysis -- //! Analyze a vector of (mono) samples at the given sample rate //! (in Hz) and store the extracted Partials in the Analyzer's //! PartialList (std::list of Partials). //! //! \param vec is a vector of floating point samples //! \param srate is the sample rate of the samples in the vector void analyze( const std::vector & vec, double srate ); //! 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). //! //! \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 void analyze( const double * bufBegin, const double * bufEnd, double srate ); // -- tracking analysis -- //! Analyze a vector 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 vec is a vector of floating point samples //! \param srate is the sample rate of the samples in the vector //! \param reference is an Envelope having the approximate //! frequency contour expected of the resulting Partials. void analyze( const std::vector & vec, double srate, const Envelope & reference ); //! 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. virtual void analyze( const double * bufBegin, const double * bufEnd, double srate, const Envelope & reference ); // -- parameter access -- //! Return the amplitude floor (lowest detected spectral amplitude), //! in (negative) dB, for this Analyzer. double ampFloor( void ) const; //! Return the crop time (maximum temporal displacement of a time- //! frequency data point from the time-domain center of the analysis //! window, beyond which data points are considered "unreliable") //! for this Analyzer. double cropTime( void ) const; //! Return the maximum allowable frequency difference between //! consecutive Breakpoints in a Partial envelope for this Analyzer. double freqDrift( void ) const; //! Return the frequency floor (minimum instantaneous Partial //! frequency), in Hz, for this Analyzer. double freqFloor( void ) const; //! Return the frequency resolution (minimum instantaneous frequency //! difference between Partials) for this Analyzer at the specified //! time in seconds. If no time is specified, then the initial resolution //! (at 0 seconds) is returned. //! //! \param time is the time in seconds at which to evaluate the //! frequency resolution double freqResolution( double time = 0.0 ) const; //! Return the hop time (which corresponds approximately to the //! average density of Partial envelope Breakpoint data) for this //! Analyzer. double hopTime( void ) const; //! Return the sidelobe attenutation level for the Kaiser analysis window in //! positive dB. Larger numbers (e.g. 90) give very good sidelobe //! rejection but cause the window to be longer in time. Smaller numbers //! (like 60) raise the level of the sidelobes, increasing the likelihood //! of frequency-domain interference, but allow the window to be shorter //! in time. double sidelobeLevel( void ) const; //! Return the frequency-domain main lobe width (measured between //! zero-crossings) of the analysis window used by this Analyzer. double windowWidth( void ) const; //! Return true if the phases and frequencies of the constructed //! partials should be modified to be consistent at the end of the //! analysis, and false otherwise. (Default is true.) bool phaseCorrect( void ) const; // -- parameter mutation -- //! Set the amplitude floor (lowest detected spectral amplitude), in //! (negative) dB, for this Analyzer. //! //! \param x is the new value of this parameter. void setAmpFloor( double x ); //! Set the crop time (maximum temporal displacement of a time- //! frequency data point from the time-domain center of the analysis //! window, beyond which data points are considered "unreliable") //! for this Analyzer. //! //! \param x is the new value of this parameter. void setCropTime( double x ); //! Set the maximum allowable frequency difference between //! consecutive Breakpoints in a Partial envelope for this Analyzer. //! //! \param x is the new value of this parameter. void setFreqDrift( double x ); //! Set the frequency floor (minimum instantaneous Partial //! frequency), in Hz, for this Analyzer. //! //! \param x is the new value of this parameter. void setFreqFloor( double x ); //! Set the frequency resolution (minimum instantaneous frequency //! difference between Partials) for this Analyzer. (Does not cause //! other parameters to be recomputed.) //! //! \param x is the new value of this parameter. void setFreqResolution( double x ); //! Set the time-varying frequency resolution (minimum instantaneous frequency //! difference between Partials) for this Analyzer. (Does not cause //! other parameters to be recomputed.) //! //! \param e is the envelope to copy for this parameter. void setFreqResolution( const Envelope & e ); //! Set the hop time (which corresponds approximately to the average //! density of Partial envelope Breakpoint data) for this Analyzer. //! //! \param x is the new value of this parameter. void setHopTime( double x ); //! Set the sidelobe attenutation level for the Kaiser analysis window in //! positive dB. More negative numbers (e.g. -90) give very good sidelobe //! rejection but cause the window to be longer in time. Less negative //! numbers raise the level of the sidelobes, increasing the likelihood //! of frequency-domain interference, but allow the window to be shorter //! in time. //! //! \param x is the new value of this parameter. void setSidelobeLevel( double x ); //! Set the frequency-domain main lobe width (measured between //! zero-crossings) of the analysis window used by this Analyzer. //! //! \param x is the new value of this parameter. void setWindowWidth( double x ); //! Indicate whether the phases and frequencies of the constructed //! partials should be modified to be consistent at the end of the //! analysis. (Default is true.) //! //! \param TF is a flag indicating whether or not to construct //! phase-corrected Partials void setPhaseCorrect( bool TF = true ); // -- bandwidth envelope specification -- enum { Default_ResidueBandwidth_RegionWidth = 2000, Default_ConvergenceBandwidth_TolerancePct = 10 }; //! Construct Partial bandwidth envelopes during analysis //! by associating residual energy in the spectrum (after //! peak extraction) with the selected spectral peaks that //! are used to construct Partials. //! //! This is the default bandwidth-enhancement strategy. //! //! \param regionWidth is the width (in Hz) of the bandwidth //! association regions used by this process, must be positive. //! If unspecified, a default value is used. void storeResidueBandwidth( double regionWidth = Default_ResidueBandwidth_RegionWidth ); //! Construct Partial bandwidth envelopes during analysis //! by storing the mixed derivative of short-time phase, //! scaled and shifted so that a value of 0 corresponds //! to a pure sinusoid, and a value of 1 corresponds to a //! bandwidth-enhanced sinusoid with maximal energy spread //! (minimum sinusoidal convergence). //! //! \param tolerance is the amount of range over which the //! mixed derivative indicator should be allowed to drift away //! from a pure sinusoid before saturating. This range is mapped //! to bandwidth values on the range [0,1]. Must be positive and //! not greater than 1. If unspecified, a default value is used. void storeConvergenceBandwidth( double tolerancePct = 0.01 * (double)Default_ConvergenceBandwidth_TolerancePct ); //! Disable bandwidth envelope construction. Bandwidth //! will be zero for all Breakpoints in all Partials. void storeNoBandwidth( void ); //! Return true if this Analyzer is configured to compute //! bandwidth envelopes using the spectral residue after //! peaks have been identified, and false otherwise. bool bandwidthIsResidue( void ) const; //! Return true if this Analyzer is configured to compute //! bandwidth envelopes using the mixed derivative convergence //! indicator, and false otherwise. bool bandwidthIsConvergence( void ) const; //! Return the width (in Hz) of the Bandwidth Association regions //! used by this Analyzer, only if the spectral residue method is //! used to compute bandwidth envelopes. Return zero if the mixed //! derivative method is used, or if no bandwidth is computed. double bwRegionWidth( void ) const; //! Return the mixed derivative convergence tolerance (percent) //! only if the convergence indicator is used to compute //! bandwidth envelopes. Return zero if the spectral residue //! method is used or if no bandwidth is computed. double bwConvergenceTolerance( void ) const; //! Return true if bandwidth envelopes are to be constructed //! by any means, that is, if either bandwidthIsResidue() or //! bandwidthIsConvergence() are true. Otherwise, return //! false. bool associateBandwidth( void ) const { return bandwidthIsResidue() || bandwidthIsConvergence(); } //! Deprecated, use storeResidueBandwidth or storeNoBandwidth instead. void setBwRegionWidth( double x ) { if ( x != 0 ) { storeResidueBandwidth( x ); } else { storeNoBandwidth(); } } // -- PartialList access -- //! Return a mutable reference to this Analyzer's list of //! analyzed Partials. PartialList & partials( void ); //! Return an immutable (const) reference to this Analyzer's //! list of analyzed Partials. const PartialList & partials( void ) const; // -- envelope access -- enum { Default_FundamentalEnv_ThreshDb = -60, Default_FundamentalEnv_ThreshHz = 8000 }; //! Specify parameters for constructing a fundamental frequency //! envelope for the analyzed sound during analysis. The fundamental //! frequency estimate can be accessed by fundamentalEnv() after the //! analysis is complete. //! //! By default, a fundamental envelope is estimated during analysis //! between the frequency resolution and 1.5 times the resolution. //! //! \param fmin is the lower bound on the fundamental frequency estimate //! \param fmax is the upper bound on the fundamental frequency estimate //! \param threshDb is the lower bound on the amplitude of a spectral peak //! that will constribute to the fundamental frequency estimate (very //! low amplitude peaks tend to have less reliable frequency estimates). //! Default is -60 dB. //! \param threshHz is the upper bound on the frequency of a spectral //! peak that will constribute to the fundamental frequency estimate. //! Default is 8 kHz. void buildFundamentalEnv( double fmin, double fmax, double threshDb = Default_FundamentalEnv_ThreshDb, double threshHz = Default_FundamentalEnv_ThreshHz ); //! Return the fundamental frequency estimate envelope constructed //! during the most recent analysis performed by this Analyzer. //! //! By default, a fundamental envelope is estimated during analysis //! between the frequency resolution and 1.5 times the resolution. const LinearEnvelope & fundamentalEnv( void ) const; //! Return the overall amplitude estimate envelope constructed //! during the most recent analysis performed by this Analyzer. const LinearEnvelope & ampEnv( void ) const; // -- legacy support -- // Fundamental and amplitude envelopes are always constructed during // analysis, these members do nothing, and are retained for backwards // compatibility. void buildAmpEnv( bool TF = true ) { TF = TF; } void buildFundamentalEnv( bool TF = true ) { TF = TF; } // -- private member variables -- protected: std::auto_ptr< Envelope > m_freqResolutionEnv; //! in Hz, minimum instantaneous frequency distance; //! this is the core parameter, others are, by default, //! computed from this one double m_ampFloor; //! dB, relative to full amplitude sine wave, absolute //! amplitude threshold (negative) double m_windowWidth; //! in Hz, width of main lobe; this might be more //! conveniently presented as window length, but //! the main lobe width more explicitly highlights //! the critical interaction with resolution // std::auto_ptr< Envelope > m_freqFloorEnv; double m_freqFloor; //! lowest frequency (Hz) component extracted //! in spectral analysis double m_freqDrift; //! the maximum frequency (Hz) difference between two //! consecutive Breakpoints that will be linked to //! form a Partial double m_hopTime; //! in seconds, time between analysis windows in //! successive spectral analyses double m_cropTime; //! in seconds, maximum time correction for a spectral //! component to be considered reliable, and to be eligible //! for extraction and for Breakpoint formation double m_bwAssocParam; //! formerly, width in Hz of overlapping bandwidth //! association regions, or zero if bandwidth association //! is disabled, now a catch-all bandwidth association //! parameter that, if negative, indicates the tolerance (%) //! level used to construct bandwidth envelopes from the //! mixed phase derivative indicator double m_sidelobeLevel; //! sidelobe attenutation level for the Kaiser analysis //! window, in positive dB bool m_phaseCorrect; //! flag indicating that phases/frequencies should be //! made consistent at the end of the analysis PartialList m_partials; //! collect Partials here //! builder object for constructing a fundamental frequency //! estimate during analysis std::auto_ptr< LinearEnvelopeBuilder > m_f0Builder; //! builder object for constructing an amplitude //! estimate during analysis std::auto_ptr< LinearEnvelopeBuilder > m_ampEnvBuilder; // -- private auxiliary functions -- // future development /* // These members make up the sequence of operations in an // analysis. If analysis were ever to be made into a // template method, these would be the operations that // derived classes could override. Or each of these could // be represented by a strategy class. //! Compute the spectrum of the next sequence of samples. void computeSpectrum( void ); //! Identify and select the spectral components that will be //! used to form Partials. void selectPeaks( void ); //! Compute the bandwidth coefficients for the Breakpoints //! that are going to be used to form Partials. void associateBandwidth( void ); //! Construct Partials from extracted spectral components. //! Partials are built up frame by frame by appending //! Breakpoints to Partials under construction, and giving //! birth to new Partials using unmatched Peaks. void formPartials( Peaks & peaks ); */ // Reject peaks that are too close in frequency to a louder peak that is // being retained, and peaks that are too quiet. Peaks that are retained, // but are quiet enough to be in the specified fadeRange should be faded. // // 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. Peaks::iterator thinPeaks( Peaks & peaks, double frameTime ); // Fix the bandwidth value stored in the specified Peaks. // This function is invoked if the spectral residue method is // not used to compute bandwidth (that method overwrites the // bandwidth already). If the convergence method is used to // compute bandwidth, the appropriate scaling is applied // to the stored mixed phase derivative. Otherwise, the // Peak bandwidth is set to zero. void fixBandwidth( Peaks & peaks ); }; // end of class Analyzer } // end of namespace Loris #endif /* ndef INCLUDE_ANALYZER_H */