/** 
   * End of job processing 
   */
  void Terminate(Option_t*)
  {
    fList = dynamic_cast<TList*>(GetOutputData(1));
    if (!fList) {
      AliError(Form("No output list defined (%p)", GetOutputData(1)));
      if (GetOutputData(1)) GetOutputData(1)->Print();
      return;
    }


    TList* output = new TList;
    output->SetName("triggerResults");
    output->SetOwner();

    fVertexMC = static_cast<TH1D*>(fList->FindObject("vertexMC"));
    fVertexESD = static_cast<TH1D*>(fList->FindObject("vertexESD"));
    fM         = static_cast<TH1D*>(fList->FindObject("m"));
    if (fVertexMC) { 
      TH1D* vtxMC = static_cast<TH1D*>(fVertexMC->Clone("vertexMC"));
      vtxMC->SetDirectory(0);
      if (vtxMC->GetEntries() > 0)
	vtxMC->Scale(1. / vtxMC->GetEntries());
      else 
	vtxMC->Scale(0);
      output->Add(vtxMC);
    }
    if (fVertexESD) { 
      TH1D* vtxESD = static_cast<TH1D*>(fVertexESD->Clone("vertexESD"));
      vtxESD->SetDirectory(0);
      if (vtxESD->GetEntries() > 0)
	vtxESD->Scale(1. / vtxESD->GetEntries());
      else 
	vtxESD->Scale(0);
      output->Add(vtxESD);
    }
    if (fM) { 
      TH1D* m = static_cast<TH1D*>(fM->Clone("m"));
      m->SetDirectory(0);
      m->SetYTitle("P(N_{ch}|_{|#eta|<1} < X)");
      if (m->GetBinContent(1) > 0)
	m->Scale(1. / m->GetBinContent(1));
      else 
	m->Scale(0);
      output->Add(m);
    }      

    TString vtxReq;
    if (fVertexRequirement & kMC)  vtxReq.Append("MC ");
    if (fVertexRequirement & kESD) vtxReq.Append("ESD ");
    output->Add(new TNamed("vtxReq", vtxReq.Data()));
    output->Add(new TNamed("trkReq",
			   fTrackletRequirement == kMC ? "MC" : "ESD"));

    fInel.Finish(fList, output);
    fInelGt0.Finish(fList, output);
    fNSD.Finish(fList, output);
    fNClusterGt0.Finish(fList, output);

    PostData(2, output);
  }
