// --------------------------------------------------------------------------- // 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 ); } }
// --------------------------------------------------------------------------- // 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 } }
// --------------------------------------------------------------------------- // 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; }