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