//------------------------------------------------------------------------------
// File: TimelineTest.cpp
//
// Desc: DirectShow sample code - creates two video tracks, using a 
//       transition from the A track to the B track, and then back again.  
//       It demonstrates how to add a transition to the timeline using a 
//       CLSID.
//
// Copyright (c) 1998 - 2000, Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


//----------------------------------------------------------------------------
// This example creates 2 (video) tracks with a transition from the A track to 
// the B track and then from B back to A. It also crossfades two audio tracks 
// from A to B.  This sample also demonstrates how to add a transition by CLSID 
// to the timeline and how to vary volume envelope properties by the 
// property setter.
//
// Note: Error checking is handled mostly with ASSERTs in order to keep
// the sample as uncluttered as possible.  In some cases where several
// related function calls are made together, the HRESULT value returned from
// the functions will be bitwise OR'ed with other HRESULT values.  This value
// will eventually be tested for failure using the FAILED macro.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include <streams.h>
#include <atlbase.h>
#include <qedit.h>
#include <dxutil.h>

#ifdef STRICT
#undef STRICT
#endif
#include <dxutil.cpp>

//
// Conditional compilation flags to enable/disable effects
//
//#define CUTS_ONLY

// define this to show an example of calling ConnectFrontEnd( ) 
// a second time on the render engine and having it rerender
// the graph
#define DO_RECONNECT

// define this to do an audio crossfade
#define DO_CROSSFADE

// define this to only render a portion of the full timeline
#define DO_RENDERRANGE

// define this to enable transitions
#define DO_TRANSITION

//
// Global data
//

// Use bitmaps and audio files known to install with the DirectX 8 SDK
WCHAR * wszVideo1Name = L"dx5_logo.bmp";
WCHAR * wszVideo2Name = L"env3.bmp";
WCHAR * wszAudio1Name = L"track2.mp3";
WCHAR * wszAudio2Name = L"track3.mp3";
WCHAR * wszTitle = L"Timeline Test Sample";

//
// Function prototypes
//
int TimelineTest( );
void Err(TCHAR *szErr);


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    // init the ole32 libraries
    //
    CoInitialize( NULL );

    //
    // NOTE: There is no user interaction with this sample.
    // It will run to completion and exit.
    //
    TimelineTest();

    CoUninitialize( );

    return 0;
}


void Err(TCHAR *szErr)
{
    MessageBox(NULL, szErr, "Error", MB_ICONEXCLAMATION);
}


