summaryrefslogtreecommitdiff
path: root/src/loris/PartialUtils.C
diff options
context:
space:
mode:
Diffstat (limited to 'src/loris/PartialUtils.C')
-rw-r--r--src/loris/PartialUtils.C603
1 files changed, 603 insertions, 0 deletions
diff --git a/src/loris/PartialUtils.C b/src/loris/PartialUtils.C
new file mode 100644
index 0000000..6152e09
--- /dev/null
+++ b/src/loris/PartialUtils.C
@@ -0,0 +1,603 @@
+/*
+ * 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 <algorithm>
+#include <cmath>
+#include <functional>
+#include <utility>
+
+// 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
+