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

883 lines
25 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/******************************************************************************
* CFmtConvert.cpp *
*-----------------*
* Functions of CFmtConvert class.
*------------------------------------------------------------------------------
* Copyright (C) 2000 Microsoft Corporation Date: 05/03/00
* All Rights Reserved
*
********************************************************************** DING ***/
#include "FmtConvert.h"
#include "sigproc.h"
#include "vapiIo.h"
#include <math.h>
#include <assert.h>
/*****************************************************************************
* Constructor *
*---------------*
* Description:
*
********************************************************************* DING ***/
CFmtConvert::CFmtConvert(double dHalfFilterLen)
{
m_fResetFilter = true;
m_pdFilterCoef = NULL;
m_pdLeftMemory = NULL;
m_pdRightMemory = NULL;
m_dHalfFilterLen = dHalfFilterLen;
}
/*****************************************************************************
* Destructor *
*---------------*
* Description:
*
********************************************************************* DING ***/
CFmtConvert::~CFmtConvert()
{
DeleteResamplingFilter();
DeleteBuffers();
}
/*****************************************************************************
* SetInputFormat *
*----------------------*
* Description:
*
********************************************************************* DING ***/
void CFmtConvert::SetInputFormat(WAVEFORMATEX* pUserWavFormat)
{
assert(pUserWavFormat);
assert(pUserWavFormat->nSamplesPerSec > 0 );
if ( pUserWavFormat )
{
m_InWavFormat = *pUserWavFormat;
m_fResetFilter = true;
}
}
/*****************************************************************************
* SetOutputFormat *
*----------------------*
* Description:
*
********************************************************************* DING ***/
void CFmtConvert::SetOutputFormat(WAVEFORMATEX* pUserWavFormat)
{
assert(pUserWavFormat);
if ( pUserWavFormat )
{
m_OutWavFormat = *pUserWavFormat;
m_fResetFilter = true;
}
}
/*****************************************************************************
* ConvertSamples *
*----------------------*
* Description:
* first read samples into VAPI_PCM16, then judge cases :
* 1. STEREO -> mono + resampling
* STEREO -> 1 mono -> reSampling
* 2. mono -> STEREO + resampling
* mono -> reSampling -> STEREO
* 3. STEREO -> STEREO + resampling
* STEREO -> 2 MONO - > reSampling -> 2 MONO -> STEREO
* 4. mono -> mono + resampling
* mono -> reSampling -> mono
*
********************************************************************* DING ***/
HRESULT CFmtConvert::ConvertSamples(const void* pvInSamples, int iInSampleLen,
void** ppvOutSamples, int* piOutSampleLen)
{
short* pnInSample = NULL;
short* pnOutSample = NULL;
short *pnBuff = NULL;
short *pnBuff2 = NULL;
double *pdBuff = NULL;
double *pdBuff1 = NULL;
int iLen;
int iNumSamples = iInSampleLen;
int iInFormatType;
int iOutFormatType;
HRESULT hr = S_OK;
assert( m_InWavFormat.nSamplesPerSec > 0 );
assert( m_InWavFormat.nChannels <= 2 );
assert( m_InWavFormat.nChannels > 0 );
assert( m_OutWavFormat.nChannels > 0 );
assert( m_OutWavFormat.nSamplesPerSec > 0 );
assert( m_OutWavFormat.nChannels <= 2 );
//--- need reset filter
if (m_fResetFilter)
{
DeleteResamplingFilter();
DeleteBuffers();
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
hr = CreateResamplingFilter(m_InWavFormat.nSamplesPerSec, m_OutWavFormat.nSamplesPerSec);
if (FAILED(hr))
{
return hr;
}
hr = CreateBuffers();
if (FAILED(hr))
{
return hr;
}
}
m_fResetFilter = false;
}
iInFormatType = VapiIO::TypeOf (&m_InWavFormat);
iOutFormatType = VapiIO::TypeOf (&m_OutWavFormat);
if ( iInFormatType < 0 || iOutFormatType < 0 )
{
return E_FAIL;
}
if ( m_OutWavFormat.nSamplesPerSec == m_InWavFormat.nSamplesPerSec && iOutFormatType == iInFormatType && m_OutWavFormat.nChannels == m_InWavFormat.nChannels )
{
*piOutSampleLen = iInSampleLen;
if ( (*ppvOutSamples = (void *)new char [*piOutSampleLen * VapiIO::SizeOf(iOutFormatType)]) == NULL )
{
return E_OUTOFMEMORY;
}
memcpy((char*)(*ppvOutSamples), (char*)pvInSamples, (*piOutSampleLen) * VapiIO::SizeOf(iOutFormatType));
return hr;
}
//--- Convert samples to VAPI_PCM16
if ((pnInSample = new short [iNumSamples]) == NULL)
{
return E_OUTOFMEMORY;
}
VapiIO::DataFormatConversion ((char *)pvInSamples, iInFormatType, (char*)pnInSample, VAPI_PCM16, iNumSamples);
//--- case 1
if ( m_InWavFormat.nChannels == 2 && m_OutWavFormat.nChannels == 1 )
{
Stereo2Mono(pnInSample, &iNumSamples, &pnOutSample);
delete[] pnInSample;
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
pdBuff1 = Short2Double (pnOutSample, iNumSamples);
delete[] pnOutSample;
if ( pdBuff1 == NULL )
{
return E_OUTOFMEMORY;
}
//--- resample
hr = Resampling(pdBuff1, iNumSamples, m_pdLeftMemory, &pdBuff, &iLen);
if ( FAILED(hr) )
{
return hr;
}
delete[] pdBuff1;
pnBuff = Double2Short (pdBuff, iLen);
delete[] pdBuff;
if ( pnBuff == NULL )
{
return E_OUTOFMEMORY;
}
iNumSamples = iLen;
}
else
{
if ( (pnBuff = new short [iNumSamples]) == NULL )
{
return E_OUTOFMEMORY;
}
memcpy(pnBuff, pnOutSample, iNumSamples * sizeof(*pnInSample));
delete[] pnOutSample;
}
}
//--- case 2
if ( m_InWavFormat.nChannels == 1 && m_OutWavFormat.nChannels == 2 )
{
//--- resampling
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
pdBuff1 = Short2Double (pnInSample, iNumSamples);
delete[] pnInSample;
if ( pdBuff1 == NULL )
{
return E_OUTOFMEMORY;
}
//--- resample
hr = Resampling(pdBuff1, iNumSamples, m_pdLeftMemory, &pdBuff, &iLen);
if ( FAILED(hr) )
{
return hr;
}
delete[] pdBuff1;
iNumSamples = iLen;
pnInSample = Double2Short (pdBuff, iLen);
delete[] pdBuff;
if ( pnInSample == NULL )
{
return E_OUTOFMEMORY;
}
}
//--- mono -> stereo
Mono2Stereo(pnInSample, &iNumSamples, &pnBuff);
delete[] pnInSample;
}
//--- case 3
if ( m_InWavFormat.nChannels == 2 && m_OutWavFormat.nChannels == 2 )
{
//--- resampling
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
SplitStereo(pnInSample, &iNumSamples, &pnBuff, &pnBuff2);
delete[] pnInSample;
// channel 1
pdBuff1 = Short2Double (pnBuff, iNumSamples);
delete[] pnBuff;
if ( pdBuff1 == NULL )
{
return E_OUTOFMEMORY;
}
//--- resample
hr = Resampling(pdBuff1, iNumSamples, m_pdLeftMemory, &pdBuff, &iLen);
if ( FAILED(hr) )
{
return hr;
}
delete[] pdBuff1;
pnInSample = Double2Short (pdBuff, iLen);
if ( pnInSample == NULL )
{
return E_OUTOFMEMORY;
}
delete[] pdBuff;
// channel 2
pdBuff1 = Short2Double (pnBuff2, iNumSamples);
delete[] pnBuff2;
if ( pdBuff1 == NULL )
{
return E_OUTOFMEMORY;
}
//--- resample
hr = Resampling(pdBuff1, iNumSamples, m_pdRightMemory, &pdBuff, &iLen);
if ( FAILED(hr) )
{
return hr;
}
delete[] pdBuff1;
iNumSamples = iLen;
pnOutSample = Double2Short (pdBuff, iNumSamples);
if ( pnOutSample == NULL )
{
return E_OUTOFMEMORY;
}
delete[] pdBuff;
MergeStereo(pnInSample, pnOutSample, &iNumSamples, &pnBuff);
delete[] pnInSample;
delete[] pnOutSample;
}
else
{
if ( (pnBuff = new short [iNumSamples]) == NULL )
{
return E_OUTOFMEMORY;
}
memcpy(pnBuff, pnInSample, iNumSamples * sizeof(*pnBuff));
delete[] pnInSample;
}
}
//--- case 4
if ( m_InWavFormat.nChannels == 1 && m_OutWavFormat.nChannels == 1 )
{
//--- resampling
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
pdBuff1 = Short2Double (pnInSample, iNumSamples);
delete[] pnInSample;
if ( pdBuff1 == NULL )
{
return E_OUTOFMEMORY;
}
//--- resample
hr = Resampling(pdBuff1, iNumSamples, m_pdLeftMemory, &pdBuff, &iLen);
if ( FAILED(hr) )
{
return hr;
}
delete[] pdBuff1;
iNumSamples = iLen;
pnBuff = Double2Short (pdBuff, iLen);
delete[] pdBuff;
if ( pnBuff == NULL )
{
return E_OUTOFMEMORY;
}
}
else
{
if ( (pnBuff = new short [iNumSamples]) == NULL )
{
return E_OUTOFMEMORY;
}
memcpy(pnBuff, pnInSample, iNumSamples * sizeof(*pnBuff));
delete[] pnInSample;
}
}
//--- output
if ( iOutFormatType < 0 )
{
iOutFormatType = iInFormatType;
}
*piOutSampleLen = iNumSamples;
//---Convert samples to output format
if ( (*ppvOutSamples = (void *) new char [iNumSamples * VapiIO::SizeOf(iOutFormatType)]) == NULL )
{
return E_OUTOFMEMORY;
}
VapiIO::DataFormatConversion((char*)pnBuff, VAPI_PCM16, (char*)*ppvOutSamples, iOutFormatType, iNumSamples);
delete[] pnBuff;
m_eChunkStatus = FMTCNVT_BLOCK;
return hr;
}
/*****************************************************************************
* FlushLastBuff *
*---------------------*
* Description:
*
********************************************************************* DING ***/
HRESULT CFmtConvert::FlushLastBuff(void** ppvOutSamples, int* piOutSampleLen)
{
short* pnInSample = NULL;
short* pnOutSample = NULL;
short *pnBuff = NULL;
double *pdBuff1 = NULL;
double *pdBuff2 = NULL;
int iBuffLen;
int iOutFormatType = VapiIO::TypeOf (&m_OutWavFormat);
HRESULT hr = S_OK;
assert( m_InWavFormat.nSamplesPerSec > 0 );
assert( m_InWavFormat.nChannels <= 2 );
assert( m_InWavFormat.nChannels > 0 );
assert( m_OutWavFormat.nChannels > 0 );
assert( m_OutWavFormat.nSamplesPerSec > 0 );
assert( m_OutWavFormat.nChannels <= 2 );
m_eChunkStatus = FMTCNVT_LAST;
if ( m_InWavFormat.nSamplesPerSec != m_OutWavFormat.nSamplesPerSec )
{
if ( m_InWavFormat.nChannels == 2 && m_OutWavFormat.nChannels == 2 )
{
hr = Resampling(pdBuff1, 0, m_pdLeftMemory, &pdBuff1, &iBuffLen);
if ( FAILED(hr) )
{
return hr;
}
hr = Resampling(pdBuff1, 0, m_pdRightMemory, &pdBuff2, &iBuffLen);
if ( FAILED(hr) )
{
return hr;
}
pnInSample = Double2Short (pdBuff1, iBuffLen);
if ( pnInSample == NULL )
{
return E_OUTOFMEMORY;
}
delete[] pdBuff1;
pnOutSample = Double2Short (pdBuff2, iBuffLen);
if ( pnOutSample == NULL )
{
return E_OUTOFMEMORY;
}
delete[] pdBuff2;
MergeStereo(pnInSample, pnOutSample, &iBuffLen, &pnBuff);
delete[] pnInSample;
delete[] pnOutSample;
}
else
{
hr = Resampling(pdBuff1, 0, m_pdLeftMemory, &pdBuff1, &iBuffLen);
if ( FAILED(hr) )
{
return hr;
}
pnBuff = Double2Short (pdBuff1, iBuffLen);
if ( pnBuff == NULL )
{
return E_OUTOFMEMORY;
}
delete[] pdBuff1;
if ( m_InWavFormat.nChannels == 1 && m_OutWavFormat.nChannels == 2 )
{
if ( (pnOutSample = new short [iBuffLen]) == NULL )
{
return E_OUTOFMEMORY;
}
memcpy((char *)pnOutSample, (char *)pnBuff, iBuffLen * sizeof(*pnBuff));
delete[] pnBuff;
Mono2Stereo(pnOutSample, &iBuffLen, &pnBuff);
delete[] pnOutSample;
}
}
*piOutSampleLen = iBuffLen;
//---Convert samples to output format
if ( (*ppvOutSamples = (void *) new char [iBuffLen * VapiIO::SizeOf(iOutFormatType)]) == NULL )
{
return E_OUTOFMEMORY;
}
VapiIO::DataFormatConversion((char*)pnBuff, VAPI_PCM16, (char*)*ppvOutSamples, iOutFormatType, iBuffLen);
delete[] pnBuff;
}
return hr;
}
/*****************************************************************************
* Short2Double *
*----------------------*
* Description:
* convert short array to double array
*
********************************************************************* DING ***/
double* CFmtConvert::Short2Double (short* pnIn, int iLen)
{
double* pdOut = NULL;
if ( (pdOut = new double [iLen]) != NULL )
{
for ( int i = 0; i < iLen; i++)
{
pdOut[i] = (double)pnIn[i];
}
}
return pdOut;
}
/*****************************************************************************
* Double2Short *
*----------------------*
* Description:
* convert double array to short array
*
********************************************************************* DING ***/
short* CFmtConvert::Double2Short (double* pdIn, int iLen)
{
short* pnOut = NULL;
if ( (pnOut = new short [iLen]) != NULL )
{
for ( int i = 0; i < iLen; i++)
{
pnOut[i] = (short)(pdIn[i] + 0.5);
}
}
return pnOut;
}
/*****************************************************************************
* Mono2Stereo *
*----------------------*
* Description:
* convert mono speech to stereo speech
*
********************************************************************* DING ***/
HRESULT CFmtConvert::Mono2Stereo (short* pnInSample, int* piNumSamples, short** ppnOutSample)
{
int iLen;
iLen = *piNumSamples;
if ( (*ppnOutSample = new short [iLen * 2]) == NULL )
{
return E_OUTOFMEMORY;
}
int k = 0;
for ( int i = 0; i < iLen; i++)
{
(*ppnOutSample)[k] = pnInSample[i];
(*ppnOutSample)[k + 1] = pnInSample[i];
k +=2;
}
*piNumSamples = 2 * iLen;
return S_OK;
}
/*****************************************************************************
* Stereo2Mono *
*----------------------*
* Description:
* convert stereo speech to mono speech
*
********************************************************************* DING ***/
HRESULT CFmtConvert::Stereo2Mono (short* pnInSample, int* piNumSamples, short** ppnOutSample)
{
int iLen = (*piNumSamples) / 2;
if ( (*ppnOutSample = new short [iLen]) == NULL )
{
return E_OUTOFMEMORY;
}
int k = 0;
for ( int i = 0;i < *piNumSamples; i += 2)
{
(*ppnOutSample)[k++] = (short)( (double)(pnInSample[i] + pnInSample[i + 1]) / 2.0 + 0.5);
}
*piNumSamples = iLen;
return S_OK;
}
/*****************************************************************************
* MergeStereo *
*----------------------*
* Description:
* merge 2 channel signals into one signal
*
********************************************************************* DING ***/
HRESULT CFmtConvert::MergeStereo (short* pnLeftSamples, short* pnRightSamples,
int *piNumSamples, short** ppnOutSamples)
{
int iLen = (*piNumSamples) * 2;
if ( (*ppnOutSamples = new short [iLen]) == NULL )
{
return E_OUTOFMEMORY;
}
int k = 0;
for ( int i = 0; i < *piNumSamples; i++)
{
(*ppnOutSamples)[k] = pnLeftSamples[i];
(*ppnOutSamples)[k + 1] = pnRightSamples[i];
k += 2;
}
*piNumSamples = iLen;
return S_OK;
}
/*****************************************************************************
* SplitStereo *
*----------------------*
* Description:
* split stereo signals into 2 channel mono signals
*
********************************************************************* DING ***/
HRESULT CFmtConvert::SplitStereo (short* pnInSample, int* piNumSamples,
short** ppnLeftSamples, short** ppnRightSamples)
{
int iLen = (*piNumSamples) / 2;
if ( (*ppnLeftSamples = new short [iLen]) == NULL )
{
return E_OUTOFMEMORY;
}
if ( (*ppnRightSamples = new short [iLen]) == NULL )
{
return E_OUTOFMEMORY;
}
int k = 0;
for ( int i = 0; i < *piNumSamples; i += 2)
{
(*ppnLeftSamples)[k] = pnInSample[i];
(*ppnRightSamples)[k] = pnInSample[i + 1];
k++;
}
*piNumSamples = iLen;
return S_OK;
}
/*****************************************************************************
* CreateResamplingFilter *
*------------------------*
* Description:
*
******************************************************************* DING ***/
HRESULT CFmtConvert::CreateResamplingFilter (int iInSampFreq, int iOutSampFreq)
{
int iLimitFactor;
assert (iInSampFreq > 0);
assert (iOutSampFreq > 0);
FindResampleFactors (iInSampFreq, iOutSampFreq);
iLimitFactor = (m_iUpFactor > m_iDownFactor) ? m_iUpFactor : m_iDownFactor;
m_iFilterHalf = (int)(iInSampFreq * iLimitFactor * m_dHalfFilterLen);
m_iFilterLen = 2 * m_iFilterHalf + 1;
if ( !(m_pdFilterCoef = WindowedLowPass(.5 / (double)iLimitFactor, (double)m_iUpFactor)))
{
return E_FAIL;
}
return S_OK;
}
/*****************************************************************************
* DeleteResamplingFilter *
*------------------------*
* Description:
*
******************************************************************* DING ***/
void CFmtConvert::DeleteResamplingFilter()
{
if ( m_pdFilterCoef )
{
delete[] m_pdFilterCoef;
m_pdFilterCoef = NULL;
}
}
/*****************************************************************************
* CreateBuffers *
*----------------*
* Description:
*
******************************************************************* DING ***/
HRESULT CFmtConvert::CreateBuffers()
{
assert(m_iUpFactor > 0);
m_iBuffLen = (int)( (double)m_iFilterLen / (double)m_iUpFactor);
if ( (m_pdLeftMemory = new double [m_iBuffLen]) == NULL )
{
return E_OUTOFMEMORY;
}
if ( (m_pdRightMemory = new double [m_iBuffLen]) == NULL )
{
return E_OUTOFMEMORY;
}
for ( int i = 0; i < m_iBuffLen; i++)
{
m_pdLeftMemory[i] = 0.0;
m_pdRightMemory[i] = 0.0;
}
m_eChunkStatus = FMTCNVT_FIRST; // first chunk
return S_OK;
}
/*****************************************************************************
* DeleteBuffers *
*----------------*
* Description:
*
******************************************************************* DING ***/
void CFmtConvert::DeleteBuffers()
{
if ( m_pdLeftMemory )
{
delete[] m_pdLeftMemory;
m_pdLeftMemory = NULL;
}
if ( m_pdRightMemory )
{
delete[] m_pdRightMemory;
m_pdRightMemory = NULL;
}
}
/*****************************************************************************
* WindowedLowPass *
*-----------------*
* Description:
* Creates a low pass filter using the windowing method.
* dCutOff is spec. in normalized frequency
******************************************************************* DING ***/
double* CFmtConvert::WindowedLowPass (double dCutOff, double dGain)
{
double* pdCoeffs = NULL;
double* pdWindow = NULL;
double dArg;
double dSinc;
assert (dCutOff>0.0 && dCutOff<0.5);
pdWindow = ComputeWindow(WINDOW_BLACK, m_iFilterLen, true);
if (!pdWindow)
{
return NULL;
}
pdCoeffs = new double[m_iFilterLen];
if (pdCoeffs)
{
dArg = 2.0 * M_PI * dCutOff;
pdCoeffs[m_iFilterHalf] = (double)(dGain * 2.0 * dCutOff);
for (long i = 1; i <= m_iFilterHalf; i++)
{
dSinc = dGain * sin(dArg * i) / (M_PI * i) * pdWindow[m_iFilterHalf- i];
pdCoeffs[m_iFilterHalf+i] = (double)dSinc;
pdCoeffs[m_iFilterHalf-i] = (double)dSinc;
}
}
delete[] pdWindow;
return pdCoeffs;
}
/*****************************************************************************
* FindResampleFactors *
*---------------------*
* Description:
*
******************************************************************* DING ***/
void CFmtConvert::FindResampleFactors (int iInSampFreq, int iOutSampFreq)
{
static int piPrimes[] = {2,3,5,7,11,13,17,19,23,29,31,37};
static int iPrimesLen = sizeof (piPrimes) / sizeof (piPrimes[0]);
int iDiv = 1;
int i;
while (iDiv)
{
iDiv = 0;
for (i = 0; i < iPrimesLen; i++)
{
if ( (iInSampFreq % piPrimes[i]) == 0 && (iOutSampFreq % piPrimes[i]) == 0 )
{
iInSampFreq /= piPrimes[i];
iOutSampFreq /= piPrimes[i];
iDiv = 1;
break;
}
}
}
m_iUpFactor = iOutSampFreq;
m_iDownFactor = iInSampFreq;
}
/*****************************************************************************
* Resampling *
*------------*
* Description:
*
******************************************************************* DING ***/
HRESULT CFmtConvert::Resampling (double* pdInSamples, int iInNumSamples, double *pdMemory,
double** ppdOutSamples, int* piOutNumSamples)
{
int iPhase;
double dAcum;
int j;
int n;
int iAddHalf;
if(m_eChunkStatus == FMTCNVT_FIRST)
{
*piOutNumSamples = (iInNumSamples * m_iUpFactor - m_iFilterHalf) / m_iDownFactor;
iAddHalf = 1;
}
else if(m_eChunkStatus == FMTCNVT_BLOCK)
{
*piOutNumSamples = (iInNumSamples * m_iUpFactor) / m_iDownFactor;
iAddHalf = 2;
}
else if(m_eChunkStatus == FMTCNVT_LAST)
{
*piOutNumSamples = (m_iFilterHalf * m_iUpFactor) / m_iDownFactor;
iAddHalf = 2;
}
*ppdOutSamples = new double[*piOutNumSamples];
if (*ppdOutSamples == NULL)
{
return E_FAIL;
}
for (int i = 0; i < *piOutNumSamples; i++)
{
dAcum = 0.0;
n = (int)((i * m_iDownFactor - iAddHalf * m_iFilterHalf) / (double)m_iUpFactor);
iPhase = (i * m_iDownFactor) - ( n * m_iUpFactor + iAddHalf * m_iFilterHalf);
for ( j = 0; j < m_iFilterLen / m_iUpFactor; j++)
{
if (m_iUpFactor * j > iPhase)
{
if ( n + j >= 0 && n + j < iInNumSamples)
{
dAcum += pdInSamples[n + j] * m_pdFilterCoef[m_iUpFactor * j - iPhase];
}
else if ( n + j < 0 )
{
dAcum += pdMemory[m_iBuffLen + n + j] * m_pdFilterCoef[m_iUpFactor * j - iPhase];
}
}
}
(*ppdOutSamples)[i] = dAcum;
}
//--- store samples into buffer
if(m_eChunkStatus != FMTCNVT_LAST)
{
for (n = 0, i = 0; i < m_iBuffLen; i++)
{
if (i + iInNumSamples >= m_iBuffLen)
{
pdMemory[i] = pdInSamples[n++];
}
else
{
pdMemory[i] = 0.0;
}
}
}
return S_OK;
}