int TimelineTest( )
{
    USES_CONVERSION;
    HRESULT hr;

    // use the ATL libraries to do automatic reference counting on pointers
    //
    CComPtr< IRenderEngine > pRenderEngine;
    CComPtr< IGraphBuilder > pGraph;
    CComPtr< IVideoWindow > pVidWindow;
    CComPtr< IMediaEvent > pEvent;
    CComPtr< IAMTimeline > pTimeline;
    CComPtr< IAMTimelineObj > pVideoGroupObj;
    CComPtr< IAMTimelineObj > pAudioGroupObj;
    CComPtr< IMediaControl > pControl;
    CComPtr< IMediaSeeking > pSeeking;

    //--------------------------------------------
    // make the timeline
    //--------------------------------------------

    hr = CoCreateInstance(
                         CLSID_AMTimeline, 
                         NULL, 
                         CLSCTX_INPROC_SERVER, 
                         IID_IAMTimeline, 
                         (void**) &pTimeline 
                         );

    if(FAILED( hr )) {
        Err(_T("Could not create timeline"));
        return hr;
    }

    //--------------------------------------------
    // make the root group/composition
    //--------------------------------------------

    hr = pTimeline->CreateEmptyNode( &pVideoGroupObj, TIMELINE_MAJOR_TYPE_GROUP );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create empty node"));
        return hr;
    }

    CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pVideoGroup( pVideoGroupObj );
    CMediaType VideoGroupType;

    // all we set is the major type. The group will automatically use other defaults
    VideoGroupType.SetType( &MEDIATYPE_Video );
    hr = pVideoGroup->SetMediaType( &VideoGroupType );
    if(FAILED( hr )) 
    {
        Err(_T("Could not set media type"));
        return hr;
    }

    //--------------------------------------------
    // add the group to the timeline
    //--------------------------------------------

    hr = pTimeline->AddGroup( pVideoGroupObj );
    if(FAILED( hr )) 
    {
        Err(_T("Could not add video group"));
        return hr;
    }

    //--------------------------------------------
    // make a track
    //--------------------------------------------

    CComPtr< IAMTimelineObj > pTrack1Obj;
    hr = pTimeline->CreateEmptyNode( &pTrack1Obj, TIMELINE_MAJOR_TYPE_TRACK );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create empty track"));
        return hr;
    }

    //--------------------------------------------
    // tell the composition about the track
    //--------------------------------------------

    CComQIPtr< IAMTimelineComp, &IID_IAMTimelineComp > pRootComp( pVideoGroupObj );
    hr = pRootComp->VTrackInsBefore( pTrack1Obj, -1 );
    if(FAILED( hr )) 
    {
        Err(_T("Could not insert track"));
        return hr;
    }

    //--------------------------------------------
    // create a source from 0 to 8 seconds
    //--------------------------------------------

    REFERENCE_TIME TLStart = 0 * UNITS;
    REFERENCE_TIME TLStop = 8 * UNITS;

    // you can set these if you want to other numbers, and the video will 
    // speed up or slow down if the duration isn't the same as the timeline's.
    REFERENCE_TIME MediaStart = 0 * UNITS;
    REFERENCE_TIME MediaStop = 8 * UNITS;
    WCHAR pClipname[256];
    const TCHAR * tBasePath = DXUtil_GetDXSDKMediaPath( );
    wcscpy( pClipname, T2W( tBasePath ) );
    wcscat( pClipname, L"\\" );
    wcscat( pClipname, wszVideo1Name );

    // create the timeline source
    //
    CComPtr<IAMTimelineObj> pSource1Obj;
    hr = pTimeline->CreateEmptyNode( &pSource1Obj, TIMELINE_MAJOR_TYPE_SOURCE );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create the timeline source"));
        return hr;
    }

    // set up source right
    //
    hr = pSource1Obj->SetStartStop( TLStart, TLStop );
    CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource1Src( pSource1Obj );
    hr |= pSource1Src->SetMediaTimes( MediaStart, MediaStop );
    hr |= pSource1Src->SetMediaName( pClipname );
    if(FAILED( hr )) 
    {
        Err(_T("Could not configure media source"));
        return E_FAIL;
    }

    //--------------------------------------------
    // tell the track about the source
    //--------------------------------------------

    CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack1( pTrack1Obj );
    hr = pTrack1->SrcAdd( pSource1Obj );
    if(FAILED( hr )) 
    {
        Err(_T("Could not add source to track"));
        return hr;
    }

    //--------------------------------------------
    // make another track 
    //--------------------------------------------

    CComPtr< IAMTimelineObj > pTrack2Obj;
    hr = pTimeline->CreateEmptyNode( &pTrack2Obj, TIMELINE_MAJOR_TYPE_TRACK );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create second track"));
        return hr;
    }

    //--------------------------------------------
    // tell the composition about the track
    //--------------------------------------------

    hr = pRootComp->VTrackInsBefore( pTrack2Obj, -1 );
    if(FAILED( hr )) 
    {
        Err(_T("Could not insert second track"));
        return hr;
    }

    //--------------------------------------------
    // create a source for the 2nd track
    //--------------------------------------------

    TLStart = 0 * UNITS;
    TLStop = 8 * UNITS;
    MediaStart = 0 * UNITS;
    MediaStop = 8 * UNITS;
    wcscpy( pClipname, T2W( tBasePath ) );
    wcscat( pClipname, L"\\" );
    wcscat( pClipname, wszVideo2Name );

    // create the timeline source
    //
    CComPtr<IAMTimelineObj> pSource2Obj;
    hr = pTimeline->CreateEmptyNode( &pSource2Obj, TIMELINE_MAJOR_TYPE_SOURCE );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create the second timeline source"));
        return hr;
    }

    // set up source right
    //
    hr = pSource2Obj->SetStartStop( TLStart, TLStop );
    CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource2Src( pSource2Obj );
    hr |= pSource2Src->SetMediaTimes( MediaStart, MediaStop );
    hr |= pSource2Src->SetMediaName( pClipname );
    if(FAILED( hr )) 
    {
        Err(_T("Could not configure second media source"));
        return E_FAIL;
    }

    //--------------------------------------------
    // tell the track about the source
    //--------------------------------------------

    CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack2( pTrack2Obj );
    hr = pTrack2->SrcAdd( pSource2Obj );
    if(FAILED( hr )) 
    {
        Err(_T("Could not add second track"));
        return hr;
    }

    CComQIPtr< IAMTimelineTransable, &IID_IAMTimelineTransable > pTransable( pTrack2 );
    

