// ---------------------------------------------------------------------------
//	phaseTravel
//
//	Compute the sinusoidal phase travel between two Breakpoints.
//	Return the total unwrapped phase travel.
//
double phaseTravel( const Breakpoint & bp0, const Breakpoint & bp1, 
					double dt )
{
	double f0 = bp0.frequency();
	double f1 = bp1.frequency();
	double favg = .5 * ( f0 + f1 );
	return 2 * Pi * favg * dt;
}
// ---------------------------------------------------------------------------
//	resetEnvelopes
// ---------------------------------------------------------------------------
//	Reset the instantaneous envelope parameters 
// 	(frequency, amplitude, bandwidth, and phase).
// 	The sample rate is needed to convert the 
// 	Breakpoint frequency (Hz) to radians per sample.
//
void 
Oscillator::resetEnvelopes( const Breakpoint & bp, double srate )
{
	//	Remember that the oscillator only knows about 
	//	radian frequency! Convert!
	i_frequency = bp.frequency() * TwoPi / srate;
	i_amplitude = bp.amplitude();
	i_bandwidth = bp.bandwidth();
	determ_phase = bp.phase();
	
 	//	clamp bandwidth:
	if ( i_bandwidth > 1. )
	{
		debugger << "clamping bandwidth at 1." << endl;
		i_bandwidth = 1.;
	}
	else if ( i_bandwidth < 0. )
	{ 
		debugger << "clamping bandwidth at 0." << endl;
		i_bandwidth = 0.;
	}

	//	don't alias:
	if ( i_frequency > Pi )
	{ 
		debugger << "fading out aliasing Partial" << endl;
		i_amplitude = 0.;
	}
}
// ---------------------------------------------------------------------------
//  resetEnvelopes
// ---------------------------------------------------------------------------
//  Reset the instantaneous envelope parameters
//  (frequency, amplitude, bandwidth, and phase).
//  The Breakpoint frequency (Hz) is in radians per sample.
void
RealtimeOscillator::restoreEnvelopes( const Breakpoint & bp) noexcept
{
    //  Remember that the oscillator only knows about
    //  radian frequency! Convert!
    m_instfrequency = bp.frequency();
    m_instamplitude = bp.amplitude();
    m_instbandwidth = bp.bandwidth();
    m_determphase = bp.phase();

    //  clamp bandwidth:
    if ( m_instbandwidth > 1. )
    {
        m_instbandwidth = 1.;
    }
    else if ( m_instbandwidth < 0. )
    {
        m_instbandwidth = 0.;
    }
    //  don't alias:
    if ( m_instfrequency > Pi )
    {
        m_instamplitude = 0.;
    }

    //  Reset the fitler state too.
    m_filter.clear();
}
// ---------------------------------------------------------------------------
//      accum_samples
// ---------------------------------------------------------------------------
//      helper
//
static void accum_samples( CSOUND * csound,
                           Oscillator & oscil, Breakpoint & bp,
                           double * bufbegin )
{
  if( bp.amplitude() > 0 || oscil.amplitude() > 0 )
    {
      double radfreq = radianFreq( csound, bp.frequency() );
      double amp = bp.amplitude();
      double bw = bp.bandwidth();

      //        initialize the oscillator if it is changing from zero
      //        to non-zero amplitude in this  control block:
      if ( oscil.amplitude() == 0. )
        {
          //    don't initialize with bogus values, Oscillator
          //    only guards against out-of-range target values
          //    in generateSamples(), parameter mutators are
          //    dangerous:

          if ( radfreq > PI )   //      don't alias
            amp = 0.;

          if ( bw > 1. )        //      clamp bandwidth
            bw = 1.;
          else if ( bw < 0. )
            bw = 0.;

#ifdef DEBUG_LORISGENS
        /*
          std::cerr << "initializing oscillator " << std::endl;
          std::cerr << "parameters: " << bp.frequency() << "  ";
          std::cerr << amp << "  " << bw << std::endl;
        */
#endif

          //    initialize frequency, amplitude, and bandwidth to
          //    their target values:
          /*
            oscil.setRadianFreq( radfreq );
            oscil.setAmplitude( amp );
            oscil.setBandwidth( bw );
          */
          oscil.resetEnvelopes( bp, (double) csound->esr );

          //    roll back the phase:
          oscil.resetPhase( bp.phase() - ( radfreq * (double) csound->ksmps ) );
        }

      //        accumulate samples into buffer:
      // oscil.generateSamples( bufbegin, bufbegin + nsamps, radfreq, amp, bw );
      oscil.oscillate( bufbegin, bufbegin + csound->ksmps,
                       bp, (double) csound->esr );
    }
}
// ---------------------------------------------------------------------------
//	generateSamples
// ---------------------------------------------------------------------------
//	Accumulate bandwidth-enhanced sinusoidal samples modulating the 
//	oscillator state from its current values of radian frequency,
//	amplitude, and bandwidth to the specified target values, into
//	the specified half-open range of doubles.
//
//	The caller must ensure that the range is valid. Target parameters
//	are bounds-checked. 
//
void
Oscillator::oscillate( double * begin, double * end,
					   const Breakpoint & bp, double srate )
{
	double targetFreq = bp.frequency() * TwoPi / srate, 
		   targetAmp = bp.amplitude(), 
		   targetBw = bp.bandwidth();
	
	//	clamp bandwidth:
	if ( targetBw > 1. )
	{
		debugger << "clamping bandwidth at 1." << endl;
		targetBw = 1.;
	}
	else if ( targetBw < 0. )
	{ 
		debugger << "clamping bandwidth at 0." << endl;
		targetBw = 0.;
	}
		
	//	don't alias:
	if ( targetFreq > Pi )	//	radian Nyquist rate
	{
		debugger << "fading out Partial above Nyquist rate" << endl;
		targetAmp = 0.;
	}

	//	compute trajectories:
	const double dTime = 1. / (end - begin);
	const double dFreq = (targetFreq - i_frequency) * dTime;
	const double dAmp = (targetAmp - i_amplitude)  * dTime;
	const double dBw = (targetBw - i_bandwidth)  * dTime;

	//	could use temporary local variables for speed... nah!
	//	Cannot possibly be worth it when I am computing square roots 
	//	and cosines!
	double am;
	for ( double * putItHere = begin; putItHere != end; ++putItHere )
	{
		//	use math functions in namespace std:
		using namespace std;

		//	compute amplitude modulation due to bandwidth:
		//
		//	This will give the right amplitude modulation when scaled
		//	by the Partial amplitude:
		//
		//	carrier amp: sqrt( 1. - bandwidth ) * amp
		//	modulation index: sqrt( 2. * bandwidth ) * amp
		//
		am = sqrt( 1. - i_bandwidth ) + ( bwModulator() * sqrt( 2. * i_bandwidth ) );	
				
		//	compute a sample and add it into the buffer:
		*putItHere += am * i_amplitude * cos( determ_phase );
			
		//	update the instantaneous oscillator state:
		determ_phase += i_frequency;	//	frequency is radians per sample
		i_frequency += dFreq;
		i_amplitude += dAmp;
		i_bandwidth += dBw;
		if (i_bandwidth < 0.)
			i_bandwidth = 0.;
			
	}	// end of sample computation loop
	
	//	wrap phase to prevent eventual loss of precision at
	//	high oscillation frequencies:
	//	(Doesn't really matter much exactly how we wrap it, 
	//	as long as it brings the phase nearer to zero.)
	determ_phase = m2pi( determ_phase );
	
	//	set the state variables to their target values,
	//	just in case they didn't arrive exactly (overshooting
	//	amplitude or, especially, bandwidth, could be bad, and
	//	it does happen):
	i_frequency = targetFreq;
	i_amplitude = targetAmp;
	i_bandwidth = targetBw;
}
// ---------------------------------------------------------------------------
//	matchPhaseFwd
//
//!	Compute the target frequency that will affect the
//!	predicted (by the Breakpoint phases) amount of
//!	sinusoidal phase travel between two breakpoints, 
//!	and assign that frequency to the target Breakpoint.
//!	After computing the new frequency, update the phase of
//!	the later Breakpoint.
//!
//! If the earlier Breakpoint is null and the later one
//! is non-null, then update the phase of the earlier
//! Breakpoint, and do not modify its frequency or the 
//! later Breakpoint.
//!
//! The most common kinds of errors are local (or burst) errors in 
//! frequency and phase. These errors are best corrected by correcting
//! less than half the detected error at any time. Correcting more
//! than that will produce frequency oscillations for the remainder of
//! the Partial, in the case of a single bad frequency (as is common
//! at the onset of a tone). Any damping factor less then one will 
//! converge eventually, .5 or less will converge without oscillating.
//! Use the damping argument to control the damping of the correction.
//!	Specify 1 for no damping.
//!
//! \pre		The two Breakpoints are assumed to be consecutive in
//!				a Partial.
//! \param		bp0	The earlier Breakpoint.
//! \param		bp1	The later Breakpoint.
//! \param		dt The time (in seconds) between bp0 and bp1.
//! \param		damping The fraction of the amount of phase error that will
//!				be corrected (.5 or less will prevent frequency oscilation 
//!				due to burst errors in phase). 
//! \param		maxFixPct The maximum amount of frequency adjustment
//!				that can be made to the frequency of bp1, expressed
//!				as a precentage of the unmodified frequency of bp1.
//!				If the necessary amount of frequency adjustment exceeds
//!				this amount, then the phase will not be matched, 
//!				but will be updated as well to be consistent with
//!				the frequencies. (default is 0.2%)
//
void matchPhaseFwd( Breakpoint & bp0, Breakpoint & bp1,
				    double dt, double damping, double maxFixPct )
{
	double travel = phaseTravel( bp0, bp1, dt );
    
    if ( ! BreakpointUtils::isNonNull( bp1 ) )
    {
        // if bp1 is null, just compute a new phase,
        // no need to match it.
        bp1.setPhase( wrapPi( bp0.phase() + travel ) );
    }
    else if ( ! BreakpointUtils::isNonNull( bp0 ) )
    {
        // if bp0 is null, and bp1 is not, then bp0
        // should be a phase reset Breakpoint during
        // rendering, so compute a new phase for
        // bp0 that achieves bp1's phase.
        bp0.setPhase( wrapPi( bp1.phase() - travel ) ) ;
    } 
    else
    {
        // invariant:
        // neither bp0 nor bp1 is null
        //
        // modify frequecies to match phases as nearly as possible
        double err = wrapPi( bp1.phase() - ( bp0.phase() + travel ) );
        
        //  The most common kinds of errors are local (or burst) errors in 
        //  frequency and phase. These errors are best corrected by correcting
        //  less than half the detected error at any time. Correcting more
        //  than that will produce frequency oscillations for the remainder of
        //  the Partial, in the case of a single bad frequency (as is common
        //  at the onset of a tone). Any damping factor less then one will 
        //  converge eventually, .5 or less will converge without oscillating.
        //  #define DAMPING .5
        travel += damping * err;
        
        double f0 = bp0.frequency();
        double ftgt = ( travel / ( Pi * dt ) ) - f0;
        
        #ifdef Loris_Debug
        debugger << "matchPhaseFwd: correcting " << bp1.frequency() << " to " << ftgt 
                 << " (phase " << wrapPi( bp1.phase() ) << "), ";
        #endif
        
        //	If the target is not a null breakpoint, may need to 
        //	clamp the amount of frequency modification.
        //
        //  Actually, should probably always clamp the amount
        //  of modulation, should never have arbitrarily large
        //  frequency adjustments. 
        //
        //  Really, should never call this function if bp1
        //  is a null Breakpoint, because we don't care about 
        //  those phases in Loris. 
        if ( true ) //  bp1.amplitude() != 0. )
        {	
            if ( ftgt > bp1.frequency() * ( 1 + (maxFixPct*.01) ) )
            {
                ftgt = bp1.frequency() * ( 1 + (maxFixPct*.01) );
            }
            else if ( ftgt < bp1.frequency() * ( 1 - (maxFixPct*.01) ) )
            {
                ftgt = bp1.frequency() * ( 1 - (maxFixPct*.01) );
            }
        }
        bp1.setFrequency( ftgt );
        
        //	Recompute the phase according to the new frequency.
        double phi = wrapPi( bp0.phase() + phaseTravel( bp0, bp1, dt ) );
        bp1.setPhase( phi );

        #ifdef Loris_Debug
        debugger << "achieved " << ftgt << " (phase " << phi << ")" << endl;
        #endif
    }
}
// ---------------------------------------------------------------------------
//  oscillate
// ---------------------------------------------------------------------------
//  Accumulate bandwidth-enhanced sinusoidal samples modulating the
//  oscillator state from its current values of radian frequency,
//  amplitude, and bandwidth to the specified target values, into
//  the specified half-open range of floats. SSE2 instructions are used.
//
//  The caller must ensure that the range is valid. Target parameters
//  are bounds-checked.
//
void
RealtimeOscillator::oscillate( float * begin, float * end,
                               const Breakpoint & bp, double srate, int dSample) noexcept
{
    double targetFreq = m_frequencyScaling * bp.frequency() * TwoPi / srate;     //  radians per sample
    double targetAmp = bp.amplitude();
    double targetBw = bp.bandwidth();

    //  clamp bandwidth:
    if ( targetBw > 1. )
    {
        targetBw = 1.;
    }
    else if ( targetBw < 0. )
    {
        targetBw = 0.;
    }

    //  don't alias:
    if ( targetFreq > Pi )  //  radian Nyquist rate
    {
        targetAmp = 0.;
    }
    //  compute trajectories:
    const double dTime = 1. / dSample; //(end - begin);
    const double dFreqOver2 = 0.5 * (targetFreq - m_instfrequency) * dTime;
    //	split frequency update in two steps, update phase using average
    //	frequency, after adding only half the frequency step

    const double dAmp = (targetAmp - m_instamplitude)  * dTime;
//        const double dBw = (targetBw - m_instbandwidth)  * dTime;
    //  Use temporary local variables for speed.
    //  Probably not worth it when I am computing square roots
    //  and cosines...
    double ph = m_determphase;
//        double f = m_instfrequency;
//        double a = m_instamplitude;
//        double bw = m_instbandwidth;

    // Ignore bandwidth
    //	Also use a more efficient sample loop when the bandwidth is zero.
//        if (false 0 < bw || 0 < dBw )
//        {
//            double am, nz;
//            for ( double * putItHere = begin; putItHere != end; ++putItHere )
//            {
//                //  use math functions in namespace std:
//                using namespace std;
//
//                //  compute amplitude modulation due to bandwidth:
//                //
//                //  This will give the right amplitude modulation when scaled
//                //  by the Partial amplitude:
//                //
//                //  carrier amp: sqrt( 1. - bandwidth ) * amp
//                //  modulation index: sqrt( 2. * bandwidth ) * amp
//                //
//                nz = m_filter.apply( m_modulator.sample() );
//                am = sqrt( 1. - bw ) + ( nz * sqrt( 2. * bw ) );
//
//                //  compute a sample and add it into the buffer:
//                *putItHere += am * a * cos( ph );
//
//                //  update the instantaneous oscillator state:
//                f += dFreqOver2;
//                ph += f;   //  frequency is radians per sample
//                f += dFreqOver2;
//                a += dAmp;
//                bw += dBw;
//                if (bw < 0.)
//                {
//                    bw = 0.;
//                }
//            }   // end of sample computation loop
//        }
//        else
    {
//          Vectorized loop

        double a4[4] = { m_instamplitude };
        double f4[4] = { m_instfrequency };
        V4SF ph4 = { (float) m_determphase };

        for (int i = 1; i < 4; i++)
        {
            f4[i] = f4[i - 1] + dFreqOver2;
            ph4.f[i] = ph4.f[i - 1] + f4[i];
            f4[i] = f4[i] + dFreqOver2;
            a4[i] = a4[i - 1] + dAmp;
        }

        V4SF cosVal;
        float * putItHere = begin;
        for ( ; putItHere + 4  < end; putItHere += 4 )
        {
            cosVal.v = cos_ps(ph4.v);
            for (int i = 0; i < 4; i++)
            {
                putItHere[i] += (a4[i] == 0 ? 0 : a4[i] * cosVal.f[i]);
            }

            f4[0] = f4[3] + dFreqOver2;
            ph4.f[0] = ph4.f[3] + f4[0];
            f4[0] = f4[0] + dFreqOver2;
            a4[0] = a4[3] + dAmp;
            for (int i = 1; i < 4; i++)
            {
                f4[i] = f4[i - 1] + dFreqOver2;
                ph4.f[i] = ph4.f[i - 1] + f4[i];
                f4[i] = f4[i] + dFreqOver2;
                a4[i] = a4[i - 1] + dAmp;
            }
        }   // end of sample computation loop

        for ( ; putItHere != end; ++putItHere )
        {
            //  use math functions in namespace std:
            using namespace std;

            //	no modulation when there is no bandwidth

            //  compute a sample and add it into the buffer:
            *putItHere += (a4[0] == 0 ? 0 : a4[0] * cos(ph4.f[0]));


            //  update the instantaneous oscillator state:
            f4[0] += dFreqOver2;
            ph4.f[0] += f4[0];   //  frequency is radians per sample
            f4[0] += dFreqOver2;
            a4[0] += dAmp;

        }   // end of

        ph = ph4.f[0];
        targetAmp = a4[0];
        targetFreq = f4[0];

    }

    //  wrap phase to prevent eventual loss of precision at
    //  high oscillation frequencies:
    //  (Doesn't really matter much exactly how we wrap it,
    //  as long as it brings the phase nearer to zero.)
    m_determphase = m2pi( ph );

    //  set the state variables to their target values,
    //  just in case they didn't arrive exactly (overshooting
    //  amplitude or, especially, bandwidth, could be bad, and
    //  it does happen):
    m_instfrequency = targetFreq;
    m_instamplitude = targetAmp;
    m_instbandwidth = targetBw;
}
Exemple #8
0
// ---------------------------------------------------------------------------
//  synthesize
// ---------------------------------------------------------------------------
//! Synthesize a bandwidth-enhanced sinusoidal Partial.
//!
//! \param  buffer  The samples buffer.
//! \param  samples Number of samples to be synthesized.
//! \param  p       The Partial to synthesize.
//! \return Nothing.
//! \pre    The buffer has to have capacity to contain all samples.
//! \post   This RealTimeSynthesizer's sample buffer (vector) contain synthesised
//!         partials and storeed inner state of synthesiser.
//!
void RealTimeSynthesizer::synthesize( PartialStruct &p, float * buffer, const int samples) noexcept
{
    if (p.state.lastBreakpointIdx == PartialStruct::NoBreakpointProcessed)
        m_osc.resetEnvelopes( p.state.envelope, m_srateHz );
    else
        m_osc.restoreEnvelopes( p.state.envelope );
        
    int sampleCounter = 0;
	int sampleDiff = 0;
    int i;
    for (i = p.state.lastBreakpointIdx + 1;  i < p.numBreakpoints; ++i )
    {
        index_type tgtSamp = index_type( (p.breakpoints[i].first * m_srateHz) + 0.5 );   //  cheap rounding
        
        sampleCounter += sampleDiff = tgtSamp - p.state.currentSamp;
        
        if (sampleCounter > samples)// if this breakpoint is longer...
        {
            // cropp it...
            sampleDiff -= (sampleCounter - samples); // substract the overflowing number of samples to get max possible sample diff
            sampleCounter = samples; // we can process max "samples" count
        }
        
        Breakpoint *bp = &(p.breakpoints[i].second);
        //  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. && p.state.breakpointFinished )
        if ( i == PartialStruct::NoBreakpointProcessed + 1 && p.state.breakpointFinished )
        {
            //  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 * ( p.state.prevFrequency + m_osc.frequencyScaling() * bp->frequency() ) * ( tgtSamp - p.state.currentSamp ) * OneOverSrate;
            
            // If we transposed/pitch-shifted the sound using sample rate change, the transpose octave above would
            // mean create new signal with every second sample missing, so the partial would start earlier. If we
            // would like to have partial transposed but in the same time t0 as original,  what the new phase will be?
            // It will be the original phase with the phase change during the delta of time. What delta of time is it?
            // The start time in sample-removing pitch shifted signal would be half of time if we transpose octave up so the
            // delta time is t0 - t0/transposeFactor. So the new phase goes like this (here we do not have time t0 so we get
            // it from partial[iSamp]/float(fs)).
            double phaseFixed = (bp->phase() + 2*Pi*p.avgFrequency*p.state.currentSamp*OneOverSrate*(m_osc.frequencyScaling()-1));

            m_osc.setPhase( phaseFixed - dphase );
        }
        
        int samplesToBp = tgtSamp - p.state.currentSamp;
        m_osc.oscillate( buffer, buffer + sampleDiff, *bp, m_srateHz, samplesToBp );

		buffer += sampleDiff;// move buffer pointer
        
		p.state.currentSamp += sampleDiff;
        p.state.breakpointFinished = tgtSamp == p.state.currentSamp;

        if (p.state.breakpointFinished)
        {
            //  remember the frequency, may need it to reset the
            //  phase if a Null Breakpoint is encountered:
//            m_osc.resetEnvelopes(*bp, m_srateHz);
//            p.state.prevFrequency = bp->frequency();
            
            p.state.prevFrequency = m_osc.envelopes().frequency();
//                m_osc.setPhase(bp->phase());
        }
        
        if (sampleCounter == samples)
        {
            i--; // there is still something to be done in this break point
            break;
        }
	}
    
    p.state.envelope = m_osc.envelopes();
    p.state.lastBreakpointIdx = i;
}