windows-nt/Source/XPSP1/NT/enduser/speech/tts/common/localttsenginesite/localttsenginesite.cpp

1264 lines
41 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/////////////////////////////////////////////////////////////////////////////
// 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;
}