// --------------------------------------------------------------------------- // bandwidthAt // --------------------------------------------------------------------------- //! Return the interpolated bandwidth (noisiness) coefficient of //! this Partial at the specified time. At times beyond the ends of //! the Partial, return the bandwidth coefficient at the nearest //! envelope endpoint. Throw an InvalidPartial exception if this //! Partial has no Breakpoints. // double Partial::bandwidthAt( double time ) const { if ( numBreakpoints() == 0 ) { Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); } // findAfter returns the position of the earliest // Breakpoint later than time, or the end // position if no such Breakpoint exists: Partial::const_iterator it = findAfter( time ); if ( it == begin() ) { // time is before the onset of the Partial: return it.breakpoint().bandwidth(); } else if (it == end() ) { // time is past the end of the Partial: return (--it).breakpoint().bandwidth(); } else { // interpolate between it and its predeccessor // (we checked already that it is not begin): const Breakpoint & hi = it.breakpoint(); double hitime = it.time(); const Breakpoint & lo = (--it).breakpoint(); double lotime = it.time(); double alpha = (time - lotime) / (hitime - lotime); return (alpha * hi.bandwidth()) + ((1. - alpha) * lo.bandwidth()); } }
// --------------------------------------------------------------------------- // frequencyAt // --------------------------------------------------------------------------- //! Return the interpolated frequency (in Hz) of this Partial at the //! specified time. At times beyond the ends of the Partial, return //! the frequency at the nearest envelope endpoint. Throw an //! InvalidPartial exception if this Partial has no Breakpoints. // double Partial::frequencyAt( double time ) const { if ( numBreakpoints() == 0 ) { Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); } // lower_bound returns a reference to the lowest // position that would be higher than an element // having key equal to time: Partial::const_iterator it = findAfter( time ); if ( it == begin() ) { // time is before the onset of the Partial: return it.breakpoint().frequency(); } else if ( it == end() ) { // time is past the end of the Partial: return (--it).breakpoint().frequency(); } else { // interpolate between it and its predeccessor // (we checked already that it is not begin): const Breakpoint & hi = it.breakpoint(); double hitime = it.time(); const Breakpoint & lo = (--it).breakpoint(); double lotime = it.time(); double alpha = (time - lotime) / (hitime - lotime); return (alpha * hi.frequency()) + ((1. - alpha) * lo.frequency()); } }
// --------------------------------------------------------------------------- // findNearest (const version) // --------------------------------------------------------------------------- //! Return the insertion position for the Breakpoint nearest //! the specified time. Always returns a valid iterator (the //! position of the nearest-in-time Breakpoint) unless there //! are no Breakpoints. // Partial::const_iterator Partial::findNearest( double time ) const { // if there are no Breakpoints, return end: if ( numBreakpoints() == 0 ) { return end(); } // get the position of the first Breakpoint after time: Partial::const_iterator pos = findAfter( time ); // if there is an earlier Breakpoint that is closer in // time, prefer that one: if ( pos != begin() ) { Partial::const_iterator prev = pos; --prev; if ( pos == end() || pos.time() - time > time - prev.time() ) { return prev; } } // failing all else: return pos; }
// --------------------------------------------------------------------------- // phaseAt // --------------------------------------------------------------------------- //! Return the interpolated phase (in radians) of this Partial at //! the specified time. At times beyond the ends of the Partial, //! return the extrapolated from the nearest envelope endpoint //! (assuming constant frequency, as reported by frequencyAt()). //! Throw an InvalidPartial exception if this Partial has no //! Breakpoints. // double Partial::phaseAt( double time ) const { if ( numBreakpoints() == 0 ) { Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); } // findAfter returns the position of the earliest // Breakpoint later than time, or the end // position if no such Breakpoint exists: Partial::const_iterator it = findAfter( time ); // compute phase: if ( it == begin() ) { // time is before the onset of the Partial: double dp = 2. * Pi * (it.time() - time) * it.breakpoint().frequency(); return std::fmod( it.breakpoint().phase() - dp, 2. * Pi); } else if (it == end() ) { // time is past the end of the Partial: // ( first decrement iterator to get the tail Breakpoint) --it; double dp = 2. * Pi * (time - it.time()) * it.breakpoint().frequency(); return std::fmod( it.breakpoint().phase() + dp, 2. * Pi ); } else { // interpolate between it and its predeccessor // (we checked already that it is not begin): const Breakpoint & hi = it.breakpoint(); double hitime = it.time(); const Breakpoint & lo = (--it).breakpoint(); double lotime = it.time(); double alpha = (time - lotime) / (hitime - lotime); double finterp = ( alpha * hi.frequency() ) + ( ( 1. - alpha ) * lo.frequency() ); // need to keep fmod in here because other stuff // (Spc export and sdif export, for example) rely // on it: if ( alpha < 0.5 ) { double favg = 0.5 * ( lo.frequency() + finterp ); double dp = 2. * Pi * (time - lotime) * favg; return std::fmod( lo.phase() + dp, 2. * Pi ); } else { double favg = 0.5 * ( hi.frequency() + finterp ); double dp = 2. * Pi * (hitime - time) * favg; return std::fmod( hi.phase() - dp, 2. * Pi ); } } }
// --------------------------------------------------------------------------- // phaseTravel // // Compute the sinusoidal phase travel between two Breakpoints. // Return the total unwrapped phase travel. // static double phaseTravel( Partial::const_iterator bp0, Partial::const_iterator bp1 ) { double f0 = bp0->frequency(); double t0 = bp0.time(); double f1 = bp1->frequency(); double t1 = bp1.time(); double favg = .5 * ( f0 + f1 ); double dt = t1 - t0; return 2 * Pi * favg * dt; }
// --------------------------------------------------------------------------- // peakAmplitude // --------------------------------------------------------------------------- //! Return the maximum amplitude achieved by a partial. //! //! \param p is the Partial to evaluate //! \return the maximum (absolute) amplitude achieved by //! the partial p // double peakAmplitude( const Partial & p ) { double peak = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { peak = std::max( peak, it->amplitude() ); } return peak; }
// --------------------------------------------------------------------------- // phaseTravel // // Compute the sinusoidal phase travel between two Breakpoints. // Return the total unwrapped phase travel. // static double phaseTravel( Partial::const_iterator bp0, Partial::const_iterator bp1 ) { return phaseTravel( bp0.breakpoint(), bp1.breakpoint(), bp1.time() - bp0.time() ); /* double f0 = bp0->frequency(); double t0 = bp0.time(); double f1 = bp1->frequency(); double t1 = bp1.time(); double favg = .5 * ( f0 + f1 ); double dt = t1 - t0; return 2 * Pi * favg * dt; */ }
// --------------------------------------------------------------------------- // setup // --------------------------------------------------------------------------- //! Prepare internal structures for synthesis. PartialList is transformed to //! to more conveniant structure for real-time processing. reset() is also called. //! Fade in/out Breakpoints are inserted at either end of the Partial. //! Partials with start times earlier than the Partial fade //! time will have shorter onset fades. //! //! \param partials The Partials to synthesize. //! \param pitch original pitch of the partials //! \return Nothing. //! \post This RealTimeSynthesizer's is ready for synthesise the sound specified //! by given partials. void RealTimeSynthesizer::setup(PartialList & partials, double pitch) noexcept { this->partials.clear(); this->pitch = pitch; clearPartialsBeingProcessed(); // assuming I am getting sorted partials by time for (auto it : partials) { if (it.numBreakpoints() <= 0) continue; PartialStruct pStruct; pStruct.numBreakpoints = it.numBreakpoints() + 2;// + fade in + fade out pStruct.breakpoints.reserve(pStruct.numBreakpoints); pStruct.label = it.label(); pStruct.startTime = ( m_fadeTimeSec < it.startTime() ) ? ( it.startTime() - m_fadeTimeSec ) : 0.;// compute fade in bp time pStruct.endTime = it.endTime() + m_fadeTimeSec;// compute fade out bp time // breakpoints Partial::const_iterator jt = it.begin(); // fade in breakpoint, compute fade in time pStruct.breakpoints.push_back(std::make_pair(pStruct.startTime, BreakpointUtils::makeNullBefore( jt.breakpoint(), it.startTime() - pStruct.startTime))); double sumF = 0; for (; jt != it.end(); jt++) { sumF += jt->frequency(); pStruct.breakpoints.push_back(std::make_pair(jt.time(), jt.breakpoint())); } pStruct.avgFrequency = sumF / (float) it.numBreakpoints(); // fade out breakpoint jt--; pStruct.breakpoints.push_back(std::make_pair(jt.time() + m_fadeTimeSec, BreakpointUtils::makeNullAfter( jt.breakpoint(), m_fadeTimeSec ))); this->partials.push_back(pStruct); // pStruct.breakpoints[0].second.setAmplitude(pStruct.breakpoints[1].second.amplitude()); } reset(); }
// --------------------------------------------------------------------------- // channelize (one Partial) // --------------------------------------------------------------------------- //! Label a Partial with the number of the frequency channel corresponding to //! the average frequency over all the Partial's Breakpoints. //! //! \param partial is the Partial to label. // void Channelizer::channelize( Partial & partial ) const { debugger << "channelizing Partial with " << partial.numBreakpoints() << " Breakpoints" << endl; // compute an amplitude-weighted average channel // label for each Partial: double ampsum = 0.; double weightedlabel = 0.; Partial::const_iterator bp; for ( bp = partial.begin(); bp != partial.end(); ++bp ) { // use sinusoidal amplitude: double a = bp.breakpoint().amplitude() * std::sqrt( 1. - bp.breakpoint().bandwidth() ); // This used to be an amplitude-weighted avg, but for many sounds, // particularly those for which the weighted avg would be very // different from the simple avg, the amplitude-weighted avg // emphasized the part of the sound in which the frequency estimates // are least reliable (e.g. a piano tone). The unweighted // average should give more intuitive results in most cases. double f = bp.breakpoint().frequency(); double t = bp.time(); double refFreq = _refChannelFreq->valueAt( t ) / _refChannelLabel; // weightedlabel += a * (f / refFreq); weightedlabel += a * giveMeN( f, refFreq, _stretchFactor ); ampsum += a; } int label; if ( ampsum > 0. ) // if ( 0 < partial.numBreakpoints() ) { label = (int)((weightedlabel / ampsum) + 0.5); } else // this should never happen, but just in case: { label = 0; } Assert( label >= 0 ); // assign label, and remember it, but // only if it is a valid (positive) // distillation label: partial.setLabel( label ); }
// --------------------------------------------------------------------------- // channelize (one Partial) // --------------------------------------------------------------------------- //! Label a Partial with the number of the frequency channel corresponding to //! the average frequency over all the Partial's Breakpoints. //! //! \param partial is the Partial to label. // void Channelizer::channelize( Partial & partial ) const { using std::pow; debugger << "channelizing Partial with " << partial.numBreakpoints() << " Breakpoints" << endl; // compute an amplitude-weighted average channel // label for each Partial: //double ampsum = 0.; double weightedlabel = 0.; Partial::const_iterator bp; for ( bp = partial.begin(); bp != partial.end(); ++bp ) { double f = bp.breakpoint().frequency(); double t = bp.time(); double weight = 1; if ( 0 != _ampWeighting ) { // This used to be an amplitude-weighted avg, but for many sounds, // particularly those for which the weighted avg would be very // different from the simple avg, the amplitude-weighted avg // emphasized the part of the sound in which the frequency estimates // are least reliable (e.g. a piano tone). The unweighted // average should give more intuitive results in most cases. // use sinusoidal amplitude: double a = bp.breakpoint().amplitude() * std::sqrt( 1. - bp.breakpoint().bandwidth() ); weight = pow( a, _ampWeighting ); } weightedlabel += weight * computeFractionalChannelNumber( t, f ); } int label = 0; if ( 0 < partial.numBreakpoints() ) // should always be the case { label = (int)((weightedlabel / partial.numBreakpoints()) + 0.5); } Assert( label >= 0 ); // assign label, and remember it, but // only if it is a valid (positive) // distillation label: partial.setLabel( label ); }
// --------------------------------------------------------------------------- // avgFrequency // --------------------------------------------------------------------------- //! Return the average frequency over all Breakpoints in this Partial. //! Return zero if the Partial has no Breakpoints. //! //! \param p is the Partial to evaluate //! \return the average frequency (Hz) of Breakpoints in the Partial p // double avgFrequency( const Partial & p ) { double avg = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { avg += it->frequency(); } if ( avg != 0 ) { avg /= p.numBreakpoints(); } return avg; }
// --------------------------------------------------------------------------- // amplitudeAt // --------------------------------------------------------------------------- //! Return the interpolated amplitude of this Partial at the //! specified time. Throw an InvalidPartial exception if this //! Partial has no Breakpoints. If non-zero fadeTime is specified, //! then the amplitude at the ends of the Partial is coomputed using //! a linear fade. The default fadeTime is ShortestSafeFadeTime, //! see the definition of ShortestSafeFadeTime, above. // double Partial::amplitudeAt( double time, double fadeTime ) const { if ( numBreakpoints() == 0 ) Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); // findAfter returns the position of the earliest // Breakpoint later than time, or the end // position if no such Breakpoint exists: Partial::const_iterator it = findAfter( time ); if ( it == begin() ) { double alpha = (time < it.time()) ? 0. : 1.; if ( fadeTime > 0 ) { // fade in ampltude if time is before the onset of the Partial: alpha = std::max(0., 1. - ((it.time() - time) / fadeTime) ); } return alpha * it.breakpoint().amplitude(); } else if ( it == end() ) { // ( first decrement iterator to get the tail Breakpoint) --it; double alpha = (time > it.time()) ? 0. : 1.; if ( fadeTime > 0 ) { // fade out ampltude if time is past the end of the Partial: alpha = std::max(0., 1. - ((time - it.time()) / fadeTime) ); } return alpha * it.breakpoint().amplitude(); } else { // interpolate between it and its predeccessor // (we checked already that it is not begin): const Breakpoint & hi = it.breakpoint(); double hitime = it.time(); const Breakpoint & lo = (--it).breakpoint(); double lotime = it.time(); double alpha = (time - lotime) / (hitime - lotime); return (alpha * hi.amplitude()) + ((1. - alpha) * lo.amplitude()); } }
// --------------------------------------------------------------------------- // weightedAvgFrequency // --------------------------------------------------------------------------- //! Return the average frequency over all Breakpoints in this Partial, //! weighted by the Breakpoint amplitudes. //! Return zero if the Partial has no Breakpoints. //! //! \param p is the Partial to evaluate //! \return the average frequency (Hz) of Breakpoints in the Partial p // double weightedAvgFrequency( const Partial & p ) { double avg = 0; double ampsum = 0; for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) { avg += it->amplitude() * it->frequency(); ampsum += it->amplitude(); } if ( avg != 0 && ampsum != 0 ) { avg /= ampsum; } else { avg = 0; } return avg; }
// --------------------------------------------------------------------------- // timeOfPeakEnergy (static helper function) // --------------------------------------------------------------------------- // Return the time at which the given Partial attains its // maximum sinusoidal energy. // static double timeOfPeakEnergy( const Partial & p ) { Partial::const_iterator partialIter = p.begin(); double maxAmp = partialIter->amplitude() * std::sqrt( 1. - partialIter->bandwidth() ); double time = partialIter.time(); for ( ++partialIter; partialIter != p.end(); ++partialIter ) { double a = partialIter->amplitude() * std::sqrt( 1. - partialIter->bandwidth() ); if ( a > maxAmp ) { maxAmp = a; time = partialIter.time(); } } return time; }
// --------------------------------------------------------------------------- // 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 ); }
// --------------------------------------------------------------------------- // parametersAt // --------------------------------------------------------------------------- //! Return the interpolated parameters of this Partial at //! the specified time, same as building a Breakpoint from //! the results of frequencyAt, ampitudeAt, bandwidthAt, and //! phaseAt, but performs only one Breakpoint envelope search. //! Throw an InvalidPartial exception if this Partial has no //! Breakpoints. If non-zero fadeTime is specified, then the //! amplitude at the ends of the Partial is coomputed using a //! linear fade. The default fadeTime is ShortestSafeFadeTime. // Breakpoint Partial::parametersAt( double time, double fadeTime ) const { if ( numBreakpoints() == 0 ) { Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); } // findAfter returns the position of the earliest // Breakpoint later than time, or the end // position if no such Breakpoint exists: Partial::const_iterator it = findAfter( time ); if ( it == begin() ) { // time is before the onset of the Partial: // frequency is starting frequency, // amplitude is 0 (or fading), bandwidth is starting // bandwidth, and phase is rolled back. double alpha = (time < it.time()) ? 0. : 1.; if ( fadeTime > 0 ) { // fade in ampltude if time is before the onset of the Partial: alpha = std::max(0., 1. - ((it.time() - time) / fadeTime) ); } double amp = alpha * it.breakpoint().amplitude(); double dp = 2. * Pi * (it.time() - time) * it.breakpoint().frequency(); double ph = std::fmod( it.breakpoint().phase() - dp, 2. * Pi); return Breakpoint( it.breakpoint().frequency(), amp, it.breakpoint().bandwidth(), ph ); } else if (it == end() ) { // time is past the end of the Partial: // frequency is ending frequency, // amplitude is 0 (or fading), bandwidth is ending // bandwidth, and phase is rolled forward. --it; double alpha = (time > it.time()) ? 0. : 1.; if ( fadeTime > 0 ) { // fade out ampltude if time is past the end of the Partial: alpha = std::max(0., 1. - ((time - it.time()) / fadeTime) ); } double amp = alpha * it.breakpoint().amplitude(); double dp = 2. * Pi * (time - it.time()) * it.breakpoint().frequency(); double ph = std::fmod( it.breakpoint().phase() + dp, 2. * Pi ); return Breakpoint( it.breakpoint().frequency(), amp, it.breakpoint().bandwidth(), ph ); } else { // interpolate between it and its predeccessor // (we checked already that it is not begin): const Breakpoint & hi = it.breakpoint(); double hitime = it.time(); const Breakpoint & lo = (--it).breakpoint(); double lotime = it.time(); double alpha = (time - lotime) / (hitime - lotime); double finterp = ( alpha * hi.frequency() ) + ( ( 1. - alpha ) * lo.frequency() ); // need to keep fmod in here because other stuff // (Spc export and sdif export, for example) rely // on it: double ph = 0; if ( alpha < 0.5 ) { double favg = 0.5 * ( lo.frequency() + finterp ); double dp = 2. * Pi * (time - lotime) * favg; ph = std::fmod( lo.phase() + dp, 2. * Pi ); } else { double favg = 0.5 * ( hi.frequency() + finterp ); double dp = 2. * Pi * (hitime - time) * favg; ph = std::fmod( hi.phase() - dp, 2. * Pi ); } return Breakpoint( (alpha * hi.frequency()) + ((1. - alpha) * lo.frequency()), (alpha * hi.amplitude()) + ((1. - alpha) * lo.amplitude()), (alpha * hi.bandwidth()) + ((1. - alpha) * lo.bandwidth()), ph ); } }
// --------------------------------------------------------------------------- // merge (STATIC) // --------------------------------------------------------------------------- // Merge the Breakpoints in the specified iterator range into the // distilled Partial. The beginning of the range may overlap, and // will replace, some non-zero-amplitude portion of the distilled // Partial. Assume that there is no such overlap at the end of the // range (could check), because findContribution only leaves overlap // at the beginning of the range. // static void merge( Partial::const_iterator beg, Partial::const_iterator end, Partial & destPartial, double fadeTime, double gapTime = 0. ) { // absorb energy in distilled Partial that overlaps the // range to merge: Partial toMerge( beg, end ); toMerge.absorb( destPartial ); fadeInAndOut( toMerge, fadeTime ); // find the first Breakpoint in destPartial that is after the // range of merged Breakpoints, plus the required gap: Partial::iterator removeEnd = destPartial.findAfter( toMerge.endTime() + gapTime ); // if this Breakpoint has non-zero amplitude, need to leave time // for a fade in: while ( removeEnd != destPartial.end() && removeEnd.breakpoint().amplitude() != 0 && removeEnd.time() < toMerge.endTime() + gapTime + fadeTime ) { ++removeEnd; } // find the first Breakpoint in the destination Partial that needs // to be removed because it is in the overlap region: Partial::iterator removeBegin = destPartial.findAfter( toMerge.startTime() - gapTime ); // if beforeMerge has non-zero amplitude, need to leave time // for a fade out: if ( removeBegin != destPartial.begin() ) { Partial::iterator beforeMerge = --Partial::iterator(removeBegin); while ( removeBegin != destPartial.begin() && beforeMerge.breakpoint().amplitude() != 0 && beforeMerge.time() > toMerge.startTime() - gapTime - fadeTime ) { --removeBegin; if ( beforeMerge != destPartial.begin() ) { --beforeMerge; } } } // remove the Breakpoints in the merge range from destPartial: double rbt = (removeBegin != destPartial.end())?(removeBegin.time()):(destPartial.endTime()); double ret = (removeEnd != destPartial.end())?(removeEnd.time()):(destPartial.endTime()); Assert( rbt <= ret ); destPartial.erase( removeBegin, removeEnd ); // how about doing the fades here instead? // fade in if necessary: if ( removeEnd != destPartial.end() && removeEnd.breakpoint().amplitude() != 0 ) { Assert( removeEnd.time() - fadeTime > toMerge.endTime() ); // update removeEnd so that we don't remove this // null we are inserting: destPartial.insert( removeEnd.time() - fadeTime, BreakpointUtils::makeNullBefore( removeEnd.breakpoint(), fadeTime ) ); } if ( removeEnd != destPartial.begin() ) { Partial::iterator beforeMerge = --Partial::iterator(removeEnd); if ( beforeMerge.breakpoint().amplitude() > 0 ) { Assert( beforeMerge.time() + fadeTime < toMerge.startTime() ); destPartial.insert( beforeMerge.time() + fadeTime, BreakpointUtils::makeNullAfter( beforeMerge.breakpoint(), fadeTime ) ); } } // insert the Breakpoints in the range: for ( Partial::const_iterator insert = toMerge.begin(); insert != toMerge.end(); ++insert ) { destPartial.insert( insert.time(), insert.breakpoint() ); } }
// --------------------------------------------------------------------------- // dilate // --------------------------------------------------------------------------- //! Replace the Partial envelope with a new envelope having the //! same Breakpoints at times computed to align temporal features //! in the sorted sequence of initial time points with their //! counterparts the sorted sequence of target time points. //! //! Depending on the specification of initial and target time //! points, the dilated Partial may have Breakpoints at times //! less than 0, even if the original Partial did not. //! //! It is possible to have duplicate time points in either sequence. //! Duplicate initial time points result in very localized stretching. //! Duplicate target time points result in very localized compression. //! //! If all initial time points are greater than 0, then an implicit //! time point at 0 is assumed in both initial and target sequences, //! so the onset of a sound can be stretched without explcitly specifying a //! zero point in each vector. (This seems most intuitive, and only looks //! like an inconsistency if clients are using negative time points in //! their Dilator, or Partials having Breakpoints before time 0, both //! of which are probably unusual circumstances.) //! //! \param p is the Partial to dilate. // void Dilator::dilate( Partial & p ) const { debugger << "dilating Partial having " << p.numBreakpoints() << " Breakpoints" << endl; // sanity check: Assert( _initial.size() == _target.size() ); // don't dilate if there's no time points, or no Breakpoints: if ( 0 == _initial.size() || 0 == p.numBreakpoints() ) { return; } // create the new Partial: Partial newp; newp.setLabel( p.label() ); // timepoint index: int idx = 0; for ( Partial::const_iterator iter = p.begin(); iter != p.end(); ++iter ) { // find the first initial time point later // than the currentTime: double currentTime = iter.time(); idx = std::distance( _initial.begin(), std::lower_bound( _initial.begin(), _initial.end(), currentTime ) ); Assert( idx == _initial.size() || currentTime <= _initial[idx] ); // compute a new time for the Breakpoint at pIter: double newtime = 0; if ( idx == 0 ) { // all time points in _initial are later than // the currentTime; stretch if no zero time // point has been specified, otherwise, shift: if ( _initial[idx] != 0. ) newtime = currentTime * _target[idx] / _initial[idx]; else newtime = _target[idx] + (currentTime - _initial[idx]); } else if ( idx == _initial.size() ) { // all time points in _initial are earlier than // the currentTime; shift: // // note: size is already known to be > 0, so // idx-1 is safe newtime = _target[idx-1] + (currentTime - _initial[idx-1]); } else { // currentTime is between the time points at idx and // idx-1 in _initial; shift and stretch: // // note: size is already known to be > 0, so // idx-1 is safe Assert( _initial[idx-1] < _initial[idx] ); // currentTime can't wind up // between two equal times double stretch = (_target[idx] - _target[idx-1]) / (_initial[idx] - _initial[idx-1]); newtime = _target[idx-1] + ((currentTime - _initial[idx-1]) * stretch); } // add a Breakpoint at the computed time: newp.insert( newtime, iter.breakpoint() ); } // new Breakpoints need to be added to the Partial at times corresponding // to all target time points that are after the first Breakpoint and // before the last, otherwise, Partials may be briefly out of tune with // each other, since our Breakpoints are non-uniformly distributed in time: for ( idx = 0; idx < _initial.size(); ++ idx ) { if ( _initial[idx] <= p.startTime() ) { continue; } else if ( _initial[idx] >= p.endTime() ) { break; } else { newp.insert( _target[idx], Breakpoint( p.frequencyAt(_initial[idx]), p.amplitudeAt(_initial[idx]), p.bandwidthAt(_initial[idx]), p.phaseAt(_initial[idx]) ) ); } } // store the new Partial: p = newp; }