#ifdef DO_TRANSITION

    //---------------------------------------------
    // create an transition on the track from A 2 B
    //---------------------------------------------

    REFERENCE_TIME TransStart = 0 * UNITS;
    REFERENCE_TIME TransStop = 4 * UNITS;

    // create the timeline effect
    //
    CComPtr<IAMTimelineObj> pTrackTransObj;
    hr = pTimeline->CreateEmptyNode(
                                   &pTrackTransObj,
                                   TIMELINE_MAJOR_TYPE_TRANSITION );
    if(FAILED( hr )) 
    {
        Err(_T("Could not create transition effect"));
        return hr;
    }

    //--------------------------------------------
    // set up filter right
    //--------------------------------------------

    // we set the CLSID of the DXT to use instead of a pointer to the
    // actual object. We let the DXT have it's default properties.
    //
    hr = pTrackTransObj->SetSubObjectGUID( CLSID_DxtJpeg );
    hr |= pTrackTransObj->SetStartStop( TransStart, TransStop );
    CComQIPtr< IAMTimelineTrans, &IID_IAMTimelineTrans > pTrackTrans( pTrackTransObj );
    hr |= pTransable->TransAdd( pTrackTransObj );
    if(FAILED( hr )) 
    {
        Err(_T("Could not configure transition object"));
        return E_FAIL;
    }

#ifdef CUTS_ONLY 
    //---------------------------------------------
    // turn the transition into a cut by doing this
    //---------------------------------------------

    hr = pTrackTrans->SetCutsOnly( TRUE );
    if(FAILED( hr )) 
    {
        Err(_T("Could not SetCutsOnly to TRUE"));
        return hr;
    }

#endif  // CUTS_ONLY

    //---------------------------------------------
    // create an transition on the track from B 2 A
    //---------------------------------------------

    TransStart = 4 * UNITS;
    TransStop = 8 * UNITS;

    // create the timeline effect
    //
    pTrackTransObj.Release( );
    hr = pTimeline->CreateEmptyNode(
                                   &pTrackTransObj,
                                   TIMELINE_MAJOR_TYPE_TRANSITION );
    ASSERT( !FAILED( hr ) );

    // set up filter right
    //
    hr = pTrackTransObj->SetSubObjectGUID( CLSID_DxtJpeg );            
    hr |= pTrackTransObj->SetStartStop( TransStart, TransStop );
    pTrackTrans = pTrackTransObj;
    hr |= pTrackTrans->SetSwapInputs( TRUE );
    hr |= pTransable->TransAdd( pTrackTransObj );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // set a property on the transition
    //--------------------------------------------

    CComPtr< IPropertySetter > pTransSetter;
    pTransSetter.CoCreateInstance( CLSID_PropertySetter );
    DEXTER_PARAM Param;
    CComBSTR ParamName( "MaskNum" ); // the property name
    Param.Name = ParamName;
    Param.nValues = 1; // how many values we want to set
    DEXTER_VALUE Value;
    memset( &Value, 0, sizeof( Value ) );
    VariantClear( &Value.v );
    V_I4( &Value.v ) = 128; // mask number 128
    V_VT( &Value.v ) = VT_I4; // integer
    hr = pTransSetter->AddProp( Param, &Value );
    hr |= pTrackTransObj->SetPropertySetter( pTransSetter );
    ASSERT( !FAILED( hr ) );
    // pTransSetter will be auto-freed by COM

#endif  // DO_TRANSITION

#ifdef CUTS_ONLY 
    //---------------------------------------------
    // turn the transition into a cut by doing this
    //---------------------------------------------

    hr = pTrackTrans->SetCutsOnly( TRUE );
    ASSERT( !FAILED( hr ) );
