/*
 * 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