// --------------------------------------------------------------------------- // freq_distance // --------------------------------------------------------------------------- // Helper function, used in formPartials(). // Returns the (positive) frequency distance between a Breakpoint // and the last Breakpoint in a Partial. // inline double PartialBuilder::freq_distance( const Partial & partial, const SpectralPeak & pk ) { double normBpFreq = pk.frequency() / mFreqWarping->valueAt( pk.time() ); double normPartialEndFreq = partial.last().frequency() / mFreqWarping->valueAt( partial.endTime() ); return std::fabs( normPartialEndFreq - normBpFreq ); }
Iter find_overlapping( Partial & p, double minGapTime, Iter start, Iter end) { for ( Iter it = start; it != end; ++it ) { // skip if other partial is already sifted out. if ( (*it)->label() == 0 ) continue; // skip the source Partial: // (identity test: compare addresses) // (this is a sanity check, should not happen since // src should be at position end) Assert( (*it) != &p ); // test for overlap: if ( p.startTime() < (*it)->endTime() + minGapTime && p.endTime() + minGapTime > (*it)->startTime() ) { // Does the overlapping Partial have longer duration? // (this should never be true, since the Partials // are sorted by duration) Assert( p.duration() <= (*it)->duration() ); #if Debug_Loris debugger << "Partial starting " << p.startTime() << ", " << p.begin().breakpoint().frequency() << " ending " << p.endTime() << ", " << (--p.end()).breakpoint().frequency() << " zapped by overlapping Partial starting " << (*it)->startTime() << ", " << (*it)->begin().breakpoint().frequency() << " ending " << (*it)->endTime() << ", " << (--(*it)->end()).breakpoint().frequency() << endl; #endif return it; } } // no overlapping Partial found: return end; }
// --------------------------------------------------------------------------- // fadeInAndOut (STATIC) // --------------------------------------------------------------------------- // Add zero-amplitude Breakpoints to the ends of a Partial if necessary. // Do this to all Partials before distilling to make distillation easier. // static void fadeInAndOut( Partial & p, double fadeTime ) { if ( p.first().amplitude() != 0 ) { p.insert( p.startTime() - fadeTime, BreakpointUtils::makeNullBefore( p.first(), fadeTime ) ); } if ( p.last().amplitude() != 0 ) { p.insert( p.endTime() + fadeTime, BreakpointUtils::makeNullAfter( p.last(), fadeTime ) ); } }
// --------------------------------------------------------------------------- // fixPhaseBetween // //! Fix the phase travel between two times by adjusting the //! frequency and phase of Breakpoints between those two times. //! //! This algorithm assumes that there is nothing interesting about the //! phases of the intervening Breakpoints, and modifies their frequencies //! as little as possible to achieve the correct amount of phase travel //! such that the frequencies and phases at the specified times //! match the stored values. The phases of all the Breakpoints between //! the specified times are recomputed. //! //! THIS DOES NOT YET TREAT NULL BREAKPOINTS DIFFERENTLY FROM OTHERS. //! //! \pre There must be at least one Breakpoint in the //! Partial between the specified times tbeg and tend. //! \post The phases and frequencies of the Breakpoints in the //! range have been recomputed such that an oscillator //! initialized to the parameters of the first Breakpoint //! will arrive at the parameters of the last Breakpoint, //! and all the intervening Breakpoints will be matched. //! \param p The partial whose phases and frequencies will be recomputed. //! The Breakpoint at this position is unaltered. //! \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 fixPhaseBetween( Partial & p, double tbeg, double tend ) { if ( tbeg > tend ) { std::swap( tbeg, tend ); } // for Partials that do not extend over the entire // specified time range, just recompute phases from // beginning or end of the range: if ( p.endTime() < tend ) { // OK if start time is also after tbeg, will // just recompute phases from start of p. fixPhaseAfter( p, tbeg ); } else if ( p.startTime() > tbeg ) { fixPhaseBefore( p, tend ); } else { // invariant: // p begins before tbeg and ends after tend. Partial::iterator b = p.findNearest( tbeg ); Partial::iterator e = p.findNearest( tend ); // if there is a null Breakpoint n between b and e, then // should fix forward from b to n, and backward from // e to n. Otherwise, do this complicated thing. Partial::iterator nullbp = std::find_if( b, e, BreakpointUtils::isNull ); if ( nullbp != e ) { fixPhaseForward( b, nullbp ); fixPhaseBackward( nullbp, e ); } else { fixPhaseBetween( b, e ); } } }
// --------------------------------------------------------------------------- // absorb // --------------------------------------------------------------------------- //! Absorb another Partial's energy as noise (bandwidth), //! by accumulating the other's energy as noise energy //! in the portion of this Partial's envelope that overlaps //! (in time) with the other Partial's envelope. // void Partial::absorb( const Partial & other ) { Partial::iterator it = findAfter( other.startTime() ); while ( it != end() && !(it.time() > other.endTime()) ) { // only non-null (non-zero-amplitude) Breakpoints // abosrb noise energy because null Breakpoints // are used especially to reset the Partial phase, // and are not part of the normal analyasis data: if ( it->amplitude() > 0 ) { // absorb energy from other at the time // of this Breakpoint: double a = other.amplitudeAt( it.time() ); it->addNoiseEnergy( a * a ); } ++it; } }
// --------------------------------------------------------------------------- // 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() ); } }
// --------------------------------------------------------------------------- // helper predicates // --------------------------------------------------------------------------- static bool ends_earlier( const Partial & lhs, const Partial & rhs ) { return lhs.endTime() < rhs.endTime(); }
bool operator() ( const Partial & p ) const { return p.endTime() < t; }
// --------------------------------------------------------------------------- // 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; }