#endif

    //--------------------------------------------
    // make the root audio group/composition
    //--------------------------------------------

    hr = pTimeline->CreateEmptyNode( &pAudioGroupObj, TIMELINE_MAJOR_TYPE_GROUP );
    ASSERT( !FAILED( hr ) );

    CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pAudioGroup( pAudioGroupObj );
    CMediaType AudioGroupType;
    // all we set is the major type. The group will automatically use other defaults
    AudioGroupType.SetType( &MEDIATYPE_Audio );
    hr = pAudioGroup->SetMediaType( &AudioGroupType );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // add the group to the timeline
    //--------------------------------------------

    hr = pTimeline->AddGroup( pAudioGroupObj );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // make a track
    //--------------------------------------------

    CComPtr< IAMTimelineObj > pTrack3Obj;
    hr = pTimeline->CreateEmptyNode( &pTrack3Obj, TIMELINE_MAJOR_TYPE_TRACK );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // tell the composition about the track
    //--------------------------------------------

    CComQIPtr< IAMTimelineComp, &IID_IAMTimelineComp > pAudioComp( pAudioGroupObj );
    hr = pAudioComp->VTrackInsBefore( pTrack3Obj, -1 );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // create a source 
    //--------------------------------------------

    TLStart = 0 * UNITS;
    TLStop = 6 * UNITS;
    // you can set these if you want to other numbers, the video will speed up
    // or slow down if the duration isn't the same at the timeline's.
    MediaStart = 0 * UNITS;
    MediaStop = 6 * UNITS;
    wcscpy( pClipname, T2W( tBasePath ) );
    wcscat( pClipname, L"\\" );
    wcscat( pClipname, wszAudio1Name );

    // create the timeline source
    //
    CComPtr<IAMTimelineObj> pSource3Obj;
    hr = pTimeline->CreateEmptyNode( &pSource3Obj, TIMELINE_MAJOR_TYPE_SOURCE );
    ASSERT( !FAILED( hr ) );

    // set up source right
    //
    hr = pSource3Obj->SetStartStop( TLStart, TLStop );
    CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource3Src( pSource3Obj );
    hr |= pSource3Src->SetMediaTimes( MediaStart, MediaStop );
    hr |= pSource3Src->SetMediaName( pClipname );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // tell the track about the source
    //--------------------------------------------

    CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack3( pTrack3Obj );
    hr = pTrack3->SrcAdd( pSource3Obj );
    ASSERT( !FAILED( hr ) );

#ifdef DO_CROSSFADE
    //--------------------------------------------
    // put a volume effect on the source
    //--------------------------------------------

    TLStart = 4 * UNITS;
    TLStop = 6 * UNITS;

    CComPtr< IAMTimelineObj > pTrack3FxObj;
    hr = pTimeline->CreateEmptyNode( &pTrack3FxObj, TIMELINE_MAJOR_TYPE_EFFECT );
    ASSERT( !FAILED( hr ) );

    // set up effect right
    //
    hr = pTrack3FxObj->SetStartStop( TLStart, TLStop );
    hr |= pTrack3FxObj->SetSubObjectGUID( CLSID_AudMixer );
    ASSERT( !FAILED( hr ) );

    // add the effect
    //
    CComQIPtr< IAMTimelineEffectable , &IID_IAMTimelineEffectable > pTrack3Fable( pTrack3 );
    hr = pTrack3Fable->EffectInsBefore( pTrack3FxObj, -1 );
    ASSERT( !FAILED( hr ) );

    //------------------------------------------------
    // set the the volume envelope on the audio source
    //------------------------------------------------

    CComPtr< IPropertySetter > pVolSetter;
    pVolSetter.CoCreateInstance( CLSID_PropertySetter );
    CComBSTR VolParamName( "Vol" ); // the property name
    Param.Name = VolParamName;
    Param.nValues = 2; // how many values we want to set

    DEXTER_VALUE AudioValue[2];
    memset( &AudioValue[0], 0, sizeof( Value ) );
    VariantClear( &AudioValue[0].v );
    V_R8( &AudioValue[0].v ) = 1.0;
    V_VT( &AudioValue[0].v ) = VT_R8;
    AudioValue[0].rt = 0 * UNITS;
    memset( &AudioValue[1], 0, sizeof( Value ) );
    VariantClear( &AudioValue[1].v );
    V_R8( &AudioValue[1].v ) = 0.0;
    V_VT( &AudioValue[1].v ) = VT_R8;
    AudioValue[1].rt = 2 * UNITS;
    AudioValue[1].dwInterp = DEXTERF_INTERPOLATE;

    hr = pVolSetter->AddProp( Param, AudioValue );
    hr |= pTrack3FxObj->SetPropertySetter( pVolSetter );
    ASSERT( !FAILED( hr ) );
    pVolSetter.Release( );
