// --------------------------------------------------------------------------- // 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() ); } }
// --------------------------------------------------------------------------- // 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 ) ); } }
// --------------------------------------------------------------------------- // 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; }
// --------------------------------------------------------------------------- // getPartial // --------------------------------------------------------------------------- // Convert any FileIOExceptions into ImportExceptions, so that clients can // reasonably expect to catch only ImportExceptions. // static void getPartial( std::istream & s, PartialList & partials, double bweCutoff ) { try { // read the Track header: TrackOnDisk tkHeader; readTrackHeader( s, tkHeader ); // create a Partial: Partial p; p.setLabel( tkHeader.label ); // keep running phase and time for Breakpoint construction: double phase = tkHeader.initialPhase; // convert time to seconds and offset by a millisecond to // allow for implied onset (Lemur analysis data was shifted // such that the earliest Partial starts at 0). double time = tkHeader.startTime * 0.001; // use this to compute phases: double prevTtnSec = 0.; // loop: read Peak, create Breakpoint, add to Partial: for ( int i = 0; i < tkHeader.numPeaks; ++i ) { // read Peak: PeakOnDisk pkData; readPeakData( s, pkData ); double frequency = pkData.frequency; double amplitude = pkData.magnitude; double bandwidth = std::min( pkData.bandwidth, 1.f ); // fix bandwidth: // Lemur used a cutoff frequency, below which // bandwidth was ignored; Loris does not, so // toss out that bogus bandwidth. if ( frequency < bweCutoff ) { amplitude *= std::sqrt(1. - bandwidth); bandwidth = 0.; } // otherwise, adjust the bandwidth value // to account for the difference in noise // scaling between Lemur and Loris; this // mess doubles the noise modulation index // without changing the sine modulation index, // see Oscillator::modulate(). else { amplitude *= std::sqrt( 1. + (3. * bandwidth) ); bandwidth = (4. * bandwidth) / ( 1. + (3. * bandwidth) ); } // update phase based on _this_ pkData's interpolated freq: phase +=2. * Pi * prevTtnSec * pkData.interpolatedFrequency; phase = std::fmod( phase, 2. * Pi ); // create Breakpoint: Breakpoint bp( frequency, amplitude, bandwidth, phase ); // insert in Partial: p.insert( time, bp ); // update time: prevTtnSec = pkData.ttn * 0.001; time += prevTtnSec; } if ( p.duration() > 0. ) { partials.push_back( p ); } /* else { debugger << "import rejecting a Partial of zero duration (" << tkHeader.numPeaks << " peaks read)" << endl; } */ } catch( FileIOException & ex ) { ex.append( "Failed to import a partial from a Lemur file." ); throw; } }
int main( void ) { cout << "Loris C++ API test, using " << LORIS_VERSION_STR << endl; cout << "Kelly Fitz 2006" << endl << endl; cout << "Generates a simple linear morph between a " << endl; cout << "clarinet and a flute using the C++ library." << endl << endl; std::string path(""); if ( std::getenv("srcdir") ) { path = std::getenv("srcdir"); path = path + "/"; } try { // analyze clarinet tone cout << "importing clarinet samples" << endl; Analyzer a( 415*.8, 415*1.6 ); AiffFile f( path + "clarinet.aiff" ); // analyze the clarinet cout << "analyzing clarinet 4G#" << endl; a.analyze( f.samples(), f.sampleRate() ); PartialList clar = a.partials(); // channelize and distill cout << "distilling" << endl; FrequencyReference clarRef( clar.begin(), clar.end(), 415*.8, 415*1.2, 50 ); Channelizer::channelize( clar.begin(), clar.end(), clarRef , 1 ); Distiller::distill( clar, 0.001 ); // test SDIF import and export cout << "exporting " << clar.size() << " partials to SDIF file" << endl; SdifFile::Export( "clarinet.ctest.sdif", clar ); cout << "importing from SDIF file" << endl; SdifFile ip("clarinet.ctest.sdif"); if ( clar.size() != ip.partials().size() ) { throw std::runtime_error( "SDIF import yields a different number of partials than were exported!" ); } clar = ip.partials(); // shift pitch of clarinet partials cout << "shifting pitch of " << clar.size() << " Partials by 600 cents" << endl; PartialUtils::shiftPitch( clar.begin(), clar.end(), -600 ); // check clarinet synthesis cout << "checking clarinet synthesis" << endl; AiffFile clarout( clar.begin(), clar.end(), f.sampleRate() ); clarout.write( "clarOK.ctest.aiff" ); // analyze flute tone cout << "importing flute samples" << endl; f = AiffFile( path + "flute.aiff" ); // analyze the flute cout << "analyzing flute 4D" << endl; a = Analyzer( 270 ); #if defined(ESTIMATE_F0) && ESTIMATE_F0 cout << "Analyzer will build a fundamental frequency estimate for the flute" << endl; a.buildFundamentalEnv( 270, 310 ); #endif #if defined(ESTIMATE_AMP) && ESTIMATE_AMP a.buildAmpEnv( true ); #endif a.analyze( f.samples(), f.sampleRate() ); PartialList flut = a.partials(); // channelize and distill cout << "distilling" << endl; #if defined(ESTIMATE_F0) && ESTIMATE_F0 const LinearEnvelope & flutRef = a.fundamentalEnv(); double est_time = flutRef.begin()->first; cout << "flute fundamental envelope starts at time " << est_time << endl; while ( est_time < 2 ) { cout << "flute fundamental estimate at time " << est_time << " is " << flutRef.valueAt( est_time ) << endl; est_time += 0.35; } #else FrequencyReference flutRef( flut.begin(), flut.end(), 291*.8, 291*1.2, 50 ); #endif Channelizer::channelize( flut.begin(), flut.end(), flutRef, 1 ); Distiller::distill( flut, 0.001 ); cout << "obtained " << flut.size() << " distilled flute Partials" << endl; #if defined(ESTIMATE_F0) && ESTIMATE_F0 #if defined(ESTIMATE_AMP) && ESTIMATE_AMP // generate a sinusoid that tracks the fundamental // and amplitude envelopes obtained during analysis cout << "synthesizing sinusoid from flute amp and fundamental estimates" << endl; Partial boo; LinearEnvelope fund = a.fundamentalEnv(); LinearEnvelope::iterator it; for ( it = fund.begin(); it != fund.end(); ++it ) { Breakpoint bp( it->second, a.ampEnv().valueAt( it->first ), 0, 0 ); boo.insert( it->first, bp ); } PartialList boolist; boolist.push_back( boo ); AiffFile boofile( boolist.begin(), boolist.end(), 44100 ); boofile.write( "flutefundamental.aiff" ); #endif #endif cout << "exporting " << flut.size() << " partials to SDIF file" << endl; SdifFile::Export( "flute.ctest.sdif", flut ); // check flute synthesis: cout << "checking flute synthesis" << endl; AiffFile flutout( flut.begin(), flut.end(), f.sampleRate() ); flutout.write( "flutOK.ctest.aiff" ); // perform temporal dilation double flute_times[] = { 0.4, 1. }; double clar_times[] = { 0.2, 1. }; double tgt_times[] = { 0.3, 1.2 }; cout << "dilating sounds to match (" << tgt_times[0] << ", " << tgt_times[1] << ")" << endl; cout << "flute times: (" << flute_times[0] << ", " << flute_times[1] << ")" << endl; Dilator::dilate( flut.begin(), flut.end() , flute_times, flute_times+2, tgt_times ); cout << "clarinet times: (" << clar_times[0] << ", " << clar_times[1] << ")" << endl; Dilator::dilate( clar.begin(), clar.end(), clar_times, clar_times+2, tgt_times ); // perform morph cout << "morphing flute and clarinet" << endl; BreakpointEnvelope mf; mf.insertBreakpoint( 0.6, 0 ); mf.insertBreakpoint( 2, 1 ); Morpher m( mf ); m.setMinBreakpointGap( 0.002 ); m.setSourceReferencePartial( clar, 3 ); m.setTargetReferencePartial( flut, 1 ); m.morph( clar.begin(), clar.end(), flut.begin(), flut.end() ); // synthesize and export samples cout << "synthesizing " << m.partials().size() << " morphed partials" << endl; AiffFile morphout( m.partials().begin(), m.partials().end(), f.sampleRate() ); morphout.write( "morph.ctest.aiff" ); } catch( Exception & ex ) { cout << "Caught Loris exception: " << ex.what() << endl; return 1; } catch( std::exception & ex ) { cout << "Caught std C++ exception: " << ex.what() << endl; return 1; } cout << "Done, bye." << endl; return 0; }
// --------------------------------------------------------------------------- // buildPartials // --------------------------------------------------------------------------- // Append spectral peaks, extracted from a reassigned time-frequency // spectrum, to eligible Partials, where possible. Peaks that cannot // be used to extend eliglble Partials spawn new Partials. // // This is similar to the basic MQ partial formation strategy, except that // before matching, all frequencies are normalized by the value of the // warping envelope at the time of the current frame. This means that // the frequency envelopes of all the Partials are warped, and need to // be un-normalized by calling finishBuilding at the end of the building // process. // void PartialBuilder::buildPartials( Peaks & peaks, double frameTime ) { mNewlyEligible.clear(); unsigned int matchCount = 0; // for debugging // frequency-sort the spectral peaks: // (the eligible partials are always sorted by // increasing frequency if we always sort the // peaks this way) std::sort( peaks.begin(), peaks.end(), SpectralPeak::sort_increasing_freq ); PartialPtrs::iterator eligible = mEligiblePartials.begin(); for ( Peaks::iterator bpIter = peaks.begin(); bpIter != peaks.end(); ++bpIter ) { //const Breakpoint & bp = bpIter->breakpoint; const double peakTime = frameTime + bpIter->time(); // find the Partial that is nearest in frequency to the Peak: PartialPtrs::iterator nextEligible = eligible; if ( eligible != mEligiblePartials.end() && end_frequency( **eligible ) < bpIter->frequency() ) { ++nextEligible; while ( nextEligible != mEligiblePartials.end() && end_frequency( **nextEligible ) < bpIter->frequency() ) { ++nextEligible; ++eligible; } if ( nextEligible != mEligiblePartials.end() && better_match( **nextEligible, **eligible, *bpIter ) ) { eligible = nextEligible; } } // INVARIANT: // // eligible is the position of the nearest (in frequency) // eligible Partial (pointer) or it is mEligiblePartials.end(). // // nextEligible is the eligible Partial with frequency // greater than bp, or it is mEligiblePartials.end(). #if defined(Debug_Loris) && Debug_Loris /* if ( nextEligible != mEligiblePartials.end() ) { debugger << matchFrequency << "( " << end_frequency( **eligible ) << ", " << end_frequency( **nextEligible ) << ")" << endl; } */ #endif // create a new Partial if there is no eligible Partial, // or the frequency difference to the eligible Partial is // too great, or the next peak is a better match for the // eligible Partial, otherwise add this peak to the eligible // Partial: Peaks::iterator nextPeak = //Peaks::iterator( bpIter ); ++nextPeak; ++Peaks::iterator( bpIter ); // some compilers choke on this? // decide whether this match should be made: // - can only make the match if eligible is not the end of the list // - the match is only good if it is close enough in frequency // - even if the match is good, only match if the next one is not better bool makeMatch = false; if ( eligible != mEligiblePartials.end() ) { bool matchIsGood = mFreqDrift > std::fabs( end_frequency( **eligible ) - bpIter->frequency() ); if ( matchIsGood ) { bool nextIsBetter = ( nextPeak != peaks.end() && better_match( **eligible, *nextPeak, *bpIter ) ); if ( ! nextIsBetter ) { makeMatch = true; } } } Breakpoint bp = bpIter->createBreakpoint(); if ( makeMatch ) { // invariant: // if makeMatch is true, then eligible is the position of a valid Partial (*eligible)->insert( peakTime, bp ); mNewlyEligible.push_back( *eligible ); ++matchCount; } else { Partial p; p.insert( peakTime, bp ); mCollectedPartials.push_back( p ); mNewlyEligible.push_back( & mCollectedPartials.back() ); } // update eligible, nextEligible is the eligible Partial // with frequency greater than bp, or it is mEligiblePartials.end(): eligible = nextEligible; } mEligiblePartials = mNewlyEligible; /* debugger << "PartialBuilder::buildPartials: matched " << matchCount << endl; debugger << "PartialBuilder::buildPartials: " << mNewlyEligible.size() << " newly eligible partials" << endl; */ }
// ----------- 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; } }
// --------------------------------------------------------------------------- // merge (STATIC) // --------------------------------------------------------------------------- // Merge the Breakpoints in the specified iterator range into the // distilled Partial. The beginning of the range may overlap, and // will replace, some non-zero-amplitude portion of the distilled // Partial. Assume that there is no such overlap at the end of the // range (could check), because findContribution only leaves overlap // at the beginning of the range. // static void merge( Partial::const_iterator beg, Partial::const_iterator end, Partial & destPartial, double fadeTime, double gapTime = 0. ) { // absorb energy in distilled Partial that overlaps the // range to merge: Partial toMerge( beg, end ); toMerge.absorb( destPartial ); fadeInAndOut( toMerge, fadeTime ); // find the first Breakpoint in destPartial that is after the // range of merged Breakpoints, plus the required gap: Partial::iterator removeEnd = destPartial.findAfter( toMerge.endTime() + gapTime ); // if this Breakpoint has non-zero amplitude, need to leave time // for a fade in: while ( removeEnd != destPartial.end() && removeEnd.breakpoint().amplitude() != 0 && removeEnd.time() < toMerge.endTime() + gapTime + fadeTime ) { ++removeEnd; } // find the first Breakpoint in the destination Partial that needs // to be removed because it is in the overlap region: Partial::iterator removeBegin = destPartial.findAfter( toMerge.startTime() - gapTime ); // if beforeMerge has non-zero amplitude, need to leave time // for a fade out: if ( removeBegin != destPartial.begin() ) { Partial::iterator beforeMerge = --Partial::iterator(removeBegin); while ( removeBegin != destPartial.begin() && beforeMerge.breakpoint().amplitude() != 0 && beforeMerge.time() > toMerge.startTime() - gapTime - fadeTime ) { --removeBegin; if ( beforeMerge != destPartial.begin() ) { --beforeMerge; } } } // remove the Breakpoints in the merge range from destPartial: double rbt = (removeBegin != destPartial.end())?(removeBegin.time()):(destPartial.endTime()); double ret = (removeEnd != destPartial.end())?(removeEnd.time()):(destPartial.endTime()); Assert( rbt <= ret ); destPartial.erase( removeBegin, removeEnd ); // how about doing the fades here instead? // fade in if necessary: if ( removeEnd != destPartial.end() && removeEnd.breakpoint().amplitude() != 0 ) { Assert( removeEnd.time() - fadeTime > toMerge.endTime() ); // update removeEnd so that we don't remove this // null we are inserting: destPartial.insert( removeEnd.time() - fadeTime, BreakpointUtils::makeNullBefore( removeEnd.breakpoint(), fadeTime ) ); } if ( removeEnd != destPartial.begin() ) { Partial::iterator beforeMerge = --Partial::iterator(removeEnd); if ( beforeMerge.breakpoint().amplitude() > 0 ) { Assert( beforeMerge.time() + fadeTime < toMerge.startTime() ); destPartial.insert( beforeMerge.time() + fadeTime, BreakpointUtils::makeNullAfter( beforeMerge.breakpoint(), fadeTime ) ); } } // insert the Breakpoints in the range: for ( Partial::const_iterator insert = toMerge.begin(); insert != toMerge.end(); ++insert ) { destPartial.insert( insert.time(), insert.breakpoint() ); } }
// --------------------------------------------------------------------------- // dilate // --------------------------------------------------------------------------- //! Replace the Partial envelope with a new envelope having the //! same Breakpoints at times computed to align temporal features //! in the sorted sequence of initial time points with their //! counterparts the sorted sequence of target time points. //! //! Depending on the specification of initial and target time //! points, the dilated Partial may have Breakpoints at times //! less than 0, even if the original Partial did not. //! //! It is possible to have duplicate time points in either sequence. //! Duplicate initial time points result in very localized stretching. //! Duplicate target time points result in very localized compression. //! //! If all initial time points are greater than 0, then an implicit //! time point at 0 is assumed in both initial and target sequences, //! so the onset of a sound can be stretched without explcitly specifying a //! zero point in each vector. (This seems most intuitive, and only looks //! like an inconsistency if clients are using negative time points in //! their Dilator, or Partials having Breakpoints before time 0, both //! of which are probably unusual circumstances.) //! //! \param p is the Partial to dilate. // void Dilator::dilate( Partial & p ) const { debugger << "dilating Partial having " << p.numBreakpoints() << " Breakpoints" << endl; // sanity check: Assert( _initial.size() == _target.size() ); // don't dilate if there's no time points, or no Breakpoints: if ( 0 == _initial.size() || 0 == p.numBreakpoints() ) { return; } // create the new Partial: Partial newp; newp.setLabel( p.label() ); // timepoint index: int idx = 0; for ( Partial::const_iterator iter = p.begin(); iter != p.end(); ++iter ) { // find the first initial time point later // than the currentTime: double currentTime = iter.time(); idx = std::distance( _initial.begin(), std::lower_bound( _initial.begin(), _initial.end(), currentTime ) ); Assert( idx == _initial.size() || currentTime <= _initial[idx] ); // compute a new time for the Breakpoint at pIter: double newtime = 0; if ( idx == 0 ) { // all time points in _initial are later than // the currentTime; stretch if no zero time // point has been specified, otherwise, shift: if ( _initial[idx] != 0. ) newtime = currentTime * _target[idx] / _initial[idx]; else newtime = _target[idx] + (currentTime - _initial[idx]); } else if ( idx == _initial.size() ) { // all time points in _initial are earlier than // the currentTime; shift: // // note: size is already known to be > 0, so // idx-1 is safe newtime = _target[idx-1] + (currentTime - _initial[idx-1]); } else { // currentTime is between the time points at idx and // idx-1 in _initial; shift and stretch: // // note: size is already known to be > 0, so // idx-1 is safe Assert( _initial[idx-1] < _initial[idx] ); // currentTime can't wind up // between two equal times double stretch = (_target[idx] - _target[idx-1]) / (_initial[idx] - _initial[idx-1]); newtime = _target[idx-1] + ((currentTime - _initial[idx-1]) * stretch); } // add a Breakpoint at the computed time: newp.insert( newtime, iter.breakpoint() ); } // new Breakpoints need to be added to the Partial at times corresponding // to all target time points that are after the first Breakpoint and // before the last, otherwise, Partials may be briefly out of tune with // each other, since our Breakpoints are non-uniformly distributed in time: for ( idx = 0; idx < _initial.size(); ++ idx ) { if ( _initial[idx] <= p.startTime() ) { continue; } else if ( _initial[idx] >= p.endTime() ) { break; } else { newp.insert( _target[idx], Breakpoint( p.frequencyAt(_initial[idx]), p.amplitudeAt(_initial[idx]), p.bandwidthAt(_initial[idx]), p.phaseAt(_initial[idx]) ) ); } } // store the new Partial: p = newp; }