// ---------------------------------------------------------------------------
//	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;
    */
}
Exemple #7
0
// ----------- 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;
    }
}
Exemple #8
0
// ----------- 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;
    }
}
Exemple #9
0
// ----------- 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() );
	}
}
Exemple #11
0
// ---------------------------------------------------------------------------
//	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;
}