#endif  // DO_CROSSFADE

    //--------------------------------------------
    // make another track 
    //--------------------------------------------

    CComPtr< IAMTimelineObj > pTrack4Obj;
    hr = pTimeline->CreateEmptyNode( &pTrack4Obj, TIMELINE_MAJOR_TYPE_TRACK );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // tell the composition about the track
    //--------------------------------------------

    hr = pAudioComp->VTrackInsBefore( pTrack4Obj, -1 );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // create a source for the 2nd track
    //--------------------------------------------

    TLStart = 4 * UNITS;
    TLStop = 8 * UNITS;
    MediaStart = 0 * UNITS;
    MediaStop = 4 * UNITS;
    wcscpy( pClipname, T2W( tBasePath ) );
    wcscat( pClipname, L"\\" );
    wcscat( pClipname, wszAudio2Name );

    // create the timeline source
    //
    CComPtr<IAMTimelineObj> pSource4Obj;
    hr = pTimeline->CreateEmptyNode( &pSource4Obj, TIMELINE_MAJOR_TYPE_SOURCE );
    ASSERT( !FAILED( hr ) );

    // set up source right
    //
    hr = pSource4Obj->SetStartStop( TLStart, TLStop );
    CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource4Src( pSource4Obj );
    hr |= pSource4Src->SetMediaTimes( MediaStart, MediaStop );
    hr |= pSource4Src->SetMediaName( pClipname );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // tell the track about the source
    //--------------------------------------------

    CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack4( pTrack4Obj );
    hr = pTrack4->SrcAdd( pSource4Obj );
    ASSERT( !FAILED( hr ) );

#ifdef DO_CROSSFADE
    //--------------------------------------------
    // put a volume effect on the source
    //--------------------------------------------

    TLStart = 4 * UNITS;
    TLStop = 6 * UNITS;

    CComPtr< IAMTimelineObj > pTrack4FxObj;
    hr = pTimeline->CreateEmptyNode( &pTrack4FxObj, TIMELINE_MAJOR_TYPE_EFFECT );
    ASSERT( !FAILED( hr ) );

    // set up effect riht
    //
    hr = pTrack4FxObj->SetStartStop( TLStart, TLStop );
    hr |= pTrack4FxObj->SetSubObjectGUID( CLSID_AudMixer );
    ASSERT( !FAILED( hr ) );

    // add the effect
    //
    CComQIPtr< IAMTimelineEffectable , &IID_IAMTimelineEffectable > pTrack4Fable( pTrack4 );
    hr = pTrack4Fable->EffectInsBefore( pTrack4FxObj, -1 );
    ASSERT( !FAILED( hr ) );

    //------------------------------------------------
    // set the the volume envelope on the audio source
    //------------------------------------------------

    pVolSetter.CoCreateInstance( CLSID_PropertySetter );
    memset( &AudioValue[0], 0, sizeof( Value ) );
    VariantClear( &AudioValue[0].v );
    V_R8( &AudioValue[0].v ) = 0.0;
    V_VT( &AudioValue[0].v ) = VT_R8;
    AudioValue[0].rt = 0 * UNITS;
    memset( &AudioValue[1], 0, sizeof( Value ) );
    VariantClear( &AudioValue[1].v );
    V_R8( &AudioValue[1].v ) = 1.0;
    V_VT( &AudioValue[1].v ) = VT_R8;
    AudioValue[1].rt = 2 * UNITS;
    AudioValue[1].dwInterp = DEXTERF_INTERPOLATE;

    hr = pVolSetter->AddProp( Param, AudioValue );
    hr |= pTrack4FxObj->SetPropertySetter( pVolSetter );
    ASSERT( !FAILED( hr ) );
    // pVolSetter will be auto-freed by COM
