// --------------------------------------------------------------------------- // Cropper function call operator // --------------------------------------------------------------------------- // Trim a Partial by removing Breakpoints outside a specified time span. // Insert a Breakpoint at the boundary when cropping occurs. // void Cropper::operator()( Partial & p ) const { // crop beginning of Partial Partial::iterator it = p.findAfter( minTime ); if ( it != p.begin() ) // Partial begins earlier than minTime { if ( it != p.end() ) // Partial ends later than minTime { Breakpoint bp = p.parametersAt( minTime ); it = p.insert( minTime, bp ); } it = p.erase( p.begin(), it ); } // crop end of Partial it = p.findAfter( maxTime ); if ( it != p.end() ) // Partial ends later than maxTime { if ( it != p.begin() ) // Partial begins earlier than maxTime { Breakpoint bp = p.parametersAt( maxTime ); it = p.insert( maxTime, bp ); ++it; // advance, we don't want to cut this one off } it = p.erase( it, p.end() ); } }
// --------------------------------------------------------------------------- // fixPhaseAt // //! Recompute phases of all Breakpoints in a Partial //! so that the synthesize phases match the stored phases, //! and the synthesized phase at (nearest) 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. Forward phase fixing is only applied //! to non-null (nonzero-amplitude) Breakpoints. 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 at which phases should be made correct. // void fixPhaseAt( Partial & p, double t ) { if ( 1 < p.numBreakpoints() ) { Partial::iterator pos = p.findNearest( t ); Assert( pos != p.end() ); fixPhaseForward( 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() ); } }
// --------------------------------------------------------------------------- // findContribution (STATIC) // --------------------------------------------------------------------------- // Find and return an iterator range delimiting the portion of pshort that // should be spliced into the distilled Partial plong. If any Breakpoint // falls in a zero-amplitude region of plong, then pshort should contribute, // AND its onset should be retained (!!! This is the weird part!!!). // Therefore, if cbeg is not equal to cend, then cbeg is pshort.begin(). // std::pair< Partial::iterator, Partial::iterator > findContribution( Partial & pshort, const Partial & plong, double fadeTime, double gapTime ) { // a Breakpoint can only fit in the gap if there's // enough time to fade out pshort, introduce a // space of length gapTime, and fade in the rest // of plong (don't need to worry about the fade // in, because we are checking that plong is zero // at cbeg.time() + clearance anyway, so the fade // in must occur after that, and already be part of // plong): // // WRONG if cbeg is before the start of plong. // Changed so that all Partials are faded in and // out before distilling, so now the clearance // need only be the gap time: double clearance = gapTime; // fadeTime + gapTime; Partial::iterator cbeg = pshort.begin(); while ( cbeg != pshort.end() && ( plong.amplitudeAt( cbeg.time() ) > 0 || plong.amplitudeAt( cbeg.time() + clearance ) > 0 ) ) { ++cbeg; } Partial::iterator cend = cbeg; // if a gap is found, find the end of the // range of Breakpoints that fit in that // gap: while ( cend != pshort.end() && plong.amplitudeAt( cend.time() ) == 0 && plong.amplitudeAt( cend.time() + clearance ) == 0 ) { ++cend; } // if a gap is found, and it is big enough for at // least one Breakpoint, then include the // onset of the Partial: if ( cbeg != pshort.end() ) { cbeg = pshort.begin(); } return std::make_pair( cbeg, cend ); }
// --------------------------------------------------------------------------- // harmonify // --------------------------------------------------------------------------- //! Apply the reference envelope to a Partial. //! //! \pre The Partial p must be labeled with its harmonic number. // void Harmonifier::harmonify( Partial & p ) const { // compute absolute magnitude thresholds: static const double FadeRangeDB = 10; const double BeginFade = std::pow( 10., 0.05 * (_freqFixThresholdDb+FadeRangeDB) ); const double Threshold = std::pow( 10., 0.05 * _freqFixThresholdDb ); const double OneOverFadeSpan = 1. / ( BeginFade - Threshold ); double fscale = (double)p.label() / _refPartial.label(); for ( Partial::iterator it = p.begin(); it != p.end(); ++it ) { Breakpoint & bp = it.breakpoint(); if ( bp.amplitude() < BeginFade ) { // alpha is the harmonic frequency weighting: // when alpha is 1, the harmonic frequency is used, // when alpha is 0, the breakpoint frequency is // unmodified. double alpha = std::min( ( BeginFade - bp.amplitude() ) * OneOverFadeSpan, 1. ); // alpha is scaled by the weigthing envelope alpha *= _weight->valueAt( it.time() ); double fRef = _refPartial.frequencyAt( it.time() ); bp.setFrequency( ( alpha * ( fRef * fscale ) ) + ( (1 - alpha) * bp.frequency() ) ); } } }
// --------------------------------------------------------------------------- // BandwidthSetter function call operator // --------------------------------------------------------------------------- // Set the bandwidth of the specified Partial according to // an envelope representing a time-varying bandwidth value. // void BandwidthSetter::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setBandwidth( env->valueAt( pos.time() ) ); } }
// --------------------------------------------------------------------------- // AmplitudeScaler function call operator // --------------------------------------------------------------------------- // Scale the amplitude of the specified Partial according to // an envelope representing a time-varying amplitude scale value. // void AmplitudeScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setAmplitude( pos.breakpoint().amplitude() * env->valueAt( pos.time() ) ); } }
// --------------------------------------------------------------------------- // FrequencyScaler function call operator // --------------------------------------------------------------------------- // Scale the frequency of the specified Partial according to // an envelope representing a time-varying frequency scale value. // void FrequencyScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { pos.breakpoint().setFrequency( pos.breakpoint().frequency() * env->valueAt( pos.time() ) ); } }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // PitchShifter function call operator // --------------------------------------------------------------------------- // Shift the pitch of the specified Partial according to // the given pitch envelope. The pitch envelope is assumed to have // units of cents (1/100 of a halfstep). // void PitchShifter::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { // compute frequency scale: double scale = std::pow( 2., ( 0.01 * env->valueAt( pos.time() ) ) / 12. ); pos.breakpoint().setFrequency( pos.breakpoint().frequency() * scale ); } }
// --------------------------------------------------------------------------- // 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 ); }
// --------------------------------------------------------------------------- // 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 ); }
// --------------------------------------------------------------------------- // TimeShifter function call operator // --------------------------------------------------------------------------- // Shift the time of all the Breakpoints in a Partial by a constant amount. // void TimeShifter::operator()( Partial & p ) const { // Since the Breakpoint times are immutable, the only way to // shift the Partial in time is to construct a new Partial and // assign it to the argument p. Partial result; result.setLabel( p.label() ); for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { result.insert( pos.time() + offset, pos.breakpoint() ); } p = result; }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // NoiseRatioScaler function call operator // --------------------------------------------------------------------------- // Scale the relative noise content of the specified Partial according // to an envelope representing a (time-varying) noise energy // scale value. // void NoiseRatioScaler::operator()( Partial & p ) const { for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) { // compute new bandwidth value: double bw = pos.breakpoint().bandwidth(); if ( bw < 1. ) { double ratio = bw / (1. - bw); ratio *= env->valueAt( pos.time() ); bw = ratio / ( 1. + ratio ); } else { bw = 1.; } pos.breakpoint().setBandwidth( bw ); } }
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; }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // 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 ); } }
// --------------------------------------------------------------------------- // 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 ); }
// ----------- 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; } }
// --------------------------------------------------------------------------- // 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() ); } }
// ----------- 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_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; } }
// --------------------------------------------------------------------------- // distillOne // --------------------------------------------------------------------------- // Distill a list of Partials having a common label // into a single Partial with that label, and append it // to the distilled collection. If an empty list of Partials // is passed, then an empty Partial having the specified // label is appended. // void Distiller::distillOne( PartialList & partials, Partial::label_type label, PartialList & distilled ) { debugger << "Distiller found " << partials.size() << " Partials labeled " << label << endl; Partial newp; newp.setLabel( label ); if ( partials.size() == 1 ) { // trivial if there is only one partial to distill newp = partials.front(); } else if ( partials.size() > 0 ) // it will be an empty Partial otherwise { // sort Partials by duration, longer // Partials will be prefered: partials.sort( distillSorter ); // keep the longest Partial: PartialList::iterator it = partials.begin(); newp = *it; fadeInAndOut( newp, _fadeTime ); // Iterate over remaining Partials: for ( ++it; it != partials.end(); ++it ) { fadeInAndOut( *it, _fadeTime ); std::pair< Partial::iterator, Partial::iterator > range = findContribution( *it, newp, _fadeTime, _gapTime ); Partial::iterator cb = range.first, ce = range.second; // There can only be one contributing regions because // (and only because) the partials are sorted by length // first! // merge Breakpoints into the new Partial, if // there are any that contribute, otherwise // just absorb the current Partial as noise: if ( cb != ce ) { // absorb the non-contributing part: if ( ce != it->end() ) { Partial absorbMe( --Partial::iterator(ce), it->end() ); // shouldn't this just be ce? newp.absorb( absorbMe ); // There cannot be a non-contributing part // at the beginning of the Partial too, because // we always retain the beginning of the Partial. // If findContribution were changed, then // we might also want to absorb the beginning. // // Partial absorbMeToo( it->begin(), cb ); // newp.absorb( absorbMeToo ); } // merge the contributing part: merge( cb, ce, newp, _fadeTime, _gapTime ); } else { // no contribution, absorb the whole thing: newp.absorb( *it ); } } } // take the null Breakpoints off the ends // should check whether this is appropriate? if ( 0 == newp.begin().breakpoint().amplitude() ) { newp.erase( newp.begin() ); } Partial::iterator lastBpPos = --(Partial::iterator(newp.end())); if ( 0 == lastBpPos.breakpoint().amplitude() ) { newp.erase( lastBpPos ); } // insert the new Partial in the distilled collection // in label order: distilled.insert( std::lower_bound( distilled.begin(), distilled.end(), newp, PartialUtils::compareLabelLess() ), newp ); }
// --------------------------------------------------------------------------- // 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; }