diff options
Diffstat (limited to 'src/loris/Resampler.C')
-rw-r--r-- | src/loris/Resampler.C | 398 |
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 + |