/* * 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 * * * PartialUtils.C * * A group of Partial utility function objects for use with STL * searching and sorting algorithms. PartialUtils is a namespace * within the Loris namespace. * * Kelly Fitz, 17 June 2003 * loris@cerlsoundgroup.org * * http://www.cerlsoundgroup.org/Loris/ * */ #if HAVE_CONFIG_H #include "config.h" #endif #include "PartialUtils.h" #include "Breakpoint.h" #include "BreakpointEnvelope.h" #include "BreakpointUtils.h" #include "Envelope.h" #include "Partial.h" #include "phasefix.h" #include #include #include #include // begin namespace namespace Loris { namespace PartialUtils { // -- base class -- // --------------------------------------------------------------------------- // PartialMutator constructor from double // --------------------------------------------------------------------------- PartialMutator::PartialMutator( double x ) : env( new BreakpointEnvelope( x ) ) { } // --------------------------------------------------------------------------- // PartialMutator constructor from envelope // --------------------------------------------------------------------------- PartialMutator::PartialMutator( const Envelope & e ) : env( e.clone() ) { } // --------------------------------------------------------------------------- // PartialMutator copy constructor // --------------------------------------------------------------------------- PartialMutator::PartialMutator( const PartialMutator & rhs ) : env( rhs.env->clone() ) { } // --------------------------------------------------------------------------- // PartialMutator destructor // --------------------------------------------------------------------------- PartialMutator::~PartialMutator( void ) { delete env; } // --------------------------------------------------------------------------- // PartialMutator assignment operator // --------------------------------------------------------------------------- PartialMutator & PartialMutator::operator=( const PartialMutator & rhs ) { if ( this != &rhs ) { delete env; env = rhs.env->clone(); } return *this; } // -- amplitude scaling -- // --------------------------------------------------------------------------- // AmplitudeScaler function call operator // --------------------------------------------------------------------------- // Scale the amplitude of the specified Partial according to // an envelope representing a time-varying amplitude scale value. // void AmplitudeScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setAmplitude( pos.breakpoint().amplitude() * env->valueAt( pos.time() ) ); } } // --------------------------------------------------------------------------- // BandwidthScaler function call operator // --------------------------------------------------------------------------- // Scale the bandwidth of the specified Partial according to // an envelope representing a time-varying bandwidth scale value. // void BandwidthScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setBandwidth( pos.breakpoint().bandwidth() * env->valueAt( pos.time() ) ); } } // --------------------------------------------------------------------------- // BandwidthSetter function call operator // --------------------------------------------------------------------------- // Set the bandwidth of the specified Partial according to // an envelope representing a time-varying bandwidth value. // void BandwidthSetter::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setBandwidth( env->valueAt( pos.time() ) ); } } // --------------------------------------------------------------------------- // FrequencyScaler function call operator // --------------------------------------------------------------------------- // Scale the frequency of the specified Partial according to // an envelope representing a time-varying frequency scale value. // void FrequencyScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setFrequency( pos.breakpoint().frequency() * env->valueAt( pos.time() ) ); } } // --------------------------------------------------------------------------- // NoiseRatioScaler function call operator // --------------------------------------------------------------------------- // Scale the relative noise content of the specified Partial according // to an envelope representing a (time-varying) noise energy // scale value. // void NoiseRatioScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { // compute new bandwidth value: double bw = pos.breakpoint().bandwidth(); if ( bw < 1. ) { double ratio = bw / (1. - bw); ratio *= env->valueAt( pos.time() ); bw = ratio / ( 1. + ratio ); } else { bw = 1.; } pos.breakpoint().setBandwidth( bw ); } } // --------------------------------------------------------------------------- // PitchShifter function call operator // --------------------------------------------------------------------------- // Shift the pitch of the specified Partial according to // the given pitch envelope. The pitch envelope is assumed to have // units of cents (1/100 of a halfstep). // void PitchShifter::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { // compute frequency scale: double scale = std::pow( 2., ( 0.01 * env->valueAt( pos.time() ) ) / 12. ); pos.breakpoint().setFrequency( pos.breakpoint().frequency() * scale ); } } // --------------------------------------------------------------------------- // Cropper function call operator // --------------------------------------------------------------------------- // Trim a Partial by removing Breakpoints outside a specified time span. // Insert a Breakpoint at the boundary when cropping occurs. // void Cropper::operator()( Partial & p ) const { // crop beginning of Partial Partial::iterator it = p.findAfter( minTime ); if ( it != p.begin() ) // Partial begins earlier than minTime { if ( it != p.end() ) // Partial ends later than minTime { Breakpoint bp = p.parametersAt( minTime ); it = p.insert( minTime, bp ); } it = p.erase( p.begin(), it ); } // crop end of Partial it = p.findAfter( maxTime ); if ( it != p.end() ) // Partial ends later than maxTime { if ( it != p.begin() ) // Partial begins earlier than maxTime { Breakpoint bp = p.parametersAt( maxTime ); it = p.insert( maxTime, bp ); ++it; // advance, we don't want to cut this one off } it = p.erase( it, p.end() ); } } // --------------------------------------------------------------------------- // TimeShifter function call operator // --------------------------------------------------------------------------- // Shift the time of all the Breakpoints in a Partial by a constant amount. // void TimeShifter::operator()( Partial & p ) const { // Since the Breakpoint times are immutable, the only way to // shift the Partial in time is to construct a new Partial and // assign it to the argument p. Partial result; result.setLabel( p.label() ); for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { result.insert( pos.time() + offset, pos.breakpoint() ); } p = result; } // --------------------------------------------------------------------------- // peakAmplitude // --------------------------------------------------------------------------- //! Return the maximum amplitude achieved by a partial. //! //! \param p is the Partial to evaluate //! \return the maximum (absolute) amplitude achieved by //! the partial p // double peakAmplitude( const Partial & p ) { double peak = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { peak = std::max( peak, it->amplitude() ); } return peak; } // --------------------------------------------------------------------------- // avgAmplitude // --------------------------------------------------------------------------- //! Return the average amplitude over all Breakpoints in this Partial. //! Return zero if the Partial has no Breakpoints. //! //! \param p is the Partial to evaluate //! \return the average amplitude of Breakpoints in the Partial p // double avgAmplitude( const Partial & p ) { double avg = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { avg += it->amplitude(); } if ( avg != 0 ) { avg /= p.numBreakpoints(); } return avg; } // --------------------------------------------------------------------------- // avgFrequency // --------------------------------------------------------------------------- //! Return the average frequency over all Breakpoints in this Partial. //! Return zero if the Partial has no Breakpoints. //! //! \param p is the Partial to evaluate //! \return the average frequency (Hz) of Breakpoints in the Partial p // double avgFrequency( const Partial & p ) { double avg = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { avg += it->frequency(); } if ( avg != 0 ) { avg /= p.numBreakpoints(); } return avg; } // --------------------------------------------------------------------------- // weightedAvgFrequency // --------------------------------------------------------------------------- //! Return the average frequency over all Breakpoints in this Partial, //! weighted by the Breakpoint amplitudes. //! Return zero if the Partial has no Breakpoints. //! //! \param p is the Partial to evaluate //! \return the average frequency (Hz) of Breakpoints in the Partial p // double weightedAvgFrequency( const Partial & p ) { double avg = 0; double ampsum = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { avg += it->amplitude() * it->frequency(); ampsum += it->amplitude(); } if ( avg != 0 && ampsum != 0 ) { avg /= ampsum; } else { avg = 0; } return avg; } // -- phase maintenance functions -- // --------------------------------------------------------------------------- // fixPhaseBefore // //! Recompute phases of all Breakpoints earlier than the specified time //! so that the synthesize phases of those earlier Breakpoints matches //! the stored phase, and the synthesized phase at the specified //! time matches the stored (not recomputed) phase. //! //! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint //! is encountered, because nulls are interpreted as phase reset points //! in Loris. If a null is encountered, the remainder of the Partial //! (the front part) is fixed in the forward direction, beginning at //! the start of the Partial. //! //! \param p The Partial whose phases should be fixed. //! \param t The time before which phases should be adjusted. // void fixPhaseBefore( Partial & p, double t ) { if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseBackward( p.begin(), pos ); } } // --------------------------------------------------------------------------- // fixPhaseAfter // //! Recompute phases of all Breakpoints later than the specified time //! so that the synthesize phases of those later Breakpoints matches //! the stored phase, as long as the synthesized phase at the specified //! time matches the stored (not recomputed) phase. //! //! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, //! because null Breakpoints are interpreted as phase reset points in //! Loris. If a null is encountered, its phase is simply left unmodified, //! and future phases wil be recomputed from that one. //! //! \param p The Partial whose phases should be fixed. //! \param t The time after which phases should be adjusted. // void fixPhaseAfter( Partial & p, double t ) { // nothing to do it there are not at least // two Breakpoints in the Partial if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseForward( pos, --p.end() ); } } // --------------------------------------------------------------------------- // fixPhaseForward // //! Recompute phases of all Breakpoints later than the specified time //! so that the synthesize phases of those later Breakpoints matches //! the stored phase, as long as the synthesized phase at the specified //! time matches the stored (not recomputed) phase. Breakpoints later than //! tend are unmodified. //! //! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, //! because null Breakpoints are interpreted as phase reset points in //! Loris. If a null is encountered, its phase is simply left unmodified, //! and future phases wil be recomputed from that one. //! //! HEY Is this interesting, in general? Why would you want to do this? //! //! \param p The Partial whose phases should be fixed. //! \param tbeg The phases and frequencies of Breakpoints later than the //! one nearest this time will be modified. //! \param tend The phases and frequencies of Breakpoints earlier than the //! one nearest this time will be modified. Should be greater //! than tbeg, or else they will be swapped. // void fixPhaseForward( Partial & p, double tbeg, double tend ) { if ( tbeg > tend ) { std::swap( tbeg, tend ); } // nothing to do it there are not at least // two Breakpoints in the Partial if ( 1 < p.numBreakpoints() ) { // find the positions nearest tbeg and tend Partial::iterator posbeg = p.findNearest( tbeg ); Partial::iterator posend = p.findNearest( tend ); // if the positions are different, and tend is // the end, back it up if ( posbeg != posend && posend == p.end() ) { --posend; } fixPhaseForward( posbeg, posend ); } } // --------------------------------------------------------------------------- // fixPhaseAt // //! Recompute phases of all Breakpoints in a Partial //! so that the synthesize phases match the stored phases, //! and the synthesized phase at (nearest) the specified //! time matches the stored (not recomputed) phase. //! //! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint //! is encountered, because nulls are interpreted as phase reset points //! in Loris. If a null is encountered, the remainder of the Partial //! (the front part) is fixed in the forward direction, beginning at //! the start of the Partial. Forward phase fixing is only applied //! to non-null (nonzero-amplitude) Breakpoints. If a null is encountered, //! its phase is simply left unmodified, and future phases wil be //! recomputed from that one. //! //! \param p The Partial whose phases should be fixed. //! \param t The time at which phases should be made correct. // void fixPhaseAt( Partial & p, double t ) { if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseForward( pos, --p.end() ); fixPhaseBackward( p.begin(), pos ); } } // --------------------------------------------------------------------------- // fixPhaseBetween // //! Fix the phase travel between two times by adjusting the //! frequency and phase of Breakpoints between those two times. //! //! This algorithm assumes that there is nothing interesting about the //! phases of the intervening Breakpoints, and modifies their frequencies //! as little as possible to achieve the correct amount of phase travel //! such that the frequencies and phases at the specified times //! match the stored values. The phases of all the Breakpoints between //! the specified times are recomputed. //! //! THIS DOES NOT YET TREAT NULL BREAKPOINTS DIFFERENTLY FROM OTHERS. //! //! \pre There must be at least one Breakpoint in the //! Partial between the specified times tbeg and tend. //! \post The phases and frequencies of the Breakpoints in the //! range have been recomputed such that an oscillator //! initialized to the parameters of the first Breakpoint //! will arrive at the parameters of the last Breakpoint, //! and all the intervening Breakpoints will be matched. //! \param p The partial whose phases and frequencies will be recomputed. //! The Breakpoint at this position is unaltered. //! \param tbeg The phases and frequencies of Breakpoints later than the //! one nearest this time will be modified. //! \param tend The phases and frequencies of Breakpoints earlier than the //! one nearest this time will be modified. Should be greater //! than tbeg, or else they will be swapped. // void fixPhaseBetween( Partial & p, double tbeg, double tend ) { if ( tbeg > tend ) { std::swap( tbeg, tend ); } // for Partials that do not extend over the entire // specified time range, just recompute phases from // beginning or end of the range: if ( p.endTime() < tend ) { // OK if start time is also after tbeg, will // just recompute phases from start of p. fixPhaseAfter( p, tbeg ); } else if ( p.startTime() > tbeg ) { fixPhaseBefore( p, tend ); } else { // invariant: // p begins before tbeg and ends after tend. Partial::iterator b = p.findNearest( tbeg ); Partial::iterator e = p.findNearest( tend ); // if there is a null Breakpoint n between b and e, then // should fix forward from b to n, and backward from // e to n. Otherwise, do this complicated thing. Partial::iterator nullbp = std::find_if( b, e, BreakpointUtils::isNull ); if ( nullbp != e ) { fixPhaseForward( b, nullbp ); fixPhaseBackward( nullbp, e ); } else { fixPhaseBetween( b, e ); } } } } // end of namespace PartialUtils } // end of namespace Loris