#endif  // DO_CROSSFADE
    
    //----------------------------------------------
    // make sure files are in their correct location
    //----------------------------------------------
    
    hr = pTimeline->ValidateSourceNames( 
        SFN_VALIDATEF_CHECK | SFN_VALIDATEF_POPUP | SFN_VALIDATEF_REPLACE, 
        NULL, 
        0 );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // create the render engine
    //--------------------------------------------

    hr = CoCreateInstance(
                         CLSID_RenderEngine,
                         NULL,
                         CLSCTX_INPROC_SERVER,
                         IID_IRenderEngine,
                         (void**) &pRenderEngine );
    ASSERT( !FAILED( hr ) );

    // tell the render engine about the timeline it should look at
    //
    hr = pRenderEngine->SetTimelineObject( pTimeline );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // connect up the front end, then the back end
    //--------------------------------------------

    hr = pRenderEngine->ConnectFrontEnd( );
    hr |= pRenderEngine->RenderOutputPins( );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // get a bunch of pointers, then run the graph
    //--------------------------------------------

    hr = pRenderEngine->GetFilterGraph( &pGraph );
    hr |= pGraph->QueryInterface( IID_IMediaEvent, (void**) &pEvent );
    hr |= pGraph->QueryInterface( IID_IMediaControl, (void**) &pControl );
    hr |= pGraph->QueryInterface( IID_IMediaSeeking, (void**) &pSeeking );
    hr |= pGraph->QueryInterface( IID_IVideoWindow, (void**) &pVidWindow );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // give the main window a meaningful title
    //--------------------------------------------

    BSTR bstrCaption;
    WriteBSTR(&bstrCaption, wszTitle);
    hr = pVidWindow->put_Caption(bstrCaption);
    FreeBSTR(&bstrCaption);

    //--------------------------------------------
    // run it
    //--------------------------------------------

    hr = pControl->Run( );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // wait for it
    //--------------------------------------------

    long EventCode = 0;
    hr = pEvent->WaitForCompletion( -1, &EventCode );
    ASSERT( !FAILED( hr ) );

    REFERENCE_TIME Start = 0;

#ifdef DO_RENDERRANGE

    // seek the timeline back to 0
    //
    hr = pSeeking->SetPositions( &Start, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning );
    ASSERT( !FAILED( hr ) );

    hr = pRenderEngine->SetRenderRange2( 2.0, 6.0 );
    ASSERT( !FAILED( hr ) );

    //------------------------------------------------------
    // connect up the front end, then the back end if needed
    //------------------------------------------------------

    hr = pRenderEngine->ConnectFrontEnd( );
    ASSERT( !FAILED( hr ) );
    if(hr == S_WARN_OUTPUTRESET) {
        hr |= pRenderEngine->RenderOutputPins( );
    }
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // run it
    //--------------------------------------------

    hr = pControl->Run( );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // wait for it
    //--------------------------------------------

    hr = pEvent->WaitForCompletion( -1, &EventCode );
    ASSERT( !FAILED( hr ) );
#endif  // DO_RENDERRANGE

#ifdef DO_RECONNECT 

    //---------------------------------------------
    // make a change to the timeline, however small
    //---------------------------------------------

    CComPtr< IAMTimelineObj > pTransObj;
    REFERENCE_TIME InOut = -1;
    pTransable->GetNextTrans( &pTransObj, &InOut );
    CComQIPtr< IAMTimelineTrans, &IID_IAMTimelineTrans > pTrans( pTransObj );
    pTrans->SetCutsOnly( TRUE );
    pTransObj.Release( );
    pTrans.Release( );
    hr = pTransable->GetNextTrans( &pTransObj, &InOut );
    pTrans = pTransObj;
    hr |= pTrans->SetCutsOnly( TRUE );
    ASSERT( !FAILED( hr ) );

    // seek the timeline back to 0
    //
    hr = pSeeking->SetPositions( &Start, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning );
    ASSERT( !FAILED( hr ) );

    //------------------------------------------------------
    // connect up the front end, then the back end if needed
    //------------------------------------------------------

    hr = pRenderEngine->ConnectFrontEnd( );
    if(hr == S_WARN_OUTPUTRESET) {
        hr |= pRenderEngine->RenderOutputPins( );
    }
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // run it
    //--------------------------------------------

    hr = pControl->Run( );
    ASSERT( !FAILED( hr ) );

    //--------------------------------------------
    // wait for it
    //--------------------------------------------

    hr = pEvent->WaitForCompletion( -1, &EventCode );
    ASSERT( !FAILED( hr ) );
#endif  // DO_RECONNECT

    return 0;
}



