1264 lines
41 KiB
C++
1264 lines
41 KiB
C++
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// LocalTTSEngineSite.cpp: implementation of the CLocalTTSEngineSite class.
|
||
|
//
|
||
|
// Created by JOEM 02-2000
|
||
|
// Copyright (C) 2000 Microsoft Corporation
|
||
|
// All Rights Reserved
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
|
||
|
#include "LocalTTSEngineSite.h"
|
||
|
#include <LIMITS>
|
||
|
#include <spddkhlp.h>
|
||
|
#include "vapiIO.h"
|
||
|
|
||
|
// If buffer is used, the min size is one sec
|
||
|
const int g_iMinBufferSize = 1;
|
||
|
const int g_iMaxBufferSize = 60; // let's keep it reasonable, ok!
|
||
|
const float g_dMinBufferShift = .25;
|
||
|
const double g_dChunkSize = .10;
|
||
|
const int g_iBase = 16;
|
||
|
|
||
|
// Default duration of chunks written from buffer to SAPI
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite
|
||
|
//
|
||
|
// Construction/Destruction
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
CLocalTTSEngineSite::CLocalTTSEngineSite()
|
||
|
{
|
||
|
m_vcRef = 1;
|
||
|
m_pMainOutputSite = NULL;
|
||
|
m_pOutputFormatId = NULL;
|
||
|
m_pOutputFormat = NULL;
|
||
|
|
||
|
m_pcBuffer = NULL;
|
||
|
m_ulBufferBytes = 0;
|
||
|
m_ulBufferSeconds = 0;
|
||
|
m_ulMinBufferShift = 0;
|
||
|
m_ulDataEnd = 0;
|
||
|
m_ulCurrentByte = 0;
|
||
|
m_ulSkipForward = 0;
|
||
|
|
||
|
m_pEventQueue = NULL;
|
||
|
m_pCurrentEvent = NULL;
|
||
|
m_pLastEvent = NULL;
|
||
|
|
||
|
m_ullTotalBytesReceived = 0;
|
||
|
m_ullPreviousBytesReceived = 0;
|
||
|
m_ullBytesWritten = 0;
|
||
|
m_ullBytesWrittenToSAPI = 0;
|
||
|
m_lTotalBytesSkipped = 0;
|
||
|
|
||
|
m_pTsm = NULL;
|
||
|
m_flRateAdj = 1.0; // Regular rate unless user changes it.
|
||
|
m_flVol = 1.0; // Full volume unless user changes it.
|
||
|
}
|
||
|
|
||
|
CLocalTTSEngineSite::~CLocalTTSEngineSite()
|
||
|
{
|
||
|
m_pMainOutputSite = NULL;
|
||
|
|
||
|
if ( m_pOutputFormat )
|
||
|
{
|
||
|
free( m_pOutputFormat );
|
||
|
m_pOutputFormat = NULL;
|
||
|
m_pOutputFormatId = NULL;
|
||
|
}
|
||
|
|
||
|
Event* pEvent = m_pEventQueue;
|
||
|
Event* pNextEvent = NULL;
|
||
|
while ( pEvent )
|
||
|
{
|
||
|
pNextEvent = pEvent->pNext;
|
||
|
delete pEvent;
|
||
|
pEvent = pNextEvent;
|
||
|
}
|
||
|
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
delete [] m_pcBuffer;
|
||
|
m_pcBuffer = NULL;
|
||
|
}
|
||
|
|
||
|
if ( m_pTsm )
|
||
|
{
|
||
|
delete m_pTsm;
|
||
|
m_pTsm = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::QueryInterface
|
||
|
//
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::QueryInterface ( REFIID iid, void** ppv )
|
||
|
{
|
||
|
if ( iid == IID_ISpTTSEngineSite )
|
||
|
{
|
||
|
*ppv = (ISpTTSEngineSite*) this;
|
||
|
}
|
||
|
else if ( iid == IID_ISpTTSEngineSite )
|
||
|
{
|
||
|
*ppv = (ISpTTSEngineSite*) this;
|
||
|
}
|
||
|
else if ( iid == IID_ISpEventSink )
|
||
|
{
|
||
|
*ppv = (ISpEventSink*) this;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ppv = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
((IUnknown*) *ppv)->AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::AddRef
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
ULONG CLocalTTSEngineSite::AddRef(void)
|
||
|
{
|
||
|
return InterlockedIncrement(&m_vcRef);
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::Release
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
ULONG CLocalTTSEngineSite::Release(void)
|
||
|
{
|
||
|
if ( 0 == InterlockedDecrement(&m_vcRef) )
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return (ULONG) m_vcRef;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::SetOutputSite
|
||
|
//
|
||
|
// Sets pointer to main SAPI output site.
|
||
|
// Sets/resets data buffer and accumulators to zero.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
void CLocalTTSEngineSite::SetOutputSite(ISpTTSEngineSite* pOutputSite)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::SetOutputSite" );
|
||
|
|
||
|
SPDBG_ASSERT(pOutputSite);
|
||
|
|
||
|
m_pMainOutputSite = pOutputSite;
|
||
|
|
||
|
// flush the buffer
|
||
|
m_ulCurrentByte = 0;
|
||
|
m_ulDataEnd = 0;
|
||
|
m_ulSkipForward = 0;
|
||
|
memset( m_pcBuffer, 0, m_ulBufferBytes * sizeof(char) );
|
||
|
|
||
|
// flush the event queue and start over
|
||
|
Event* pEvent = m_pEventQueue;
|
||
|
Event* pNextEvent = NULL;
|
||
|
while ( pEvent )
|
||
|
{
|
||
|
pNextEvent = pEvent->pNext;
|
||
|
delete pEvent;
|
||
|
pEvent = pNextEvent;
|
||
|
}
|
||
|
m_pEventQueue = NULL;
|
||
|
m_pCurrentEvent = NULL;
|
||
|
m_pLastEvent = NULL;
|
||
|
|
||
|
// reset counters
|
||
|
m_ullTotalBytesReceived = 0;
|
||
|
m_ullPreviousBytesReceived = 0;
|
||
|
m_ullBytesWritten = 0;
|
||
|
m_ullBytesWrittenToSAPI = 0;
|
||
|
m_lTotalBytesSkipped = 0;
|
||
|
|
||
|
m_flRateAdj = 1.0; // Regular rate unless user changes it.
|
||
|
m_flVol = 1.0; // Full volume unless user changes it.
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::SetBufferSize
|
||
|
//
|
||
|
// Sets the size of the audio data buffer, and creates the buffer if the
|
||
|
// output format is known (but Text output is not buffered).
|
||
|
//
|
||
|
// **Destroys contents of existing buffer.** That's ok, because this function
|
||
|
// is not exposed to the application -- just to the engine, where speak calls
|
||
|
// are synchronous.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::SetBufferSize(const ULONG ulSeconds)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::SetBufferSize" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
// don't need to preserve contents of old buffer -- it's flushed between
|
||
|
// speak calls anyway.
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
delete [] m_pcBuffer;
|
||
|
m_pcBuffer = NULL;
|
||
|
}
|
||
|
m_ulBufferBytes = 0;
|
||
|
m_ulBufferSeconds = 0;
|
||
|
m_ulMinBufferShift = 0;
|
||
|
m_ulDataEnd = 0;
|
||
|
m_ulCurrentByte = 0;
|
||
|
|
||
|
if ( ulSeconds )
|
||
|
{
|
||
|
if ( ulSeconds < g_iMinBufferSize )
|
||
|
{
|
||
|
m_ulBufferSeconds = g_iMinBufferSize;
|
||
|
}
|
||
|
else if ( ulSeconds > g_iMaxBufferSize )
|
||
|
{
|
||
|
m_ulBufferSeconds = g_iMaxBufferSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_ulBufferSeconds = ulSeconds;
|
||
|
}
|
||
|
|
||
|
// check format -- don't set a buffer for text format
|
||
|
if ( m_pOutputFormatId )
|
||
|
{
|
||
|
if ( *m_pOutputFormatId != SPDFID_Text )
|
||
|
{
|
||
|
// if the wav format is defined, allocate the buffer for the correct #secs of data
|
||
|
if ( m_pOutputFormat )
|
||
|
{
|
||
|
m_ulBufferBytes = m_ulBufferSeconds * m_pOutputFormat->nAvgBytesPerSec;
|
||
|
|
||
|
m_pcBuffer = new char[m_ulBufferBytes];
|
||
|
if ( !m_pcBuffer )
|
||
|
{
|
||
|
m_ulBufferBytes = 0;
|
||
|
m_ulBufferSeconds = 0;
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// Make the min shift approximately 1/4 of buffer
|
||
|
// When the buffer is full, data shifts off the end by this amount.
|
||
|
m_ulMinBufferShift = g_dMinBufferShift * m_ulBufferBytes;
|
||
|
// data must shift by multiples of nBlockAlign
|
||
|
m_ulMinBufferShift -= ( m_ulMinBufferShift % m_pOutputFormat->nBlockAlign );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_ulBufferSeconds = 0; // can't buffer text - but not an error(?)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::SetOutputFormat
|
||
|
//
|
||
|
// Sets the format in which samples are sent to SAPI.
|
||
|
// Also creates the rate changer.
|
||
|
//
|
||
|
////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::SetOutputFormat(const GUID *pOutputFormatId, const WAVEFORMATEX *pOutputFormat)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::SetOutputFormat" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
SPDBG_ASSERT(pOutputFormatId);
|
||
|
|
||
|
if ( pOutputFormatId && *pOutputFormatId == SPDFID_Text )
|
||
|
{
|
||
|
m_pOutputFormatId = pOutputFormatId;
|
||
|
|
||
|
SetBufferSize(0); // deletes the current buffer, if any (can't buffer text)
|
||
|
|
||
|
if ( m_pOutputFormat )
|
||
|
{
|
||
|
delete m_pOutputFormat;
|
||
|
m_pOutputFormat = NULL;
|
||
|
}
|
||
|
}
|
||
|
else if ( pOutputFormat )
|
||
|
{
|
||
|
|
||
|
// Do we need to change the format?
|
||
|
if ( !m_pOutputFormat ||
|
||
|
m_pOutputFormat->wFormatTag != pOutputFormat->wFormatTag ||
|
||
|
m_pOutputFormat->nChannels != pOutputFormat->nChannels ||
|
||
|
m_pOutputFormat->nSamplesPerSec != pOutputFormat->nSamplesPerSec ||
|
||
|
m_pOutputFormat->nAvgBytesPerSec != pOutputFormat->nAvgBytesPerSec ||
|
||
|
m_pOutputFormat->nBlockAlign != pOutputFormat->nBlockAlign ||
|
||
|
m_pOutputFormat->wBitsPerSample != pOutputFormat->wBitsPerSample ||
|
||
|
m_pOutputFormat->cbSize != pOutputFormat->cbSize
|
||
|
)
|
||
|
{
|
||
|
|
||
|
// save these values to determine whether buffer and/or rate changer need adjusting
|
||
|
// for the new format
|
||
|
DWORD oldnAvgBytesPerSec = 0;
|
||
|
DWORD oldnSamplesPerSec = 0;
|
||
|
if ( m_pOutputFormat )
|
||
|
{
|
||
|
oldnAvgBytesPerSec = m_pOutputFormat->nAvgBytesPerSec;
|
||
|
oldnSamplesPerSec = m_pOutputFormat->nSamplesPerSec;
|
||
|
}
|
||
|
|
||
|
// free the current waveformatex
|
||
|
if ( m_pOutputFormat )
|
||
|
{
|
||
|
free(m_pOutputFormat);
|
||
|
m_pOutputFormat = NULL;
|
||
|
}
|
||
|
// this needs to copy the output format, not just point to it,
|
||
|
// because engine will pass in const pointer.
|
||
|
m_pOutputFormat = (WAVEFORMATEX*) malloc( sizeof(WAVEFORMATEX) + pOutputFormat->cbSize );
|
||
|
if ( !m_pOutputFormat )
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pOutputFormatId = pOutputFormatId;
|
||
|
memcpy(m_pOutputFormat, pOutputFormat, sizeof(WAVEFORMATEX) + pOutputFormat->cbSize );
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// Re-adjust the buffer size for the new format, if necessary
|
||
|
if ( oldnAvgBytesPerSec != m_pOutputFormat->nAvgBytesPerSec )
|
||
|
{
|
||
|
hr = SetBufferSize(m_ulBufferSeconds);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// once we know the sampling frequency, we can create the rate changer
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
if ( !m_pTsm )
|
||
|
{
|
||
|
m_pTsm = new CTsm( g_dRateScale[BASE_RATE], m_pOutputFormat->nSamplesPerSec );
|
||
|
}
|
||
|
|
||
|
if ( !m_pTsm )
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// Reset the rate changer sampling frequency, if necessary
|
||
|
if ( oldnSamplesPerSec != m_pOutputFormat->nSamplesPerSec )
|
||
|
{
|
||
|
if ( m_pTsm->SetSamplingFrequency( m_pOutputFormat->nSamplesPerSec )
|
||
|
!= m_pOutputFormat->nSamplesPerSec )
|
||
|
{
|
||
|
delete m_pTsm;
|
||
|
m_pTsm = NULL;
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::GetActions
|
||
|
//
|
||
|
// When using a buffer, mask all actions except abort. This module handles
|
||
|
// the real-time changes, so it makes the engine output regular rate and vol
|
||
|
// all the time. (But the engine handles XML-based rate/vol changes.)
|
||
|
//
|
||
|
// When not buffering, pass the call to SAPI, but OR'd with vol and rate.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 02-2000 //
|
||
|
DWORD CLocalTTSEngineSite::GetActions( void )
|
||
|
{
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
return m_pMainOutputSite->GetActions() & SPVES_ABORT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// When prompt eng calls GetVolume or GetRate, SAPI turns off the
|
||
|
// SPVES_VOLUME/SPVES_RATE flags. OR them here to force the TTS engine
|
||
|
// to call GetRate and GetVolume too.
|
||
|
return m_pMainOutputSite->GetActions() | SPVES_VOLUME | SPVES_RATE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::GetRate (ISpTTSEngineSite::GetRate)
|
||
|
//
|
||
|
// When using a buffer, simply sets plRateAdjust to 0 to make the engine
|
||
|
// use normal rate (module controls real-time rate changes).
|
||
|
//
|
||
|
// When not buffering, pass the call to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::GetRate ( long* plRateAdjust )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::GetRate" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
*plRateAdjust = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = m_pMainOutputSite->GetRate ( plRateAdjust );
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::GetVolume (ISpTTSEngineSite::GetVolume)
|
||
|
//
|
||
|
// When using a buffer, simply sets punVolume to 100 to make the engine use
|
||
|
// full volume (module controls real-time vol changes).
|
||
|
//
|
||
|
// When not buffering, pass the call to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::GetVolume ( USHORT* punVolume )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::GetVolume" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
*punVolume = 100;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = m_pMainOutputSite->GetVolume ( punVolume );
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::GetSkipInfo (ISpTTSEngineSite::GetSkipInfo)
|
||
|
//
|
||
|
// When using a buffer, simply returns 0 to prevent the engine from skipping
|
||
|
// (module controls skips).
|
||
|
//
|
||
|
// When not buffering, pass the call to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::GetSkipInfo ( SPVSKIPTYPE* peType, long* plNumItems )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::GetSkipInfo" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
*peType = SPVST_SENTENCE;
|
||
|
*plNumItems = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = m_pMainOutputSite->GetSkipInfo ( peType, plNumItems );
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::CompleteSkip (ISpTTSEngineSite::CompleteSkip)
|
||
|
//
|
||
|
// When using a buffer, this just returns.
|
||
|
//
|
||
|
// When not buffering, pass the call to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::CompleteSkip ( long lNumSkipped )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::CompleteSkip" );
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if ( !m_pcBuffer )
|
||
|
{
|
||
|
hr = m_pMainOutputSite->CompleteSkip ( lNumSkipped );
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::AddEvents (ISpTTSEngineSite::AddEvents)
|
||
|
//
|
||
|
// Two engines (Prompt and TTS) might be writing to this output site.
|
||
|
// Each will not know that the other is sending data, so events
|
||
|
// won't have the correct ullAudioStreamOffset.
|
||
|
//
|
||
|
// Consequently, CLocalTTSEngineSite::UpdateBytesWritten must be called
|
||
|
// after each time the Prompt engine issues a TTS->speak call and after
|
||
|
// each time the PE calls CLocalTTSEngineSite::Write.
|
||
|
//
|
||
|
// Note that when this is used by a single engine, there is no need to
|
||
|
// (or harm in) calling UpdateBytesWritten. Consequently, TTS engines
|
||
|
// require no modification -- the Prompt Engine manages AudioStreamOffset
|
||
|
// when there are 2 engines writing data.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::AddEvents(const SPEVENT* pEventArray, ULONG ulCount )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::AddEvents" );
|
||
|
HRESULT hr = S_OK;
|
||
|
ULONG i = 0;
|
||
|
|
||
|
SPDBG_ASSERT(pEventArray || !ulCount);
|
||
|
|
||
|
// Are we buffering?
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
Event* pNewEvent = NULL;
|
||
|
|
||
|
for ( i=0; SUCCEEDED(hr) && i<ulCount; i++ )
|
||
|
{
|
||
|
// Make a new Event structure
|
||
|
pNewEvent = new Event;
|
||
|
if ( !pNewEvent )
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
pNewEvent->pPrev = NULL;
|
||
|
pNewEvent->pNext = NULL;
|
||
|
memcpy(&pNewEvent->event, &pEventArray[i], sizeof(SPEVENT) );
|
||
|
// ullAudioStreamOffset adjusted here since two engines may be writing data
|
||
|
pNewEvent->event.ullAudioStreamOffset += m_ullPreviousBytesReceived;
|
||
|
|
||
|
// Put it in the event queue, sorted
|
||
|
// Is this the only event in the queue?
|
||
|
if ( !m_pEventQueue )
|
||
|
{
|
||
|
m_pEventQueue = pNewEvent;
|
||
|
m_pCurrentEvent = pNewEvent;
|
||
|
m_pLastEvent = pNewEvent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Does the new event go last?
|
||
|
if ( m_pLastEvent && m_pLastEvent->event.ullAudioStreamOffset <= pNewEvent->event.ullAudioStreamOffset )
|
||
|
{
|
||
|
m_pLastEvent->pNext = pNewEvent;
|
||
|
pNewEvent->pPrev = m_pLastEvent;
|
||
|
m_pLastEvent = pNewEvent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// figure out where it goes -- most likely it goes near the end, so step
|
||
|
// backward through the list
|
||
|
Event* pInsertHere = m_pLastEvent;
|
||
|
while ( pInsertHere )
|
||
|
{
|
||
|
if ( pInsertHere->event.ullAudioStreamOffset <= pNewEvent->event.ullAudioStreamOffset )
|
||
|
{
|
||
|
pNewEvent->pPrev = pInsertHere;
|
||
|
pInsertHere->pNext->pPrev = pNewEvent;
|
||
|
pNewEvent->pNext = pInsertHere->pNext;
|
||
|
pInsertHere->pNext = pNewEvent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pInsertHere = pInsertHere->pPrev;
|
||
|
}
|
||
|
}
|
||
|
// did we reach the front of the list?
|
||
|
if ( !pInsertHere )
|
||
|
{
|
||
|
// insert it at the beginning
|
||
|
pNewEvent->pNext = m_pEventQueue;
|
||
|
m_pEventQueue->pPrev = pNewEvent;
|
||
|
m_pEventQueue = pNewEvent;
|
||
|
}
|
||
|
} // else
|
||
|
} // else
|
||
|
|
||
|
//if there's no current event, make the first in this array current.
|
||
|
if ( !m_pCurrentEvent )
|
||
|
{
|
||
|
m_pCurrentEvent = pNewEvent;
|
||
|
}
|
||
|
} // if ( SUCCEEDED(hr) )
|
||
|
} // for ( i=0; SUCCEEDED(hr) && i<ulCount; i++ )
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// no buffer -- just send the events
|
||
|
SPEVENT* pEvents = const_cast<SPEVENT*> (pEventArray);
|
||
|
|
||
|
for ( i=0; i<ulCount; i++ )
|
||
|
{
|
||
|
pEvents[i].ullAudioStreamOffset += m_ullPreviousBytesReceived;
|
||
|
}
|
||
|
|
||
|
hr = m_pMainOutputSite->AddEvents(pEvents, ulCount);
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::SendEvents
|
||
|
//
|
||
|
// Steps through the event buffer, sending events that should occur within
|
||
|
// the current block of data.
|
||
|
//
|
||
|
// The event's ullAudioStreamOffset must be adjust by the count of bytes
|
||
|
// skipped (includes skip forward and backward). The sum must then be scaled
|
||
|
// to match the cumulative real-time rate adjustments that have occurred
|
||
|
// (ratio of bytes-sent-to-SAPI to the unaltered byte count).
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::SendEvents()
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::SendEvents" );
|
||
|
HRESULT hr = S_OK;
|
||
|
ULONG ulEventPos = 0;
|
||
|
ULONG ulOriginalOffset = 0;
|
||
|
float flRateAdj = 1.0;
|
||
|
|
||
|
// was there a previous rate adjustment to factor in?
|
||
|
if ( m_ullBytesWrittenToSAPI != m_ullBytesWritten )
|
||
|
{
|
||
|
flRateAdj = (float)m_ullBytesWrittenToSAPI / m_ullBytesWritten;
|
||
|
}
|
||
|
|
||
|
while ( SUCCEEDED(hr) && m_pCurrentEvent )
|
||
|
{
|
||
|
// only send events up to the current byte count
|
||
|
if ( m_pCurrentEvent->event.ullAudioStreamOffset <= (m_ullBytesWritten + m_lTotalBytesSkipped) )
|
||
|
{
|
||
|
ulOriginalOffset = m_pCurrentEvent->event.ullAudioStreamOffset;
|
||
|
|
||
|
// Adjust the cb by amount skipped
|
||
|
// BytesSkipped should never be greater than the current event's position!!
|
||
|
// If this happens, RescheduleEvents is not working properly.
|
||
|
SPDBG_ASSERT(m_lTotalBytesSkipped <= (long)m_pCurrentEvent->event.ullAudioStreamOffset);
|
||
|
m_pCurrentEvent->event.ullAudioStreamOffset -= m_lTotalBytesSkipped;
|
||
|
// Adjust by proportion rate change
|
||
|
m_pCurrentEvent->event.ullAudioStreamOffset *= flRateAdj;
|
||
|
// Make sure it's a multiple of nBlockAlign
|
||
|
m_pCurrentEvent->event.ullAudioStreamOffset -= (m_pCurrentEvent->event.ullAudioStreamOffset % m_pOutputFormat->nBlockAlign );
|
||
|
|
||
|
hr = m_pMainOutputSite->AddEvents(&m_pCurrentEvent->event, 1);
|
||
|
|
||
|
// restore the original in case we need to skip back
|
||
|
m_pCurrentEvent->event.ullAudioStreamOffset = ulOriginalOffset;
|
||
|
|
||
|
m_pCurrentEvent = m_pCurrentEvent->pNext;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::RescheduleEvents
|
||
|
//
|
||
|
// Resets the pointer to the current event.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
void CLocalTTSEngineSite::RescheduleEvents(Event* pStart)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::RescheduleEvents" );
|
||
|
|
||
|
Event* pEvent = pStart;
|
||
|
|
||
|
// Reschedules (or skips) events based on bytes skipped (forward or backward)
|
||
|
while ( pEvent && ( pEvent->event.ullAudioStreamOffset < m_ullBytesWritten + m_lTotalBytesSkipped ) )
|
||
|
{
|
||
|
pEvent = pEvent->pNext;
|
||
|
}
|
||
|
|
||
|
m_pCurrentEvent = pEvent;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::FlushEvents
|
||
|
//
|
||
|
// Some events are flushed when corresponding audio data is pushed off the
|
||
|
// end of the buffer.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
void CLocalTTSEngineSite::FlushEvents(const ULONG cb)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::FlushEvents" );
|
||
|
|
||
|
Event* pEvent = m_pEventQueue;
|
||
|
Event* pNextEvent = NULL;
|
||
|
|
||
|
while ( pEvent && pEvent->event.ullAudioStreamOffset <= cb )
|
||
|
{
|
||
|
pNextEvent = pEvent->pNext;
|
||
|
|
||
|
delete pEvent;
|
||
|
pEvent = pNextEvent;
|
||
|
if ( pEvent )
|
||
|
{
|
||
|
pEvent->pPrev = NULL;
|
||
|
}
|
||
|
}
|
||
|
if ( pEvent )
|
||
|
{
|
||
|
m_pEventQueue = pEvent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pEventQueue = NULL;
|
||
|
m_pCurrentEvent = NULL;
|
||
|
m_pLastEvent = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::Write
|
||
|
//
|
||
|
// Copies the data (pBuff) onto the end of the audio buffer (at current empty
|
||
|
// location), pushing data off the front of the buffer to make room,
|
||
|
// if necessary.
|
||
|
//
|
||
|
// If buffer size is too small, some of the data is written directly to
|
||
|
// the SAPI output site and the rest will be buffered.
|
||
|
//
|
||
|
// If no buffer exists, all of the data is written directly to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::Write( const void* pBuff, ULONG cb, ULONG *pcbWritten )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::Write" );
|
||
|
HRESULT hr = S_OK;
|
||
|
long lSpaceNeeded = 0;
|
||
|
char* pcStart = NULL;
|
||
|
ULONG ulBytesToWrite = 0;
|
||
|
bool fAbort = false;
|
||
|
|
||
|
char* pcBuff = (char*)pBuff;
|
||
|
pcStart = pcBuff;
|
||
|
|
||
|
m_ullTotalBytesReceived += cb;
|
||
|
|
||
|
// Are we buffering the samples, or just sending them?
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
// Do we need extra space?
|
||
|
lSpaceNeeded = ( m_ulDataEnd + cb ) - m_ulBufferBytes;
|
||
|
|
||
|
// if out of space, push some off the front to make room
|
||
|
if ( lSpaceNeeded > 0 )
|
||
|
{
|
||
|
// Is the buffer big enough?
|
||
|
if ( cb > m_ulBufferBytes )
|
||
|
{
|
||
|
ulBytesToWrite = cb - m_ulBufferBytes;
|
||
|
hr = WriteToSAPI( pcBuff, ulBytesToWrite, &fAbort );
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// we're going to replace the entire buffer with what's left
|
||
|
// so the current data in the buffer is garbage now.
|
||
|
m_ulDataEnd = 0;
|
||
|
pcStart = &pcBuff[ulBytesToWrite]; // make pcStart point to what's left after WriteToSapi
|
||
|
// Flush the entire event buffer
|
||
|
FlushEvents(m_ullBytesWritten + m_lTotalBytesSkipped);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// how much should we shift off the end?
|
||
|
lSpaceNeeded = ( lSpaceNeeded > m_ulMinBufferShift ) ? lSpaceNeeded : m_ulMinBufferShift;
|
||
|
|
||
|
// Move the stuff we're saving to the beginning of the buffer (overwrite the shifted amount)
|
||
|
memmove( m_pcBuffer, &m_pcBuffer[ lSpaceNeeded ], (m_ulDataEnd - lSpaceNeeded) * sizeof(char) );
|
||
|
// The data end now shifts toward front of buffer
|
||
|
m_ulDataEnd -= lSpaceNeeded;
|
||
|
pcStart = pcBuff; // make pcStart point to all of the new data
|
||
|
// Flush everything received (minus the new bytes) minus what's left to buffer
|
||
|
FlushEvents(m_ullTotalBytesReceived - cb - (m_ulBufferBytes - lSpaceNeeded));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// go to the end of the data we're keeping
|
||
|
m_ulCurrentByte = m_ulDataEnd;
|
||
|
// copy new data onto end of buffer.
|
||
|
memcpy( &m_pcBuffer[m_ulCurrentByte], pcStart, cb - ulBytesToWrite );
|
||
|
// Mark the new end
|
||
|
m_ulDataEnd += (cb - ulBytesToWrite);
|
||
|
|
||
|
if ( !fAbort )
|
||
|
{
|
||
|
hr = WriteBuffer();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = WriteToSAPI( pcBuff, cb, &fAbort );
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) && pcbWritten )
|
||
|
{
|
||
|
*pcbWritten = cb;
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::WriteBuffer
|
||
|
//
|
||
|
// Sends data from the buffer to SAPI (via WriteToSAPI) in small chunks,
|
||
|
// checking for skip/abort actions. (WriteToSAPI does vol/rate changes).
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::WriteBuffer()
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::WriteBuffer" );
|
||
|
HRESULT hr = S_OK;
|
||
|
ULONG nBytesToWrite = 0;
|
||
|
bool fAbort = false;
|
||
|
ULONG ulChunkSize = 0;
|
||
|
|
||
|
ulChunkSize = g_dChunkSize * m_pOutputFormat->nAvgBytesPerSec;
|
||
|
ulChunkSize -= (ulChunkSize % m_pOutputFormat->nBlockAlign); // write in multiples of nBlockAlign
|
||
|
|
||
|
// Try to write one full chunk (approx. 0.1 sec) of audio per loop
|
||
|
while ( SUCCEEDED(hr) && m_ulCurrentByte < m_ulDataEnd )
|
||
|
{
|
||
|
// Stop speaking?
|
||
|
if ( fAbort || ( m_pMainOutputSite->GetActions() & SPVES_ABORT ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Still skipping forward?
|
||
|
if ( m_ulSkipForward > 0 )
|
||
|
{
|
||
|
if ( m_ulDataEnd - m_ulCurrentByte > m_ulSkipForward )
|
||
|
{
|
||
|
// there is some data in buffer that can be written
|
||
|
m_ulCurrentByte += m_ulSkipForward; // move the current byte forward by the skip amount
|
||
|
m_lTotalBytesSkipped += m_ulSkipForward; // keep accumulating
|
||
|
m_ulSkipForward = 0; // no more to skip
|
||
|
RescheduleEvents(m_pCurrentEvent); // skip associated events
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// skip all of the rest of the data in the buffer and await more
|
||
|
m_ulSkipForward -= ( m_ulDataEnd - m_ulCurrentByte ); // reduce bytes left to skip
|
||
|
m_lTotalBytesSkipped += ( m_ulDataEnd - m_ulCurrentByte ); // keep accumulating
|
||
|
m_ulCurrentByte = m_ulDataEnd; // move to end of buffered data
|
||
|
m_pCurrentEvent = m_pLastEvent; // move to end of event buffer
|
||
|
break; // no data to be written
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// New skip?
|
||
|
if( m_pMainOutputSite->GetActions() & SPVES_SKIP )
|
||
|
{
|
||
|
SPVSKIPTYPE SkipType;
|
||
|
long lSkipCount = 0;
|
||
|
|
||
|
hr = m_pMainOutputSite->GetSkipInfo( &SkipType, &lSkipCount );
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
long lBytesToSkip = 0;
|
||
|
long lSkipped = 0;
|
||
|
|
||
|
lBytesToSkip = lSkipCount * m_pOutputFormat->nAvgBytesPerSec;
|
||
|
lBytesToSkip *= m_flRateAdj; // we're skipping by seconds, so rate-adjust
|
||
|
lBytesToSkip -= ( lBytesToSkip % m_pOutputFormat->nBlockAlign ); // need multiple of nBlockAlign
|
||
|
|
||
|
// skipping forward?
|
||
|
if ( lBytesToSkip > 0 )
|
||
|
{
|
||
|
m_ulSkipForward += lBytesToSkip;
|
||
|
lSkipped = lBytesToSkip;
|
||
|
}
|
||
|
else // skipping backward
|
||
|
{
|
||
|
// don't skip backward past the beginning
|
||
|
if ( (long) m_ulCurrentByte + lBytesToSkip < 0 )
|
||
|
{
|
||
|
lSkipped -= m_ulCurrentByte; // just go back to beginning
|
||
|
m_ulCurrentByte = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lSkipped = lBytesToSkip;
|
||
|
m_ulCurrentByte += lSkipped; // lBytesSkipped is negative
|
||
|
}
|
||
|
m_lTotalBytesSkipped += lSkipped;
|
||
|
|
||
|
RescheduleEvents(m_pEventQueue);
|
||
|
}
|
||
|
|
||
|
hr = m_pMainOutputSite->CompleteSkip( lSkipCount );
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// Do we have a full chunk of audio samples?
|
||
|
if ( m_ulDataEnd - m_ulCurrentByte > ulChunkSize )
|
||
|
{
|
||
|
// Yes? Write a full chunk
|
||
|
nBytesToWrite = ulChunkSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No? Write all that we have
|
||
|
nBytesToWrite = m_ulDataEnd - m_ulCurrentByte;
|
||
|
// this should always be a multiple of nBlockAlign already!
|
||
|
SPDBG_ASSERT(nBytesToWrite == (nBytesToWrite - (nBytesToWrite % m_pOutputFormat->nBlockAlign) ) );
|
||
|
}
|
||
|
|
||
|
if ( nBytesToWrite )
|
||
|
{
|
||
|
hr = WriteToSAPI( (void*)&m_pcBuffer[m_ulCurrentByte], nBytesToWrite, &fAbort );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
m_ulCurrentByte += nBytesToWrite;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::WriteToSAPI
|
||
|
//
|
||
|
// Forwards the data to the SAPI output site, adjusting vol/rate if necessary.
|
||
|
// Accumulates count of bytes to be written, and bytes actually written to SAPI.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::WriteToSAPI( const void* pvBuff, const ULONG cb, bool* pfAbort )
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::WriteToSAPI" );
|
||
|
HRESULT hr = S_OK;
|
||
|
void* pvTsmBuff = NULL;
|
||
|
void* pvVolBuff = NULL;
|
||
|
int iNumOutSamples = 0;
|
||
|
ULONG ulOutCb = 0;
|
||
|
long lRateAdj = 0;
|
||
|
|
||
|
const void* pvSendBuff = pvBuff; // this is what we'll send to SAPI, unless Tsm/Vol changes it
|
||
|
|
||
|
SPDBG_ASSERT(pvBuff);
|
||
|
SPDBG_ASSERT(cb);
|
||
|
SPDBG_ASSERT(pfAbort);
|
||
|
|
||
|
if ( m_pOutputFormatId && *m_pOutputFormatId != SPDFID_Text )
|
||
|
{
|
||
|
iNumOutSamples = cb / m_pOutputFormat->nBlockAlign;
|
||
|
|
||
|
if ( m_pcBuffer )
|
||
|
{
|
||
|
// check for rate change
|
||
|
if ( m_pMainOutputSite->GetActions() & SPVES_RATE )
|
||
|
{
|
||
|
hr = m_pMainOutputSite->GetRate( &lRateAdj );
|
||
|
if ( SUCCEEDED(hr) && lRateAdj )
|
||
|
{
|
||
|
ComputeRateAdj(lRateAdj, &m_flRateAdj);
|
||
|
m_pTsm->SetScaleFactor( m_flRateAdj );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// real-time rate change
|
||
|
if ( SUCCEEDED(hr) && m_pTsm && m_flRateAdj != 1.0 )
|
||
|
{
|
||
|
int iTsmResult = 0;
|
||
|
|
||
|
if ( iNumOutSamples > m_pTsm->GetFrameLen() )
|
||
|
{
|
||
|
iTsmResult = m_pTsm->AdjustTimeScale(pvBuff, iNumOutSamples, &pvTsmBuff, &iNumOutSamples, m_pOutputFormat);
|
||
|
|
||
|
if ( iTsmResult == 0 )
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
// send the Tsm modified buff instead of the original
|
||
|
pvSendBuff = pvTsmBuff;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// real-time volume change
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
if ( m_pMainOutputSite->GetActions() & SPVES_VOLUME )
|
||
|
{
|
||
|
USHORT unVol = 0;
|
||
|
|
||
|
hr = m_pMainOutputSite->GetVolume( &unVol );
|
||
|
|
||
|
if ( SUCCEEDED(hr) && unVol != 100 )
|
||
|
{
|
||
|
m_flVol = ((float) unVol) / 100; // make dVol fractional
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_flVol != 1.0 )
|
||
|
{
|
||
|
hr = ApplyGain( pvSendBuff, &pvVolBuff, iNumOutSamples );
|
||
|
// Did ApplyGain need to create a new buffer?
|
||
|
if ( pvVolBuff )
|
||
|
{
|
||
|
// send the volume modified buff instead of the original
|
||
|
pvSendBuff = pvVolBuff;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} // if ( m_pcBuffer )
|
||
|
} // if ( m_pOutputFormatId && *m_pOutputFormatId != SPDFID_Text )
|
||
|
|
||
|
// stop speaking?
|
||
|
if ( m_pMainOutputSite->GetActions() & SPVES_ABORT )
|
||
|
{
|
||
|
*pfAbort = true;
|
||
|
}
|
||
|
|
||
|
if ( SUCCEEDED(hr) && !*pfAbort )
|
||
|
{
|
||
|
ULONG ulOutCb = 0;
|
||
|
m_ullBytesWritten += cb;
|
||
|
if ( m_pOutputFormatId && *m_pOutputFormatId != SPDFID_Text )
|
||
|
{
|
||
|
ulOutCb = iNumOutSamples * m_pOutputFormat->nBlockAlign;
|
||
|
m_ullBytesWrittenToSAPI += ulOutCb;
|
||
|
// send all the buffered events up to the current byte count
|
||
|
hr = SendEvents();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ulOutCb = cb;
|
||
|
m_ullBytesWrittenToSAPI += cb;
|
||
|
}
|
||
|
|
||
|
hr = m_pMainOutputSite->Write(pvSendBuff, ulOutCb, NULL);
|
||
|
}
|
||
|
|
||
|
if ( pvTsmBuff )
|
||
|
{
|
||
|
delete [] pvTsmBuff;
|
||
|
pvTsmBuff = NULL;
|
||
|
}
|
||
|
if ( pvVolBuff )
|
||
|
{
|
||
|
delete [] pvVolBuff;
|
||
|
pvVolBuff = NULL;
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::ComputeRateAdj
|
||
|
//
|
||
|
// Computes the rate multiplier.
|
||
|
//
|
||
|
/////////////////////////////////////////////////////// JOEM 11-2000 //
|
||
|
void CLocalTTSEngineSite::ComputeRateAdj(const long lRate, float* flRate)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::ComputeRateAdj" );
|
||
|
|
||
|
SPDBG_ASSERT(flRate);
|
||
|
|
||
|
if ( lRate < 0 )
|
||
|
{
|
||
|
if ( lRate < MIN_RATE )
|
||
|
{
|
||
|
*flRate = 1.0 / g_dRateScale[0 - MIN_RATE];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*flRate = 1.0 / g_dRateScale[0 - lRate];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( lRate > MAX_RATE )
|
||
|
{
|
||
|
*flRate = g_dRateScale[MAX_RATE];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*flRate = g_dRateScale[lRate];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// CLocalTTSEngineSite::ApplyGain
|
||
|
//
|
||
|
// Real-time volume adjustment.
|
||
|
//
|
||
|
////////////////////////////////////////////////////// JOEM 01-2001 //
|
||
|
STDMETHODIMP CLocalTTSEngineSite::ApplyGain(const void* pvInBuff, void** ppvOutBuff, const int iNumSamples)
|
||
|
{
|
||
|
SPDBG_FUNC( "CLocalTTSEngineSite::ApplyGain" );
|
||
|
HRESULT hr = S_OK;
|
||
|
int i = 0;
|
||
|
|
||
|
SPDBG_ASSERT(pvInBuff);
|
||
|
SPDBG_ASSERT(iNumSamples);
|
||
|
|
||
|
// Apply Volume
|
||
|
if ( SUCCEEDED(hr) && m_flVol != 1.0 )
|
||
|
{
|
||
|
long lGain = (long) ( m_flVol * (1 << g_iBase) );
|
||
|
|
||
|
if ( m_pOutputFormat->wFormatTag == WAVE_FORMAT_ALAW || m_pOutputFormat->wFormatTag == WAVE_FORMAT_MULAW )
|
||
|
{
|
||
|
short* pnBuff = NULL;
|
||
|
|
||
|
// need to convert samples
|
||
|
int iOriginalFormatType = VapiIO::TypeOf (m_pOutputFormat);
|
||
|
|
||
|
if ( iOriginalFormatType == -1 )
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
|
||
|
// Allocate the intermediate buffer
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
pnBuff = new short[iNumSamples];
|
||
|
if ( !pnBuff )
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
// Allocate the final (out) buffer
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
*ppvOutBuff = new char[iNumSamples * VapiIO::SizeOf(iOriginalFormatType)];
|
||
|
if ( !*ppvOutBuff )
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert to something we can use
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
if ( 0 == VapiIO::DataFormatConversion ((char *)pvInBuff, iOriginalFormatType, (char*)pnBuff, VAPI_PCM16, iNumSamples) )
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
// Apply gain
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
for ( i=0; i<iNumSamples; i++ )
|
||
|
{
|
||
|
pnBuff[i] = (short) ( ( pnBuff[i] * lGain ) >> g_iBase );
|
||
|
}
|
||
|
}
|
||
|
// convert it back (from intermediate buff to final out buff)
|
||
|
if ( SUCCEEDED(hr) )
|
||
|
{
|
||
|
if ( 0 == VapiIO::DataFormatConversion ((char *)pnBuff, VAPI_PCM16, (char*)*ppvOutBuff, iOriginalFormatType, iNumSamples) )
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pnBuff )
|
||
|
{
|
||
|
delete [] pnBuff;
|
||
|
pnBuff = NULL;
|
||
|
}
|
||
|
}
|
||
|
else if ( m_pOutputFormat->wFormatTag == WAVE_FORMAT_PCM )
|
||
|
{
|
||
|
// no converting necessary
|
||
|
switch ( m_pOutputFormat->nBlockAlign )
|
||
|
{
|
||
|
case 1:
|
||
|
for ( i=0; i<iNumSamples; i++ )
|
||
|
{
|
||
|
((char*)pvInBuff)[i] = (char) ( (((char*)pvInBuff)[i] * lGain) >> g_iBase );
|
||
|
}
|
||
|
break;
|
||
|
case 2:
|
||
|
for ( i=0; i<iNumSamples; i++ )
|
||
|
{
|
||
|
((short*)pvInBuff)[i] = (short) ( (((short*)pvInBuff)[i] * lGain) >> g_iBase );
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( FAILED(hr) )
|
||
|
{
|
||
|
if ( *ppvOutBuff )
|
||
|
{
|
||
|
delete [] *ppvOutBuff;
|
||
|
*ppvOutBuff = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SPDBG_REPORT_ON_FAIL( hr );
|
||
|
return hr;
|
||
|
}
|