bool PartialBuilder::better_match( const Partial & part1, const Partial & part2, const SpectralPeak & pk ) { Assert( part1.numBreakpoints() > 0 ); Assert( part2.numBreakpoints() > 0 ); return freq_distance( part1, pk ) < freq_distance( part2, pk ); }
// --------------------------------------------------------------------------- // 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 ); }
bool PartialBuilder::better_match( const Partial & part, const SpectralPeak & pk1, const SpectralPeak & pk2 ) { Assert( part.numBreakpoints() > 0 ); return freq_distance( part, pk1 ) < freq_distance( part, pk2 ); }
// --------------------------------------------------------------------------- // fixPhaseBefore // //! Recompute phases of all Breakpoints earlier than the specified time //! so that the synthesize phases of those earlier Breakpoints matches //! the stored phase, and the synthesized phase at the specified //! time matches the stored (not recomputed) phase. //! //! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint //! is encountered, because nulls are interpreted as phase reset points //! in Loris. If a null is encountered, the remainder of the Partial //! (the front part) is fixed in the forward direction, beginning at //! the start of the Partial. //! //! \param p The Partial whose phases should be fixed. //! \param t The time before which phases should be adjusted. // void fixPhaseBefore( Partial & p, double t ) { if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseBackward( p.begin(), pos ); } }
// --------------------------------------------------------------------------- // fixPhaseAfter // //! Recompute phases of all Breakpoints later than the specified time //! so that the synthesize phases of those later Breakpoints matches //! the stored phase, as long as the synthesized phase at the specified //! time matches the stored (not recomputed) phase. //! //! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, //! because null Breakpoints are interpreted as phase reset points in //! Loris. If a null is encountered, its phase is simply left unmodified, //! and future phases wil be recomputed from that one. //! //! \param p The Partial whose phases should be fixed. //! \param t The time after which phases should be adjusted. // void fixPhaseAfter( Partial & p, double t ) { // nothing to do it there are not at least // two Breakpoints in the Partial if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseForward( pos, --p.end() ); } }
// --------------------------------------------------------------------------- // fixFrequency // //! Adjust frequencies of the Breakpoints in the //! specified Partial such that the rendered Partial //! achieves (or matches as nearly as possible, within //! the constraint of the maximum allowable frequency //! alteration) the analyzed phases. //! //! This just iterates over the Partial calling //! matchPhaseFwd, should probably name those similarly. //! //! \param partial The Partial whose frequencies, //! and possibly phases (if the frequencies //! cannot be sufficiently altered to match //! the phases), will be recomputed. //! \param maxFixPct The maximum allowable frequency //! alteration, default is 0.2%. // void fixFrequency( Partial & partial, double maxFixPct ) { if ( partial.numBreakpoints() > 1 ) { Partial::iterator next = partial.begin(); Partial::iterator prev = next++; while ( next != partial.end() ) { matchPhaseFwd( prev.breakpoint(), next.breakpoint(), next.time() - prev.time(), 0.5, maxFixPct ); prev = next++; } } }
// --------------------------------------------------------------------------- // 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 ); }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // fixPhaseForward // //! Recompute phases of all Breakpoints later than the specified time //! so that the synthesize phases of those later Breakpoints matches //! the stored phase, as long as the synthesized phase at the specified //! time matches the stored (not recomputed) phase. Breakpoints later than //! tend are unmodified. //! //! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, //! because null Breakpoints are interpreted as phase reset points in //! Loris. If a null is encountered, its phase is simply left unmodified, //! and future phases wil be recomputed from that one. //! //! HEY Is this interesting, in general? Why would you want to do this? //! //! \param p The Partial whose phases should be fixed. //! \param tbeg The phases and frequencies of Breakpoints later than the //! one nearest this time will be modified. //! \param tend The phases and frequencies of Breakpoints earlier than the //! one nearest this time will be modified. Should be greater //! than tbeg, or else they will be swapped. // void fixPhaseForward( Partial & p, double tbeg, double tend ) { if ( tbeg > tend ) { std::swap( tbeg, tend ); } // nothing to do it there are not at least // two Breakpoints in the Partial if ( 1 < p.numBreakpoints() ) { // find the positions nearest tbeg and tend Partial::iterator posbeg = p.findNearest( tbeg ); Partial::iterator posend = p.findNearest( tend ); // if the positions are different, and tend is // the end, back it up if ( posbeg != posend && posend == p.end() ) { --posend; } fixPhaseForward( posbeg, posend ); } }
// ----------- test_distill_overlapping3 ----------- // static void test_distill_overlapping3( void ) { std::cout << "\t--- testing distill on three " "temporally-overlapping Partials... ---\n\n"; // Fabricate three Partials, overlapping temporally, give // them the same label, and distill them. Partial p1; p1.insert( 0, Breakpoint( 100, 0.4, 0, 0 ) ); p1.insert( 0.28, Breakpoint( 100, 0.4, 0, .1 ) ); p1.setLabel( 123 ); Partial p2; p2.insert( 0.2, Breakpoint( 200, 0.3, 0.2, 0 ) ); p2.insert( 0.29, Breakpoint( 200, 0.3, 0.2, .1 ) ); p2.insert( 0.35, Breakpoint( 200, 0.3, 0.2, .1 ) ); p2.setLabel( 123 ); Partial p3; p3.insert( 0.32, Breakpoint( 300, 0.3, 0, 0 ) ); p3.insert( 0.4, Breakpoint( 310, 0.3, 0.2, .1 ) ); p3.insert( 0.7, Breakpoint( 310, 0.3, 0.2, .1 ) ); p3.setLabel( 123 ); PartialList l; l.push_back( p3 ); l.push_back( p1 ); l.push_back( p2 ); const double fade = .008; // 8 ms Distiller d( fade ); d.distill( l ); // Fabricate the Partial that the distillation should // produce. Partial compare; // first Breakpoint from p1 compare.insert( 0, Breakpoint( 100, 0.4, 0, 0 ) ); // null Breakpoint at 0+fade double t = 0 + fade; compare.insert( t, Breakpoint( p1.frequencyAt(t), 0, p1.bandwidthAt(t), p1.phaseAt(t) ) ); // interpolated Breakpoint at .18 (.2 - 2*fade) //double t = 0.2 - (2*fade); //compare.insert( t, p1.parametersAt( t ) ); // null Breakpoint at .19 (.2-fade) // bandwidth introduced in the overlap region: // (0.4^2 + 0.2*0.3^2) / (0.3^2 + 0.4^2)) = 0.712 // amp = sqrt(0.3^2 + 0.4^2) = .5 // no, actually zero-amplitude Breakpoints are // introduced with zero bandwidth. t = 0.2 - fade; compare.insert( t, Breakpoint( p2.frequencyAt(t), 0, 0, // 0.712, p2.phaseAt(t) ) ); // first Breakpoint from p2: compare.insert( 0.2, Breakpoint( 200, 0.5, 0.712, 0 ) ); // second Breakpoint from p2: compare.insert( 0.29, Breakpoint( 200, 0.3, 0.2, 0.1 ) ); // null Breakpoint at .29 + fade t = 0.29 + fade; compare.insert( t, Breakpoint( p2.frequencyAt(t), 0, 0, // p2.bandwidthAt(t), p2.phaseAt(t) ) ); // null Breakpoint at .31 (.32-fade) t = 0.32 - fade; compare.insert( t, Breakpoint( p3.frequencyAt(t), 0, 0, p3.phaseAt(t) ) ); // first Breakpoint from p3 (with bandwidth): compare.insert( 0.32, Breakpoint( 300, std::sqrt(0.18), 0.5, 0 ) ); // second Breakpoint from p3: compare.insert( 0.4, Breakpoint( 310, 0.3, 0.2, .1 ) ); // third Breakpoint from p3: compare.insert( 0.7, Breakpoint( 310, 0.3, 0.2, .1 ) ); compare.setLabel( 123 ); // compare Partials (distilled Partials // should be in label order): TEST_VALUE( l.size(), 1 ); TEST_VALUE( l.begin()->numBreakpoints(), compare.numBreakpoints() ); Partial::iterator distit = l.begin()->begin(); Partial::iterator compareit = compare.begin(); while ( compareit != compare.end() ) { SAME_PARAM_VALUES( distit.time(), compareit.time() ); SAME_PARAM_VALUES( distit->frequency(), compareit->frequency() ); SAME_PARAM_VALUES( distit->amplitude(), compareit->amplitude() ); SAME_PARAM_VALUES( distit->bandwidth(), compareit->bandwidth() ); SAME_PARAM_VALUES( wrapPi( distit->phase() ), wrapPi( compareit->phase() ) ); ++compareit; ++distit; } }
// ----------- test_distill_overlapping2 ----------- // static void test_distill_overlapping2( void ) { std::cout << "\t--- testing distill on two " "temporally-overlapping Partials... ---\n\n"; // Fabricate two Partials, overlapping temporally, give // them the same label, and distill them. Partial p1; p1.insert( 0, Breakpoint( 100, 0.4, 0, 0 ) ); p1.insert( 0.3, Breakpoint( 100, 0.4, 0, .1 ) ); p1.setLabel( 12 ); Partial p2; p2.insert( 0.2, Breakpoint( 200, 0.3, 0, 0 ) ); p2.insert( 0.35, Breakpoint( 210, 0.3, 0.2, .1 ) ); p2.setLabel( 12 ); PartialList l; l.push_back( p1 ); l.push_back( p2 ); const double fade = .01; // 10 ms Distiller d( fade ); d.distill( l ); for ( Partial::iterator distit = l.front().begin(); distit != l.front().end(); ++distit ) { cout << distit.time() << " " << distit.breakpoint().frequency() << endl; } // Fabricate the Partial that the distillation should // produce. Partial compare; // first Breakpoint from p1 compare.insert( 0, Breakpoint( 100, 0.4, 0, 0 ) ); // null Breakpoint at 0+fade double t = 0 + fade; compare.insert( t, Breakpoint( p1.frequencyAt(t), 0, p1.bandwidthAt(t), p1.phaseAt(t) ) ); // interpolated Breakpoint at .18 (.2 - 2*fade) //double t = 0.2 - (2*fade); //compare.insert( t, p1.parametersAt( t ) ); // null Breakpoint at .19 (.2-fade) // bandwidth introduced in the overlap region: // 0.4^2 / (0.3^2 + 0.4^2) = 0.64 // amp = sqrt(0.3^2 + 0.4^2) = .5 // no, actually zero-amplitude Breakpoints are // introduced with zero bandwidth. t = 0.2 - fade; compare.insert( t, Breakpoint( p2.frequencyAt(t), 0, 0, // 0.64, p2.phaseAt(t) ) ); // first Breakpoint from p2: compare.insert( 0.2, Breakpoint( 200, 0.5, 0.64, 0 ) ); // second Breakpoint from p2 compare.insert( 0.35, Breakpoint( 210, 0.3, 0.2, .1 ) ); compare.setLabel( 12 ); // compare Partials (distilled Partials // should be in label order): TEST( l.size() == 1 ); TEST( l.begin()->numBreakpoints() == compare.numBreakpoints() ); TEST( l.begin()->label() == compare.label() ); Partial::iterator distit = l.begin()->begin(); Partial::iterator compareit = compare.begin(); while ( compareit != compare.end() ) { SAME_PARAM_VALUES( distit.time(), compareit.time() ); SAME_PARAM_VALUES( distit->frequency(), compareit->frequency() ); SAME_PARAM_VALUES( distit->amplitude(), compareit->amplitude() ); SAME_PARAM_VALUES( distit->bandwidth(), compareit->bandwidth() ); SAME_PARAM_VALUES( distit->phase(), compareit->phase() ); ++compareit; ++distit; } }
// ----------- test_distill_nonoverlapping ----------- // static void test_distill_nonoverlapping( void ) { std::cout << "\t--- testing distill on " "non-overlapping Partials... ---\n\n"; // Fabricate three non-overlapping Partials, give // them all the same label, and distill them. Also // add a fourth Partial with a different label, verify // that it remains unaffacted. Partial p1; p1.insert( 0, Breakpoint( 100, 0.1, 0, 0 ) ); p1.insert( .1, Breakpoint( 110, 0.2, 0.2, .1 ) ); p1.setLabel( 123 ); Partial p2; p2.insert( 0.2, Breakpoint( 200, 0.1, 0, 0 ) ); p2.insert( 0.3, Breakpoint( 210, 0.2, 0.2, .1 ) ); p2.setLabel( 123 ); Partial p3; p3.insert( 0.4, Breakpoint( 300, 0.1, 0, 0 ) ); p3.insert( 0.5, Breakpoint( 310, 0.2, 0.2, .1 ) ); p3.setLabel( 123 ); Partial p4; p4.insert( 0, Breakpoint( 400, 0.1, 0, 0 ) ); p4.insert( 0.5, Breakpoint( 410, 0.2, 0.2, .1 ) ); p4.setLabel( 4 ); PartialList l; l.push_back( p1 ); l.push_back( p3 ); l.push_back( p4 ); l.push_back( p2 ); const double fade = .01; // 10 ms Distiller d( fade ); d.distill( l ); // Fabricate the Partial that the distillation should // produce. Partial compare; compare.insert( 0, Breakpoint( 100, 0.1, 0, 0 ) ); compare.insert( 0.1, Breakpoint( 110, 0.2, 0.2, .1 ) ); double t = 0.1 + fade; compare.insert( t, Breakpoint( p1.frequencyAt(t), 0, 0, // p1.bandwidthAt(t), p1.phaseAt(t) ) ); t = 0.2 - fade; compare.insert( t, Breakpoint( p2.frequencyAt(t), 0, 0, // p2.bandwidthAt(t), p2.phaseAt(t) ) ); compare.insert( 0.2, Breakpoint( 200, 0.1, 0, 0 ) ); compare.insert( 0.3, Breakpoint( 210, 0.2, 0.2, .1 ) ); t = 0.3 + fade; compare.insert( t, Breakpoint( p2.frequencyAt(t), 0, 0, // p2.bandwidthAt(t), p2.phaseAt(t) ) ); t = 0.4 - fade; compare.insert( t, Breakpoint( p3.frequencyAt(t), 0, 0, // p3.bandwidthAt(t), p3.phaseAt(t) ) ); compare.insert( 0.4, Breakpoint( 300, 0.1, 0, 0 ) ); compare.insert( 0.5, Breakpoint( 310, 0.2, 0.2, .1 ) ); compare.setLabel( 123 ); // compare Partials (distilled Partials // should be in label order): TEST( l.size() == 2 ); PartialList::iterator it = l.begin(); TEST( it->label() == p4.label() ); TEST( it->numBreakpoints() == p4.numBreakpoints() ); ++it; for ( Partial::iterator distit = it->begin(); distit != it->end(); ++distit ) { cout << distit.time() << " " << distit.breakpoint().frequency() << endl; } TEST( it->numBreakpoints() == compare.numBreakpoints() ); Partial::iterator distit = it->begin(); Partial::iterator compareit = compare.begin(); while ( compareit != compare.end() ) { SAME_PARAM_VALUES( distit.time(), compareit.time() ); SAME_PARAM_VALUES( distit->frequency(), compareit->frequency() ); SAME_PARAM_VALUES( distit->amplitude(), compareit->amplitude() ); SAME_PARAM_VALUES( distit->bandwidth(), compareit->bandwidth() ); SAME_PARAM_VALUES( distit->phase(), compareit->phase() ); ++compareit; ++distit; } }
// --------------------------------------------------------------------------- // 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 ); }
// --------------------------------------------------------------------------- // 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; }