summaryrefslogtreecommitdiff
path: root/src/loris/Resampler.C
diff options
context:
space:
mode:
Diffstat (limited to 'src/loris/Resampler.C')
-rw-r--r--src/loris/Resampler.C398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/loris/Resampler.C b/src/loris/Resampler.C
new file mode 100644
index 0000000..be39674
--- /dev/null
+++ b/src/loris/Resampler.C
@@ -0,0 +1,398 @@
+/*
+ * 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
+ *
+ *
+ * Resampler.C
+ *
+ * Implementation of class Resampler, for converting reassigned Partial envelopes
+ * into more conventional additive synthesis envelopes, having data points
+ * at regular time intervals. The benefits of reassigned analysis are NOT
+ * lost in this process, since the elimination of unreliable data and the
+ * reduction of temporal smearing are reflected in the resampled data.
+ *
+ * Lippold, 7 Aug 2003
+ * loris@cerlsoundgroup.org
+ *
+ * http://www.cerlsoundgroup.org/Loris/
+ *
+ *
+ * Phase correction added by Kelly 13 Dec 2005.
+ */
+#if HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "Resampler.h"
+#include "Breakpoint.h"
+#include "LinearEnvelope.h"
+#include "LorisExceptions.h"
+#include "Notifier.h"
+#include "Partial.h"
+#include "phasefix.h"
+
+#include <cmath>
+
+// begin namespace
+namespace Loris {
+
+// helper declarations:
+static Partial::iterator insert_resampled_at( Partial & newp, const Partial & p,
+ double sampleTime, double insertTime );
+
+/*
+TODO
+ - remove empties (currently handled automatically in the Python module
+
+ - remove insert_resampled_at
+
+ - phase correct with timing?
+
+ - fade time (for amplitude envelope sampling) - equal to interval? half?
+*/
+
+
+// ---------------------------------------------------------------------------
+// constructor - sampling interval
+// ---------------------------------------------------------------------------
+//! Initialize a Resampler having the specified uniform sampling
+//! interval. Enable phase-correct resampling, in which frequencies
+//! of resampled Partials are modified (using fixFrequency) such
+//! that the resampled phases are achieved in synthesis. Phase-
+//! correct resampling can be disabled using setPhaseCorrect.
+//!
+//! Resampled Partials will be composed of Breakpoints at every
+//! integer multiple of the resampling interval.
+//!
+//! \sa setPhaseCorrect
+//! \sa fixFrequency
+//!
+//! \param sampleInterval is the resampling interval in seconds,
+//! Breakpoint data is computed at integer multiples of
+//! sampleInterval seconds.
+//! \throw InvalidArgument if sampleInterval is not positive.
+//
+Resampler::Resampler( double sampleInterval ) :
+ interval_( sampleInterval ),
+ phaseCorrect_( true )
+{
+ if ( sampleInterval <= 0. )
+ {
+ Throw( InvalidArgument, "Resampler sample interval must be positive." );
+ }
+}
+
+// ---------------------------------------------------------------------------
+// setPhaseCorrect
+// ---------------------------------------------------------------------------
+//! Specify phase-corrected resampling, or not. If phase
+//! correct, Partial frequencies are altered slightly
+//! to match, as nearly as possible, the Breakpoint
+//! phases after resampling. Phases are updated so that
+//! the Partial frequencies and phases are consistent after
+//! resampling.
+//!
+//! \param correctPhase is a boolean flag specifying that
+//! (if true) frequency/phase correction should be
+//! applied after resampling.
+void Resampler::setPhaseCorrect( bool correctPhase )
+{
+ phaseCorrect_ = correctPhase;
+}
+
+// ---------------------------------------------------------------------------
+// resample
+// ---------------------------------------------------------------------------
+//! Resample a Partial using this Resampler's stored quanitization interval.
+//! If sparse resampling (the default) has be selected, Breakpoint times
+//! are quantized to integer multiples of the resampling interval.
+//! If dense resampling is selected, a Breakpoint will be provided at
+//! every integer multiple of the resampling interval in the time span of
+//! the Partial, starting and ending with the nearest multiples to the
+//! ends of the Partial. Frequencies and phases are corrected to be in
+//! agreement and to match as nearly as possible the resampled phases if
+//! phase correct resampling is specified (the default). Resampling
+//! is performed in-place.
+//!
+//! \param p is the Partial to resample
+//
+void
+Resampler::resample( Partial & p ) const
+{
+ debugger << "resampling Partial labeled " << p.label()
+ << " having " << p.numBreakpoints()
+ << " Breakpoints" << endl;
+
+
+ // create the new Partial:
+ Partial newp;
+ newp.setLabel( p.label() );
+
+ // find time of first and last breakpoint for the resampled envelope:
+ double firstInsertTime = interval_ * int( 0.5 + p.startTime() / interval_ );
+ double lastInsertTime = p.endTime() + ( 0.5 * interval_ );
+
+ // resample:
+ for ( double tins = firstInsertTime; tins <= lastInsertTime; tins += interval_ )
+ {
+ // sample time is obtained from the timing envelope, if specified,
+ // otherwise same as the insert time:
+ double tsamp = tins;
+ insert_resampled_at( newp, p, tins, tins );
+ }
+
+ // store the new Partial:
+ p = newp;
+
+ debugger << "resampled Partial has " << p.numBreakpoints()
+ << " Breakpoints" << endl;
+
+
+ if ( phaseCorrect_ )
+ {
+ fixFrequency( p ); // use default maxFixPct
+ }
+}
+
+// ---------------------------------------------------------------------------
+// resample
+// ---------------------------------------------------------------------------
+//! Resample a Partial using this Resampler's stored quanitization interval.
+//! If sparse resampling (the default) has be selected, Breakpoint times
+//! are quantized to integer multiples of the resampling interval.
+//! If dense resampling is selected, a Breakpoint will be provided at
+//! every integer multiple of the resampling interval in the time span of
+//! the Partial, starting and ending with the nearest multiples to the
+//! ends of the Partial. Frequencies and phases are corrected to be in
+//! agreement and to match as nearly as possible the resampled phases if
+//! phase correct resampling is specified (the default). Resampling
+//! is performed in-place.
+//!
+//! \param p is the Partial to resample
+//!
+//! \param timingEnv is the timing envelope, a map of Breakpoint
+//! times in resampled Partials onto parameter sampling
+//! instants in the original Partials.
+//!
+//! \throw InvalidArgument if timingEnv has any negative breakpoint
+//! times or values.
+//
+void
+Resampler::resample( Partial & p, const LinearEnvelope & timingEnv ) const
+{
+ debugger << "resampling Partial labeled " << p.label()
+ << " having " << p.numBreakpoints()
+ << " Breakpoints" << endl;
+
+
+ Assert( 0 != timingEnv.size() );
+
+ // create the new Partial:
+ Partial newp;
+ newp.setLabel( p.label() );
+
+ // find the extent of the timing envelope, if specified, otherwise
+ // the insert time range is the same as the sample time range:
+ double firstInsertTime = interval_ * int( 0.5 + timingEnv.begin()->first / interval_ );
+ double lastInsertTime = (--timingEnv.end())->first + ( 0.5 * interval_ );
+
+ // resample:
+ for ( double insertTime = firstInsertTime;
+ insertTime <= lastInsertTime;
+ insertTime += interval_ )
+ {
+ // sample time is obtained from the timing envelope, if specified,
+ // otherwise same as the insert time:
+ double sampleTime = timingEnv.valueAt( insertTime );
+
+ // make a resampled Breakpoint:
+ Breakpoint newbp = p.parametersAt( sampleTime );
+
+ Partial::iterator ret_pos = newp.insert( insertTime, newbp );
+
+ }
+
+ // remove excess null Breakpoints at the ends of the newly-formed
+ // Partial, no simple way to anticipate these, without evaluating
+ // the timing envelope at all points.
+ //
+ // Also runs of nulls in the middle?
+ Partial::iterator it = newp.begin();
+ while( it != newp.end() && 0 == it->amplitude() )
+ {
+ ++it;
+ }
+ newp.erase( newp.begin(), it );
+
+ it = newp.end();
+ while( it != newp.begin() && 0 == (--it)->amplitude() )
+ {
+ }
+ if ( it != newp.end() )
+ {
+ newp.erase( ++it, newp.end() );
+ }
+
+ // is this a good idea? generally not.
+ if ( phaseCorrect_ && ( 0 != newp.numBreakpoints() ) )
+ {
+ fixFrequency( newp ); // use default maxFixPct
+ }
+
+ // store the new Partial:
+ p = newp;
+
+ debugger << "resampled Partial has " << p.numBreakpoints()
+ << " Breakpoints" << endl;
+}
+
+// ---------------------------------------------------------------------------
+// quantize
+// ---------------------------------------------------------------------------
+//! The Breakpoint times in the resampled Partial will comprise a
+//! sparse sequence of integer multiples of the sampling interval,
+//! beginning with the multiple nearest to the Partial's start time and
+//! ending with the multiple nearest to the Partial's end time, and including
+//! only multiples that are near to Breakpoint times in the original Partial.
+//! Resampling is performed in-place.
+//!
+//! \param p is the Partial to resample
+//
+void Resampler::quantize( Partial & p ) const
+{
+ debugger << "quantizing Partial labeled " << p.label()
+ << " having " << p.numBreakpoints()
+ << " Breakpoints" << endl;
+
+ // for phase-correct quantization, first make the phases correct by
+ // fixing them from the initial phase (ideally this should have
+ // no effect but there's no way to be phase-correct after quantization
+ // unless the phases start correct), then quantize the Breakpoint
+ // times, then afterwards, adjust the frequencies to match
+ // the interpolated phases:
+ if ( phaseCorrect_ )
+ {
+ fixPhaseForward( p.begin(), --p.end() );
+ }
+
+ // create the new Partial:
+ Partial newp;
+ newp.setLabel( p.label() );
+
+ Partial::const_iterator iter = p.begin();
+ while( iter != p.end() )
+ {
+ const Breakpoint & bp = iter.breakpoint();
+ double bpt = iter.time();
+
+ // find the nearest multiple of the quantization interval:
+ long qstep = long( 0.5 + ( bpt / interval_ ) );
+
+ long endstep = qstep-1; // guarantee first insertion
+ if ( newp.numBreakpoints() != 0 )
+ {
+ endstep = long( 0.5 + ( newp.endTime() / interval_ ) );
+ }
+
+ // insert a new Breakpoint if it does not duplicate
+ // a previous insertion, or if it is a Null (needed
+ // for phase-correction):
+ if ( (endstep != qstep) || (0 == bp.amplitude()) )
+ {
+ double qt = interval_ * qstep;
+
+ // insert another Breakpoint and advance the Breakpoint
+ // iterator and the current time:
+ //
+ // sample the Partial with a long fade time so that
+ // the amplitudes at the ends keep their original values:
+ const double a_long_time = 1.;
+ Breakpoint newbp = p.parametersAt( qt, a_long_time );
+ Partial::iterator new_pos = newp.insert( qt, newbp );
+
+ // tricky: if the quantized position (iter) is a null Breakpoint,
+ // we had better made the new position a null also, very important
+ // for making phase resets happen at synthesis time.
+ //
+ // Also, if new_pos is earlier than iter, the phase should be rolled
+ // back from iter, rather than interpolated. If new_pos is later
+ // than iter, then its phase will have been correctly interpolated.
+ if ( 0 == bp.amplitude() )
+ {
+ new_pos.breakpoint().setAmplitude( 0 );
+
+ if ( new_pos.time() < bpt )
+ {
+ double dp = phaseTravel( new_pos.breakpoint(), bp,
+ bpt - new_pos.time() );
+ new_pos.breakpoint().setPhase( bp.phase() - dp );
+ }
+ }
+ }
+ ++iter;
+ }
+
+ // for phase-correct quantization, adjust the frequencies to match
+ // the interpolated phases:
+ if ( phaseCorrect_ )
+ {
+ fixFrequency( newp, 5 );
+ }
+
+
+ debugger << "quantized Partial has " << newp.numBreakpoints()
+ << " Breakpoints" << endl;
+
+ // store the new Partial:
+ p = newp;
+}
+
+// ---------------------------------------------------------------------------
+// insert_resampled_at (helper)
+// ---------------------------------------------------------------------------
+//
+static Partial::iterator
+insert_resampled_at( Partial & newp, const Partial & p,
+ double sampleTime, double insertTime )
+{
+ // make a resampled Breakpoint:
+ Breakpoint newbp = p.parametersAt( sampleTime );
+
+ // handle end points to reduce error at ends
+ if ( sampleTime < p.startTime() )
+ {
+ newbp.setAmplitude( p.first().amplitude() );
+ }
+ else if ( sampleTime > p.endTime() )
+ {
+ newbp.setAmplitude( p.last().amplitude() );
+ }
+
+
+ Partial::iterator ret_pos = newp.insert( insertTime, newbp );
+
+ debugger << "inserted Breakpoint having amplitude " << newbp.amplitude()
+ << " at time " << insertTime << endl;
+
+ return ret_pos;
+}
+
+} // end of namespace Loris
+