// Load a static copy buffer (g_temppaintbuffer) with the requested number of samples, 
// with the first sample(s) in the buffer always set up as the last sample(s) of the previous load.
// Return a pointer to the head of the copy buffer.
// This ensures that interpolating pitch shifters always have the previous sample to reference.
//		pChannel:				sound's channel data
//		sample_load_request:	number of samples to load from source data
//		pSamplesLoaded:			returns the actual number of samples loaded (should always = sample_load_request)
//		copyBuf:				req'd by GetOutputData, used by some Mixers
// Returns: NULL ptr to data if no samples available, otherwise always fills remainder of copy buffer with
// 0 to pad remainder.
// NOTE: DO NOT MODIFY THIS ROUTINE (KELLYB)
char *CAudioMixerWave::LoadMixBuffer( channel_t *pChannel, int sample_load_request, int *pSamplesLoaded, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
	int samples_loaded;
	char *pSample = NULL;
	char *pData = NULL;
	int cCopySamps = 0;

	// save index of last sample loaded (updated in GetOutputData)
	int sample_loaded_index = m_sample_loaded_index;

	// get data from source (copyBuf is expected to be available for use)
	samples_loaded = GetOutputData( (void **)&pData, sample_load_request, copyBuf );
	if ( !samples_loaded && sample_load_request )
	{
		// none available, bail out
		// 360 might not be able to get samples due to latency of loop seek
		// could also be the valid EOF for non-loops (caller keeps polling for data, until no more)
		AssertOnce( IsX360() || !m_pData->Source().IsLooped() );
		*pSamplesLoaded = 0;
		return NULL;
	}

	int samplesize = GetMixSampleSize();
	char *pCopy = (char *)g_temppaintbuffer;

	if ( IsX360() || IsDebug() )
	{	
		// for safety, 360 always validates sample request, due to new xma audio code and possible logic flaws
		// PC can expect number of requested samples to be within tolerances due to exisiting aged code
		// otherwise buffer overruns cause hard to track random crashes
		if ( ( ( sample_load_request + 1 ) * samplesize ) > ( TEMP_COPY_BUFFER_SIZE * sizeof( portable_samplepair_t ) ) )
		{
			// make sure requested samples will fit in temp buffer.
			// if this assert fails, then pitch is too high (ie: > 2.0) or the sample counters have diverged.
			// NOTE: to prevent this, pitch should always be capped in MixDataToDevice (but isn't nor are the sample counters).
			DevWarning( "LoadMixBuffer: sample load request %d exceeds buffer sizes\n", sample_load_request );
			Assert( 0 );
			*pSamplesLoaded = 0;
			return NULL;
		}
	}

	// copy all samples from pData to copy buffer, set 0th sample to saved previous sample - this ensures
	// interpolation pitch shift routines always have a previous sample to reference.

	// copy previous sample(s) to head of copy buffer pCopy
	// In some cases, we'll need the previous 2 samples.  This occurs when
	// Rate < 1.0 - in example below, sample 4.86 - 6.48 requires samples 4-7 (previous samples saved are 4 & 5)

	/*
	Example:
		rate = 0.81, sampleCount = 3 (ie: # of samples to return )

		_____load 3______       ____load 3_______       __load 2__

		0		1		 2		 3		 4		 5		6		7		sample_index     (whole samples)

		^     ^      ^      ^      ^     ^     ^     ^     ^        
		|     |      |      |      |     |     |     |     |   
		0    0.81   1.68   2.43   3.24  4.05  4.86  5.67  6.48          m_fsample_index  (rate*sample)
		_______________    ________________   ________________
						^   ^                  ^ ^        	
						|   |                  | |                                    
	m_sample_loaded_index   |                  | m_sample_loaded_index
							|                  |
		m_fsample_index----   		       ----m_fsample_index

		[return 3 samp]     [return 3 samp]    [return 3 samp]
	*/
	pSample = &(pChannel->sample_prev[0]);

	// determine how many saved samples we need to copy to head of copy buffer (0,1 or 2)
	// so that pitch interpolation will correctly reference samples.
	// NOTE: pitch interpolators always reference the sample before and after the indexed sample.

	// cCopySamps = sample_max_loaded - floor(m_fsample_index);

	if ( sample_loaded_index < 0 || (floor(m_fsample_index) > sample_loaded_index))
	{
		// no samples previously loaded, or
		// next sample index is entirely within the next block of samples to be loaded,
		// so we won't need any samples from the previous block. (can occur when rate > 2.0)
		cCopySamps = 0;
	}
	else if ( m_fsample_index < sample_loaded_index )
	{
		// next sample index is entirely within the previous block of samples loaded,
		// so we'll need the last 2 samples loaded.  (can occur when rate < 1.0)		
		Assert ( ceil(m_fsample_index + 0.00000001) == sample_loaded_index );
		cCopySamps = 2;
	}
	else
	{
		// next sample index is between the next block and the previously loaded block,
		// so we'll need the last sample loaded.  (can occur when 1.0 < rate < 2.0)
		Assert( floor(m_fsample_index) == sample_loaded_index );
		cCopySamps = 1;
	}
	Assert( cCopySamps >= 0 && cCopySamps <= 2 );

	// point to the sample(s) we are to copy
	if ( cCopySamps )
	{
		pSample = cCopySamps == 1 ? pSample + samplesize : pSample;
		Q_memcpy( pCopy, pSample, samplesize * cCopySamps );
		pCopy += samplesize * cCopySamps;
	}

	// don't overflow copy buffer
	Assert ( (PAINTBUFFER_MEM_SIZE * sizeof( portable_samplepair_t )) > ( ( samples_loaded + 1 ) * samplesize ) );
	
	// copy loaded samples from pData into pCopy
	// and update pointer to free space in copy buffer
	if ( ( samples_loaded * samplesize ) != 0 && !pData )
	{
		char const *pWavName = "";
		CSfxTable *source = pChannel->sfx;
		if ( source )
		{
			pWavName = source->getname();
		}
		
		Warning( "CAudioMixerWave::LoadMixBuffer: '%s' samples_loaded * samplesize = %i but pData == NULL\n", pWavName, ( samples_loaded * samplesize ) );
		*pSamplesLoaded = 0;
		return NULL;
	}

	Q_memcpy( pCopy, pData, samples_loaded * samplesize );
	pCopy += samples_loaded * samplesize;
	
	// if we loaded fewer samples than we wanted to, and we're not
	// delaying, load more samples or, if we run out of samples from non-looping source, 
	// pad copy buffer.
	if ( samples_loaded < sample_load_request )
	{
		// retry loading source data until 0 bytes returned, or we've loaded enough data.
		// if we hit 0 bytes, fill remaining space in copy buffer with 0 and exit
		int samples_load_extra;
		int samples_loaded_retry = -1;
			
		for ( int k = 0; (k < 10000 && samples_loaded_retry && samples_loaded < sample_load_request); k++ )
		{
			// how many more samples do we need to satisfy load request
			samples_load_extra = sample_load_request - samples_loaded;
			samples_loaded_retry = GetOutputData( (void**)&pData, samples_load_extra, copyBuf );

			// copy loaded samples from pData into pCopy
			if ( samples_loaded_retry )
			{
				if ( ( samples_loaded_retry * samplesize ) != 0 && !pData )
				{
					Warning( "CAudioMixerWave::LoadMixBuffer:  samples_loaded_retry * samplesize = %i but pData == NULL\n", ( samples_loaded_retry * samplesize ) );
					*pSamplesLoaded = 0;
					return NULL;
				}

				Q_memcpy( pCopy, pData, samples_loaded_retry * samplesize );
				pCopy += samples_loaded_retry * samplesize;
				samples_loaded += samples_loaded_retry;
			}
		}
	}

	// if we still couldn't load the requested samples, fill rest of copy buffer with 0
	if ( samples_loaded < sample_load_request )
	{
		// should always be able to get as many samples as we request from looping sound sources
		AssertOnce ( IsX360() || !m_pData->Source().IsLooped() );

		// these samples are filled with 0, not loaded.
		// non-looping source hit end of data, fill rest of g_temppaintbuffer with 0
		int samples_zero_fill = sample_load_request - samples_loaded;

		Q_memset( pCopy, 0, samples_zero_fill * samplesize );
		pCopy += samples_zero_fill * samplesize;
		samples_loaded += samples_zero_fill;
	}

	if ( samples_loaded >= 2 )
	{
		// always save last 2 samples from copy buffer to channel 
		// (we'll need 0,1 or 2 samples as start of next buffer for interpolation)
		Assert( sizeof( pChannel->sample_prev ) >= samplesize*2 );
		pSample = pCopy - samplesize*2;
		Q_memcpy( &(pChannel->sample_prev[0]), pSample, samplesize*2 );
	}

	// this routine must always return as many samples loaded (or zeros) as requested.
	Assert( samples_loaded == sample_load_request );

	*pSamplesLoaded = samples_loaded;

	return (char *)g_temppaintbuffer;
}
//-----------------------------------------------------------------------------
// Purpose: The device calls this to request data.  The mixer must provide the
//			full amount of samples or have silence in its output stream.
// Input  : *pDevice - requesting device
//			sampleCount - number of samples at the output rate
//			outputRate - sampling rate of the request
// Output : Returns true to keep mixing, false to delete this mixer
//-----------------------------------------------------------------------------
bool CAudioMixerWave::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward /*= true*/ )
{
	int offset = 0;

	int inputSampleRate = (int)(pChannel->pitch * m_pData->Source().SampleRate());
	float rate = (float)inputSampleRate / outputRate;
	fixedint fracstep = FIX_FLOAT( rate );

	sampleCount = min( sampleCount, PAINTBUFFER_SIZE );

	int startpos = startSample;

	if ( !forward )
	{
		int requestedstart = startSample - (int)( sampleCount * rate );
		if ( requestedstart < 0 )
			return false;

		startpos = max( 0, requestedstart );
		SetSamplePosition( startpos );
	}

	while ( sampleCount > 0 )
	{
		int availableSamples;
		int inputSampleCount;
		char *pData = NULL;
		int outputSampleCount = sampleCount;
		
		
		if ( outputRate != inputSampleRate )
		{
			inputSampleCount = (int)(sampleCount * rate);

			int availableSamples = GetOutputData( (void **)&pData, startpos, inputSampleCount, forward );
			if ( !availableSamples )
				break;

			if ( availableSamples < inputSampleCount )
			{
				outputSampleCount = (int)(availableSamples / rate);
				inputSampleCount = availableSamples;
			}

			Mix( pDevice, pChannel, pData, offset, m_fracOffset, fracstep, outputSampleCount, 0, forward );
			
			// compute new fraction part of sample index
			float offset = (m_fracOffset / FIX_SCALE) + (rate * outputSampleCount);
			offset = offset - (float)((int)offset);
			m_fracOffset = FIX_FLOAT(offset);
		}
		else
		{
			availableSamples = GetOutputData( (void **)&pData, startpos, sampleCount, forward );
			if ( !availableSamples )
				break;

			outputSampleCount = availableSamples;
			inputSampleCount = availableSamples;

			Mix( pDevice, pChannel, pData, offset, m_fracOffset, FIX(1), outputSampleCount, 0, forward );
		}
		offset += outputSampleCount;
		sampleCount -= outputSampleCount;

		if ( forward )
		{
			m_sample += inputSampleCount;
			m_absoluteSample += inputSampleCount;
		}

		if ( m_loop != 0 && m_sample >= m_loop )
		{
			SetSamplePosition( m_startpos );
		}

	}

	if ( sampleCount > 0 )
		return false;

	return true;
}