summaryrefslogtreecommitdiff
path: root/src/loris/Synthesizer.C
diff options
context:
space:
mode:
Diffstat (limited to 'src/loris/Synthesizer.C')
-rw-r--r--src/loris/Synthesizer.C518
1 files changed, 518 insertions, 0 deletions
diff --git a/src/loris/Synthesizer.C b/src/loris/Synthesizer.C
new file mode 100644
index 0000000..db102e4
--- /dev/null
+++ b/src/loris/Synthesizer.C
@@ -0,0 +1,518 @@
+/*
+ * 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
+ *
+ *
+ * Synthesizer.C
+ *
+ * Implementation of class Loris::SynthesizerSynthesizer, a synthesizer of
+ * bandwidth-enhanced Partials.
+ *
+ * Kelly Fitz, 16 Aug 1999
+ * loris@cerlsoundgroup.org
+ *
+ * http://www.cerlsoundgroup.org/Loris/
+ *
+ */
+
+#if HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "Synthesizer.h"
+#include "Oscillator.h"
+#include "Breakpoint.h"
+#include "BreakpointUtils.h"
+#include "Envelope.h"
+#include "LorisExceptions.h"
+#include "Notifier.h"
+#include "Partial.h"
+#include "Resampler.h"
+#include "phasefix.h"
+
+#include <algorithm>
+#include <cmath>
+
+#if defined(HAVE_M_PI) && (HAVE_M_PI)
+ const double Pi = M_PI;
+#else
+ const double Pi = 3.14159265358979324;
+#endif
+
+// begin namespace
+namespace Loris {
+
+
+// ---------------------------------------------------------------------------
+// Synthesizer constructor
+// ---------------------------------------------------------------------------
+//! Construct a Synthesizer using the default parameters and sample
+//! buffer (a standard library vector). Since Partials generated by the
+//! Loris Analyzer generally begin and end at non-zero amplitude, zero-amplitude
+//! Breakpoints are inserted at either end of the Partial, at a temporal
+//! distance equal to the fade time, to reduce turn-on and turn-off
+//! artifacts.
+//!
+//! \sa Synthesizer::Parameters
+//!
+//! \param buffer The vector (of doubles) into which rendered samples
+//! should be accumulated.
+//! \throw InvalidArgument if any of the parameters is invalid.
+Synthesizer::Synthesizer( std::vector<double> & buffer ) :
+ m_sampleBuffer( & buffer ),
+ m_fadeTimeSec( DefaultParameters().fadeTime ),
+ m_srateHz( DefaultParameters().sampleRate )
+{
+}
+
+
+// ---------------------------------------------------------------------------
+// Synthesizer constructor
+// ---------------------------------------------------------------------------
+//! Construct a Synthesizer using the specified parameters and sample
+//! buffer (a standard library vector). Since Partials generated by the
+//! Loris Analyzer generally begin and end at non-zero amplitude, zero-amplitude
+//! Breakpoints are inserted at either end of the Partial, at a temporal
+//! distance equal to the fade time, to reduce turn-on and turn-off
+//! artifacts. If the fade time is unspecified, the default value of one
+//! millisecond (0.001 seconds) is used.
+//!
+//! \param params A Parameters struct storing the configuration of
+//! Synthesizer parameters.
+//! \param buffer The vector (of doubles) into which rendered samples
+//! should be accumulated.
+//! \throw InvalidArgument if any of the parameters is invalid.
+//
+Synthesizer::Synthesizer( Parameters params, std::vector<double> & buffer ) :
+ m_sampleBuffer( & buffer )
+{
+ // make sure that the parameters are valid before proceeding
+ if ( IsValidParameters( params ) )
+ {
+ m_fadeTimeSec = params.fadeTime;
+ m_srateHz = params.sampleRate;
+ m_osc.filter() = params.filter;
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Synthesizer constructor
+// ---------------------------------------------------------------------------
+//! Construct a Synthesizer using the specified sampling rate, sample
+//! buffer (a standard library vector), and the default fade time
+//! stored in the DefaultParameters. Since Partials generated by the Loris
+//! Analyzer generally begin and end at non-zero amplitude, zero-amplitude
+//! Breakpoints are inserted at either end of the Partial, at a temporal
+//! distance equal to the fade time, to reduce turn-on and turn-off
+//! artifacts.
+//!
+//! \param srate The rate (Hz) at which to synthesize samples
+//! (must be positive).
+//! \param buffer The vector (of doubles) into which rendered samples
+//! should be accumulated.
+//! \throw InvalidArgument if the specfied sample rate is non-positive.
+Synthesizer::Synthesizer( double samplerate, std::vector<double> & buffer ) :
+ m_sampleBuffer( & buffer ),
+ m_fadeTimeSec( DefaultParameters().fadeTime ),
+ m_srateHz( samplerate )
+{
+ // check to make sure that the sample rate is valid:
+ if ( m_srateHz <= 0. )
+ {
+ Throw( InvalidArgument, "Synthesizer sample rate must be positive." );
+ }
+
+ // assign the default bw enhancement filter to the Oscillator
+ m_osc.filter() = DefaultParameters().filter;
+
+}
+
+// ---------------------------------------------------------------------------
+// Synthesizer constructor
+// ---------------------------------------------------------------------------
+//! Construct a Synthesizer using the specified sampling rate, sample
+//! buffer (a standard library vector), and Partial
+//! fade time (in seconds). Since Partials generated by the Loris Analyzer
+//! generally begin and end at non-zero amplitude, zero-amplitude
+//! Breakpoints are inserted at either end of the Partial, at a temporal
+//! distance equal to the fade time, to reduce turn-on and turn-off
+//! artifacts. If the fade time is unspecified, the default value of one
+//! millisecond (0.001 seconds) is used.
+//!
+//! \param samplerate The rate (Hz) at which to synthesize samples
+//! (must be positive).
+//! \param buffer The vector (of doubles) into which rendered samples
+//! should be accumulated.
+//! \param fade The Partial fade time in seconds (must be non-negative).
+//! \throw InvalidArgument if the specfied sample rate is non-positive.
+//! \throw InvalidArgument if the specified fade time is negative.
+Synthesizer::Synthesizer( double samplerate, std::vector<double> & buffer,
+ double fade ) :
+ m_sampleBuffer( & buffer ),
+ m_fadeTimeSec( fade ),
+ m_srateHz( samplerate )
+{
+ // check to make sure that the sample rate is valid:
+ if ( m_srateHz <= 0. )
+ {
+ Throw( InvalidArgument, "Synthesizer sample rate must be positive." );
+ }
+
+ // check to make sure that the specified fade time
+ // is valid:
+ if ( m_fadeTimeSec < 0. )
+ {
+ Throw( InvalidArgument,
+ "Synthesizer Partial fade time must be non-negative." );
+ }
+
+ // assign the default bw enhancement filter to the Oscillator
+ m_osc.filter() = DefaultParameters().filter;
+
+}
+
+// -- synthesis --
+
+// ---------------------------------------------------------------------------
+// synthesize
+// ---------------------------------------------------------------------------
+//! Synthesize a bandwidth-enhanced sinusoidal Partial. Zero-amplitude
+//! Breakpoints are inserted at either end of the Partial to reduce
+//! turn-on and turn-off artifacts, as described above. The synthesizer
+//! will resize the buffer as necessary to accommodate all the samples,
+//! including the fade out. Previous contents of the buffer are not
+//! overwritten. Partials with start times earlier than the Partial fade
+//! time will have shorter onset fades. Partials are not rendered at
+//! frequencies above the half-sample rate.
+//!
+//! \param p The Partial to synthesize.
+//! \return Nothing.
+//! \pre The partial must have non-negative start time.
+//! \post This Synthesizer's sample buffer (vector) has been
+//! resized to accommodate the entire duration of the
+//! Partial, p, including fade out at the end.
+//! \throw InvalidPartial if the Partial has negative start time.
+//
+void
+Synthesizer::synthesize( Partial p )
+{
+ if ( p.numBreakpoints() == 0 )
+ {
+ debugger << "Synthesizer ignoring a partial that contains no Breakpoints" << endl;
+ return;
+ }
+
+ if ( p.startTime() < 0 )
+ {
+ Throw( InvalidPartial, "Tried to synthesize a Partial having start time less than 0." );
+ }
+
+ debugger << "synthesizing Partial from " << p.startTime() * m_srateHz
+ << " to " << p.endTime() * m_srateHz << " starting phase "
+ << p.initialPhase() << " starting frequency "
+ << p.first().frequency() << endl;
+
+ // better to compute this only once:
+ const double OneOverSrate = 1. / m_srateHz;
+
+
+ // use a Resampler to quantize the Breakpoint times and
+ // correct the phases:
+ Resampler quantizer( OneOverSrate );
+ quantizer.setPhaseCorrect( true );
+ quantizer.quantize( p );
+
+
+ // resize the sample buffer if necessary:
+ typedef unsigned long index_type;
+ index_type endSamp = index_type( ( p.endTime() + m_fadeTimeSec ) * m_srateHz );
+ if ( endSamp+1 > m_sampleBuffer->size() )
+ {
+ // pad by one sample:
+ m_sampleBuffer->resize( endSamp+1 );
+ }
+
+ // compute the starting time for synthesis of this Partial,
+ // m_fadeTimeSec before the Partial's startTime, but not before 0:
+ double itime = ( m_fadeTimeSec < p.startTime() ) ? ( p.startTime() - m_fadeTimeSec ) : 0.;
+ index_type currentSamp = index_type( (itime * m_srateHz) + 0.5 ); // cheap rounding
+
+ // reset the oscillator:
+ // all that really needs to happen here is setting the frequency
+ // correctly, the phase will be reset again in the loop over
+ // Breakpoints below, and the amp and bw can start at 0.
+ m_osc.resetEnvelopes( BreakpointUtils::makeNullBefore( p.first(), p.startTime() - itime ), m_srateHz );
+
+ // cache the previous frequency (in Hz) so that it
+ // can be used to reset the phase when necessary
+ // in the sample computation loop below (this saves
+ // having to recompute from the oscillator's radian
+ // frequency):
+ double prevFrequency = p.first().frequency();
+
+ // synthesize linear-frequency segments until
+ // there aren't any more Breakpoints to make segments:
+ double * bufferBegin = &( m_sampleBuffer->front() );
+ for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it )
+ {
+ index_type tgtSamp = index_type( (it.time() * m_srateHz) + 0.5 ); // cheap rounding
+ Assert( tgtSamp >= currentSamp );
+
+ // if the current oscillator amplitude is
+ // zero, and the target Breakpoint amplitude
+ // is not, reset the oscillator phase so that
+ // it matches exactly the target Breakpoint
+ // phase at tgtSamp:
+ if ( m_osc.amplitude() == 0. )
+ {
+ // recompute the phase so that it is correct
+ // at the target Breakpoint (need to do this
+ // because the null Breakpoint phase was computed
+ // from an interval in seconds, not samples, so
+ // it might be inaccurate):
+ //
+ // double favg = 0.5 * ( prevFrequency + it.breakpoint().frequency() );
+ // double dphase = 2 * Pi * favg * ( tgtSamp - currentSamp ) / m_srateHz;
+ //
+ double dphase = Pi * ( prevFrequency + it.breakpoint().frequency() )
+ * ( tgtSamp - currentSamp ) * OneOverSrate;
+ m_osc.setPhase( it.breakpoint().phase() - dphase );
+ }
+
+ m_osc.oscillate( bufferBegin + currentSamp, bufferBegin + tgtSamp,
+ it.breakpoint(), m_srateHz );
+
+ currentSamp = tgtSamp;
+
+ // remember the frequency, may need it to reset the
+ // phase if a Null Breakpoint is encountered:
+ prevFrequency = it.breakpoint().frequency();
+ }
+
+ // render a fade out segment:
+ m_osc.oscillate( bufferBegin + currentSamp, bufferBegin + endSamp,
+ BreakpointUtils::makeNullAfter( p.last(), m_fadeTimeSec ), m_srateHz );
+
+}
+
+// -- sample access --
+
+// ---------------------------------------------------------------------------
+// samples (const)
+// ---------------------------------------------------------------------------
+//! Return a const reference to the sample buffer used (not
+//! owned) by this Synthesizer.
+const std::vector<double> &
+Synthesizer::samples( void ) const
+{
+ return *m_sampleBuffer;
+}
+
+// ---------------------------------------------------------------------------
+// samples (non-const)
+// ---------------------------------------------------------------------------
+//! Return a reference to the sample buffer used (not
+//! owned) by this Synthesizer.
+std::vector<double> &
+Synthesizer::samples( void )
+{
+ return *m_sampleBuffer;
+}
+
+// -- parameter access and mutation --
+
+// ---------------------------------------------------------------------------
+// fadeTime
+// ---------------------------------------------------------------------------
+//! Return this Synthesizer's Partial fade time, in seconds.
+double
+Synthesizer::fadeTime( void ) const
+{
+ return m_fadeTimeSec;
+}
+
+// ---------------------------------------------------------------------------
+// sampleRate
+// ---------------------------------------------------------------------------
+//! Return the sampling rate (in Hz) for this Synthesizer.
+double
+Synthesizer::sampleRate( void ) const
+{
+ return m_srateHz;
+}
+
+
+// ---------------------------------------------------------------------------
+// setFadeTime
+// ---------------------------------------------------------------------------
+//! Set this Synthesizer's fade time to the specified value
+//! (in seconds, must be non-negative).
+//!
+//! \param t The new Partial fade time.
+//! \throw InvalidArgument if the specified fade time is negative.
+void
+Synthesizer::setFadeTime( double partialFadeTime )
+{
+ // check to make sure that the specified fade time
+ // is valid:
+ if ( partialFadeTime < 0. )
+ {
+ Throw( InvalidArgument, "Synthesizer Partial fade time must be non-negative." );
+ }
+
+ m_fadeTimeSec = partialFadeTime;
+}
+
+// ---------------------------------------------------------------------------
+// setSampleRate
+// ---------------------------------------------------------------------------
+//! Set this Synthesizer's sample rate to the specified value
+//! (in Hz, must be positive).
+//!
+//! \param rate The new synthesis sample rate.
+//! \throw InvalidArgument if the specified rate is nonpositive.
+void
+Synthesizer::setSampleRate( double rate )
+{
+ // check to make sure that the specified rate
+ // is valid:
+ if ( rate <= 0. )
+ {
+ Throw( InvalidArgument, "Synthesizer sample rate must be positive." );
+ }
+
+ m_srateHz = rate;
+}
+
+// ---------------------------------------------------------------------------
+// filter
+// ---------------------------------------------------------------------------
+//! Return access to the Filter used by this Synthesizer's
+//! Oscillator to implement bandwidth-enhanced sinusoidal
+//! synthesis. (Can use this access to make changes to the
+//! filter coefficients.)
+Filter &
+Synthesizer::filter( void )
+{
+ return m_osc.filter();
+}
+
+// -- parameters structure --
+
+// ---------------------------------------------------------------------------
+// Synthesizer::Parameters default constructor
+// ---------------------------------------------------------------------------
+//! Assign default initial values to the Synthesizer parameters, Filter
+//! defaults are defined in Oscillator.C.
+
+static const double Default_FadeTime_Ms = 1;
+static const double Default_SampleRate_Hz = 44100;
+//static const Synthesizer::EnhancementFlag Default_Enhancement_Flag = Synthesizer::BwEnhanced;
+
+
+Synthesizer::Parameters::Parameters( void ) :
+ fadeTime( Default_FadeTime_Ms * 0.001 ),
+ sampleRate( Default_SampleRate_Hz ),
+ // enhancement( Default_Enhancement_Flag ),
+ filter( Oscillator::prototype_filter() )
+{
+}
+
+// ---------------------------------------------------------------------------
+// Synthesizer default Parameters local access only
+// ---------------------------------------------------------------------------
+
+static
+Synthesizer::Parameters & TheSynthesizerDefaultParameters( void )
+{
+ static Synthesizer::Parameters params;
+ return params;
+
+}
+
+// ---------------------------------------------------------------------------
+// Synthesizer default Parameters access (static)
+// ---------------------------------------------------------------------------
+//! Default configuration of a Loris::Synthesizer. Modify the values
+//! in this structure to alter the configuration of all Synthesizers,
+//! including those used by the AiffFile class to render Partials.
+const Synthesizer::Parameters &
+Synthesizer::DefaultParameters( void )
+{
+ return TheSynthesizerDefaultParameters();
+}
+
+// ---------------------------------------------------------------------------
+// Synthesizer default Parameters access (static)
+// ---------------------------------------------------------------------------
+//! Assign a new default configuration of a Loris::Synthesizer
+//! to alter the configuration of all Synthesizers,
+//! including those used by the AiffFile class to render Partials.
+//!
+//! \param params A Parameters struct describing the new default
+//! configuration for Synthesizers.
+//! \throw InvalidArgument if any of the parameters is invalid.
+void Synthesizer::SetDefaultParameters( const Synthesizer::Parameters & params )
+{
+ if ( IsValidParameters( params ) )
+ {
+ TheSynthesizerDefaultParameters() = params;
+ }
+}
+
+// ---------------------------------------------------------------------------
+// IsValidParameters (static)
+// ---------------------------------------------------------------------------
+//! Check the validty of a Parameters structure. Returns true if the
+//! struct represents a valid Synthesizer configuration, and false otherwise.
+//!
+//! \param params A Parameters struct describing a configuration for
+//! Synthesizers.
+bool Synthesizer::IsValidParameters( const Parameters & params )
+{
+ // check to make sure that the sample rate is valid:
+ if ( 0. >= params.sampleRate )
+ {
+ Throw( InvalidArgument, "Synthesizer sample rate must be positive." );
+ }
+
+ // check to make sure that the specified fade time
+ // is valid:
+ if ( 0. > params.fadeTime )
+ {
+ Throw( InvalidArgument,
+ "Synthesizer Partial fade time must be non-negative." );
+ }
+
+ // check that the filter coefficients are valid -- pretty much
+ // any coefficients are valid as long as the zeroeth feedback
+ // coefficient is non-zero:
+ if ( 0. == params.filter.denominator()[0] )
+ {
+ Throw( InvalidArgument,
+ "Synthesizer filter zeroeth feedback coefficient must be non-zero." );
+ }
+
+ // if no exception has been raised, return true indicating valid params
+ return true;
+
+}
+
+} // end of namespace Loris