//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// synth.cpp
//
// Audio Signal Generator Source Filter


#include <windows.h>
#include <streams.h>
#include <math.h>

#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif


#define _AUDIOSYNTH_IMPLEMENTATION_

#include "isynth.h"
#include "synth.h"
#include "synthprp.h"

// setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{ &MEDIATYPE_Audio      // clsMajorType
, &MEDIASUBTYPE_NULL }; // clsMinorType

const AMOVIESETUP_PIN sudOpPin =
{ L"Output"          // strName
, FALSE              // bRendered
, TRUE               // bOutput
, FALSE              // bZero
, FALSE              // bMany
, &CLSID_NULL        // clsConnectsToFilter
, L"Input"           // strConnectsToPin
, 1                  // nTypes
, &sudOpPinTypes };  // lpTypes

const AMOVIESETUP_FILTER sudSynth =
{ &CLSID_SynthFilter     // clsID
, L"Audio Synthesizer" // strName
, MERIT_UNLIKELY       // dwMerit
, 1                    // nPins
, &sudOpPin };         // lpPin

// -------------------------------------------------------------------------
// g_Templates
// -------------------------------------------------------------------------
// COM global table of objects in this dll

CFactoryTemplate g_Templates[] = {

    { L"Audio Synthesizer"
    , &CLSID_SynthFilter
    , CSynthFilter::CreateInstance
    , NULL
    , &sudSynth }
  ,
    { L"Audio Synthesizer Property Page"
    , &CLSID_SynthPropertyPage
    , CSynthProperties::CreateInstance }

};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

// -------------------------------------------------------------------------
// CSynthFilter, the main filter object
// -------------------------------------------------------------------------
//
// CreateInstance
//
// The only allowed way to create Synthesizers

CUnknown * WINAPI CSynthFilter::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr) {

    CUnknown *punk = new CSynthFilter(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return punk;
}

//
// CSynthFilter::Constructor
//
// initialise a CSynthStream object so that we have a pin.

CSynthFilter::CSynthFilter(LPUNKNOWN lpunk, HRESULT *phr)
    : CSource(NAME("Audio Synthesizer Filter"),lpunk, CLSID_SynthFilter)
    , CPersistStream(lpunk, phr)
{
    CAutoLock l(&m_cStateLock);

    m_paStreams    = (CSourceStream **) new CSynthStream*[1];
    if (m_paStreams == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    m_paStreams[0] = new CSynthStream(phr, this, L"Audio Synth Stream");
    if (m_paStreams[0] == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    if (SUCCEEDED(*phr)) {
	ASSERT(m_Synth);
	m_Channels = 1;
	m_SamplesPerSec = 11025;
	m_BitsPerSample = 8;
	m_Synth->put_SynthFormat(m_Channels, m_BitsPerSample, m_SamplesPerSec);
    }
}

//
// CSynthFilter::Destructor
//
CSynthFilter::~CSynthFilter(void) {

    //
    //  Base class will free our pins
    //
}

//
// NonDelegatingQueryInterface
//
// Reveal our property page, persistance, and control interfaces

STDMETHODIMP CSynthFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CAutoLock l(&m_cStateLock);

    if (riid == IID_ISynth) {
        return GetInterface((ISynth *) this, ppv);
    }
    else if (riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *) this, ppv);
    }
    else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else {
        return CSource::NonDelegatingQueryInterface(riid, ppv);
    }
}


//
// GetPages
//
STDMETHODIMP CSynthFilter::GetPages(CAUUID * pPages) {

    CAutoLock l(&m_cStateLock);

    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_SynthPropertyPage;

    return NOERROR;

}

// -------------------------------------------------------------------------
// --- IPersistStream ---
// -------------------------------------------------------------------------

#define WRITEOUT(var)   hr = pStream->Write(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

#define READIN(var)     hr = pStream->Read(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

STDMETHODIMP CSynthFilter::GetClassID(CLSID *pClsid)
{
    return CBaseFilter::GetClassID(pClsid);
}

int CSynthFilter::SizeMax ()
{
    return sizeof (int) * 8;
}

HRESULT CSynthFilter::WriteToStream(IStream *pStream)
{
    HRESULT hr;
    int i, k;

    get_Frequency (&i);  // don't we wish we'd used a structure, now?
    WRITEOUT(i);
    get_Waveform (&i);
    WRITEOUT(i);
    get_Channels (&i);
    WRITEOUT(i);
    get_BitsPerSample (&i);
    WRITEOUT(i);
    get_SamplesPerSec (&i);
    WRITEOUT(i);
    get_Amplitude (&i);
    WRITEOUT(i);
    get_SweepRange (&i, &k);
    WRITEOUT(i);
    WRITEOUT(k);

    return hr;
}


HRESULT CSynthFilter::ReadFromStream(IStream *pStream)
{
    HRESULT hr;
    int i, k;

    READIN(i);
    put_Frequency(i);
    READIN(i);
    put_Waveform (i);
    READIN(i);
    put_Channels (i);
    READIN(i);
    put_BitsPerSample (i);
    READIN(i);
    put_SamplesPerSec (i);
    READIN(i);
    put_Amplitude (i);
    READIN(i);
    READIN(k);
    put_SweepRange (i, k);

    return hr;
}

// -------------------------------------------------------------------------
// ISynth, the control interface for the synthesizer
// -------------------------------------------------------------------------

//
// get_Frequency
//
STDMETHODIMP CSynthFilter::get_Frequency(int *Frequency) {

    m_Synth->get_Frequency(Frequency);

    DbgLog((LOG_TRACE, 3, TEXT("get_Frequency: %d"), *Frequency));

    return NOERROR;
}


//
// put_Frequency
//
STDMETHODIMP CSynthFilter::put_Frequency(int Frequency) {

    m_Synth->put_Frequency (Frequency);

    DbgLog((LOG_TRACE, 3, TEXT("put_Frequency: %d"), Frequency));

    return NOERROR;
}

//
// get_Waveform
//
STDMETHODIMP CSynthFilter::get_Waveform(int *Waveform) {

    m_Synth->get_Waveform (Waveform);

    DbgLog((LOG_TRACE, 3, TEXT("get_Waveform: %d"), *Waveform));

    return NOERROR;
}


//
// put_Waveform
//
STDMETHODIMP CSynthFilter::put_Waveform(int Waveform) {

    m_Synth->put_Waveform (Waveform);

    DbgLog((LOG_TRACE, 3, TEXT("put_Waveform: %d"), Waveform));

    return NOERROR;
}

//
// get_Channels
//
STDMETHODIMP CSynthFilter::get_Channels(int *Channels) {

    *Channels = m_Channels;

    DbgLog((LOG_TRACE, 3, TEXT("get_Channels: %d"), *Channels));

    return NOERROR;
}

//
// If the format changes, we need to reconnect
//
void CSynthFilter::ReconnectWithNewFormat(void) {

//    CAutoLock l(&m_SynthLock);

    HRESULT hr;
    FILTER_STATE  State;

    CBasePin *pPin = GetPin(0);

    // Get the state and confirm that the graph is stopped
    GetState (0, &State);
    if (State != State_Stopped && pPin->GetConnected()) {
	// let's attempt a dynamic connection
	CMediaType mtNew;

	// !!! better way to get back to a CSynthStream???
	CSynthStream * pStream = (CSynthStream *) pPin;
	pStream->GetMediaType(&mtNew);

	// !!! does this really mean they'll accept a dynamic format change?
	hr = pPin->GetConnected()->QueryAccept(&mtNew);

	DbgLog((LOG_TRACE,2,TEXT("Attempting format change: queryAccept returned %x"), hr));

	if (hr == S_OK) {
	    // actually change what's being pushed
	    m_Synth->put_SynthFormat(m_Channels, m_BitsPerSample, m_SamplesPerSec);
	} else {
	    // !!! couldn't change right now, we should really schedule a reconnect
	    // for the next time the graph is stopped.
	}
	
        return;
    }

    if (!m_pGraph)
        return;

    hr = GetFilterGraph()->Reconnect (pPin);       // Renegotiate the format
    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, 1, TEXT("Reconnect failed, err=%x"), hr));
        return;
    }
}


//
// put_Channels
//
STDMETHODIMP CSynthFilter::put_Channels(int Channels) {

    m_Channels = Channels;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_Channels: %d"), Channels));

    return NOERROR;
}

//
// get_BitsPerSample
//
STDMETHODIMP CSynthFilter::get_BitsPerSample(int *BitsPerSample) {

    *BitsPerSample = m_BitsPerSample;

    DbgLog((LOG_TRACE, 3, TEXT("get_BitsPerSample: %d"), *BitsPerSample));

    return NOERROR;
}


//
// put_BitsPerSample
//
STDMETHODIMP CSynthFilter::put_BitsPerSample(int BitsPerSample) {

    m_BitsPerSample = BitsPerSample;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_BitsPerSample: %d"), BitsPerSample));

    return NOERROR;
}

//
// get_SamplesPerSec
//
STDMETHODIMP CSynthFilter::get_SamplesPerSec(int *SamplesPerSec) {

    *SamplesPerSec = m_SamplesPerSec;

    DbgLog((LOG_TRACE, 3, TEXT("get_SamplesPerSec: %d"), *SamplesPerSec));

    return NOERROR;
}


//
// put_SamplesPerSec
//
STDMETHODIMP CSynthFilter::put_SamplesPerSec(int SamplesPerSec) {

    m_SamplesPerSec = SamplesPerSec;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_SamplesPerSec: %d"), SamplesPerSec));

    return NOERROR;
}

//
// get_Amplitude
//
STDMETHODIMP CSynthFilter::get_Amplitude(int *Amplitude) {

    m_Synth->get_Amplitude (Amplitude);

    DbgLog((LOG_TRACE, 3, TEXT("get_Amplitude: %d"), *Amplitude));

    return NOERROR;
}


//
// put_Amplitude
//
STDMETHODIMP CSynthFilter::put_Amplitude(int Amplitude) {

    m_Synth->put_Amplitude (Amplitude);

    DbgLog((LOG_TRACE, 3, TEXT("put_Amplitude: %d"), Amplitude));

    return NOERROR;
}


//
// get_SweepRange
//
STDMETHODIMP CSynthFilter::get_SweepRange(int *SweepStart, int *SweepEnd) {

    m_Synth->get_SweepRange (SweepStart, SweepEnd);

    DbgLog((LOG_TRACE, 3, TEXT("get_SweepStart: %d %d"), *SweepStart, *SweepEnd));

    return NOERROR;
}


//
// put_SweepRange
//
STDMETHODIMP CSynthFilter::put_SweepRange(int SweepStart, int SweepEnd) {

    m_Synth->put_SweepRange (SweepStart, SweepEnd);

    DbgLog((LOG_TRACE, 3, TEXT("put_SweepRange: %d %d"), SweepStart, SweepEnd));

    return NOERROR;
}


// -------------------------------------------------------------------------
// CSynthStream, the output pin
// -------------------------------------------------------------------------

//
// CSynthStream::Constructor
//

CSynthStream::CSynthStream(HRESULT *phr, CSynthFilter *pParent, LPCWSTR pName)
    : CSourceStream(NAME("Audio Synth output pin"),phr, pParent, pName) {

    CAutoLock l(m_pFilter->pStateLock());

    {
        CAutoLock l(&m_cSharedState);

        m_Synth = new CAudioSynth( );
        pParent->m_Synth = m_Synth;
        if (m_Synth == NULL) {
            *phr = E_OUTOFMEMORY;
            return;
        }
	m_pParent = pParent;
    }
}


//
// CSynthStream::Destructor
//
CSynthStream::~CSynthStream(void) {

    CAutoLock l(&m_cSharedState);

    delete m_Synth;
}


//
// FillBuffer
//
// Stuffs the buffer with data
HRESULT CSynthStream::FillBuffer(IMediaSample *pms) {

    BYTE *pData;
    long lDataLen;
    int nSamplesPerSec;
    int nBitsPerSample;
    int nChannels;
    BOOL fNewFormat = FALSE;

    pms->GetPointer(&pData);
    lDataLen = pms->GetSize();

    CAutoLock lShared(&m_cSharedState);
    m_Synth->FillAudioBuffer (pData, lDataLen, &fNewFormat);

    if (fNewFormat) {
	CMediaType mtNew;
	GetMediaType(&mtNew);

	pms->SetMediaType(&mtNew);

	DbgLog((LOG_TRACE,2,TEXT("Sending buffer with new media type")));
    }

    CRefTime rtStart  = m_rtSampleTime;  // the current time is the sample's start

    m_Synth->get_SamplesPerSec (&nSamplesPerSec);
    m_Synth->get_BitsPerSample (&nBitsPerSample);
    m_Synth->get_Channels (&nChannels);

    m_rtSampleTime += (UNITS * lDataLen /
            (nSamplesPerSec * nChannels * nBitsPerSample / 8));

    pms->SetTime((REFERENCE_TIME*)&rtStart,
                 (REFERENCE_TIME*)&m_rtSampleTime);

    return NOERROR;
}


//
// Format Support
//

//
// GetMediaType
//
HRESULT CSynthStream::GetMediaType(CMediaType *pmt) {

    CAutoLock l(m_pFilter->pStateLock());

    WAVEFORMATEX *pwf = (WAVEFORMATEX *) pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));

    pwf->wFormatTag = WAVE_FORMAT_PCM;
    pwf->nChannels = (WORD) m_pParent->m_Channels;
    pwf->nSamplesPerSec = (DWORD) m_pParent->m_SamplesPerSec;
    pwf->wBitsPerSample = (WORD) m_pParent->m_BitsPerSample;
    pwf->nBlockAlign = pwf->wBitsPerSample * pwf->nChannels / 8;
    pwf->nAvgBytesPerSec = (int) ((DWORD) pwf->nBlockAlign *
                           pwf->nSamplesPerSec);
    pwf->cbSize = 0;

    return CreateAudioMediaType(pwf, pmt, FALSE);
}


//
// CheckMediaType
//
// Returns E_INVALIDARG if the mediatype is not acceptable, S_OK if it is
HRESULT CSynthStream::CheckMediaType(const CMediaType *pMediaType) {

    CAutoLock l(m_pFilter->pStateLock());

    //  Check that's Audio and that the format block
    //  has the WAVEFORMATEX structure (indicated by a format type
    //  GUID of FORMAT_WaveFormatEx)

    if ((*pMediaType->Type() != MEDIATYPE_Audio) ||
        (*pMediaType->FormatType() != FORMAT_WaveFormatEx))
        return E_INVALIDARG;

    WAVEFORMATEX * pwfx  = (WAVEFORMATEX *)pMediaType->Format();

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
	return E_INVALIDARG;

    // !!! check 8/16, 1/2 channel

    // Check for the subtypes we support

    // Get the format area of the media type

    // !!! if we're going to allow arbitrary media types here, we have to actually
    // look at SetMediaType to see what we've agreed on!

    return S_OK;  // This format is acceptable.
}

//
// DecideBufferSize
//
// This will always be called after the format has been sucessfully
// negotiated. So we have a look at m_mt to see what format we agreed to.
// Then we can ask for buffers of the correct size to contain them.
HRESULT CSynthStream::DecideBufferSize(IMemAllocator *pAlloc,
                                       ALLOCATOR_PROPERTIES *pProperties)
{
    CAutoLock l(m_pFilter->pStateLock());
    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cbBuffer = WaveBufferSize;

    int nBitsPerSample;
    int nSamplesPerSec;
    int nChannels;

    m_Synth->get_SamplesPerSec (&nSamplesPerSec);
    m_Synth->get_BitsPerSample (&nBitsPerSample);
    m_Synth->get_Channels (&nChannels);

    pProperties->cBuffers = nChannels * (nSamplesPerSec / pProperties->cbBuffer) * (nBitsPerSample / 8);
    // Get 1/2 second worth of buffers
    pProperties->cBuffers /= 2;
    if (pProperties->cBuffers < 1)
        pProperties->cBuffers = 1 ;

    // Ask the allocator to reserve us the memory

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    // Is this allocator unsuitable

    if (Actual.cbBuffer < pProperties->cbBuffer) {
        return E_FAIL;
    }
    return NOERROR;
}


//
// SetMediaType
//
// Overriden from CBasePin.
HRESULT CSynthStream::SetMediaType(const CMediaType *pMediaType) {

    CAutoLock l(m_pFilter->pStateLock());

    HRESULT hr;         // return code from base class calls

    // Pass the call up to my base class
    hr = CSourceStream::SetMediaType(pMediaType);
    if (SUCCEEDED(hr))
        return NOERROR;
    else
        return hr;

}


//
// OnThreadCreate
//
// as we go active reset the stream time to zero
HRESULT CSynthStream::OnThreadCreate(void) {

    CAutoLock lShared(&m_cSharedState);

    m_rtSampleTime = 0;

    return NOERROR;
}

//
// Active
//
// Send a message to the property page telling it to disable
// buttons which change the format when the graph starts running
HRESULT CSynthStream::Active  (void) {
    m_Synth->AllocWaveCache();

    return CSourceStream::Active();
}


//
// Inactive
//
// Send a message to the property page telling it to enable
// buttons which change the format when the graph stops running
HRESULT CSynthStream::Inactive  (void) {
    return CSourceStream::Inactive();
}

// -------------------------------------------------------------------------
// CAudioSynth
// -------------------------------------------------------------------------
// Object that knows nothing about ActiveMovie, but just synthesizes
// waveforms

CAudioSynth::CAudioSynth(
                int Frequency,
                int Waveform,
                int iBitsPerSample,
                int iChannels,
                int iSamplesPerSec,
                int iAmplitude
                )
    : m_bWaveCache(NULL),
      m_wWaveCache(NULL)
{

    ASSERT(Waveform >= WAVE_SINE);
    ASSERT(Waveform <  WAVE_LAST);

    m_iFrequency = Frequency;
    m_iWaveform = Waveform;
    m_iAmplitude = iAmplitude;
    m_iSweepStart = DefaultSweepStart;
    m_iSweepEnd = DefaultSweepEnd;

    // init our WAVEFORMATEX structure
    wfex.wFormatTag = WAVE_FORMAT_PCM;
    wfex.wBitsPerSample = iBitsPerSample;
    wfex.nChannels = iChannels;
    wfex.nSamplesPerSec = iSamplesPerSec;
    wfex.nBlockAlign = wfex.wBitsPerSample * wfex.nChannels / 8;
    wfex.nAvgBytesPerSec = ((DWORD) wfex.nBlockAlign *
                           wfex.nSamplesPerSec);
    wfex.cbSize = 0;
}

CAudioSynth::~CAudioSynth()
{
    if (m_bWaveCache) {
        delete[] m_bWaveCache;
    }

    if (m_wWaveCache) {
        delete[] m_wWaveCache;
    }
}

//
// AllocWaveCache
//
//
void CAudioSynth::AllocWaveCache (void) {
    wfexLast = wfex;

    m_iWaveCacheCycles = m_iFrequency;
    m_iWaveCacheSize = (int) wfex.nSamplesPerSec;

    m_iFrequencyLast = 0;	// force cache contents invalid

    if (m_bWaveCache) {
        delete[] m_bWaveCache;
        m_bWaveCache = NULL;
    }
    if (m_wWaveCache) {
        delete[] m_wWaveCache;
        m_wWaveCache = NULL;
    }

    if (wfex.wBitsPerSample == 8)
        m_bWaveCache = new BYTE [m_iWaveCacheSize];
    else
        m_wWaveCache = new WORD [m_iWaveCacheSize];
}

//
// FillAudioBuffer
//
//
//
void CAudioSynth::FillAudioBuffer (BYTE pBuf[], int iSize, BOOL *pfNewFormat) {
    BOOL fCalcCache = FALSE;

    CAutoLock l(&m_SynthLock);

    // Only realloc the cache if the format has changed !

    if ((wfex.nChannels != wfexLast.nChannels) ||
        (wfex.wBitsPerSample != wfexLast.wBitsPerSample) ||
        (wfex.nSamplesPerSec != wfexLast.nSamplesPerSec)) {
	    *pfNewFormat = TRUE;
            fCalcCache = TRUE;
	    AllocWaveCache();
    }

    if (m_iFrequency != m_iFrequencyLast) {
	fCalcCache = TRUE;
	m_iFrequencyLast = m_iFrequency;
    }
    if (m_iWaveform != m_iWaveformLast) {
	fCalcCache = TRUE;
	m_iWaveformLast = m_iWaveform;
    }
    if (m_iAmplitude != m_iAmplitudeLast) {
	fCalcCache = TRUE;
	m_iAmplitudeLast = m_iAmplitude;
    }

    if (fCalcCache) {
        switch (m_iWaveform) {

        case WAVE_SINE:
                CalcCacheSine ();
                break;

        case WAVE_SQUARE:
                CalcCacheSquare ();
                break;

        case WAVE_SAWTOOTH:
                CalcCacheSawtooth ();
                break;

        case WAVE_SINESWEEP:
                CalcCacheSweep ();
                break;

        }
    }

    // Copy cache to output buffers
    if (wfex.wBitsPerSample == 8 && wfex.nChannels == 1) {
        while (iSize--) {
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 8 && wfex.nChannels == 2) {
        iSize /= 2;
        while (iSize--) {
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex];
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 16 && wfex.nChannels == 1) {
        WORD * pW = (WORD *) pBuf;
        iSize /= 2;
        while (iSize--) {
            *pW++ = m_wWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 16 && wfex.nChannels == 2) {
        WORD * pW = (WORD *) pBuf;
        iSize /= 4;
        while (iSize--) {
            *pW++ = m_wWaveCache[m_iWaveCacheIndex];
            *pW++ = m_wWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }

}

//
// CalcCacheSine
//
//
void CAudioSynth::CalcCacheSine (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;

    amplitude = ((wfex.wBitsPerSample == 8) ? 127 : 32767 )
                    * m_iAmplitude / 100;

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pB++ = (BYTE) (sin (d) * amplitude) + 128;
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pW++ = (WORD) (sin (d) * amplitude);
        }
    }

}

//
// CalcCacheSquare
//
//
void CAudioSynth::CalcCacheSquare (void) {

    int i;
    double d;
    double FTwoPIDivSpS;
    BYTE b0, b1;
    WORD w0, w1;

    b0 = (BYTE) 128 - (127 * m_iAmplitude / 100);
    b1 = (BYTE) 128 + (127 * m_iAmplitude / 100);
    w0 = (WORD) (32767. * m_iAmplitude / 100);
    w1 = (WORD) - (32767. * m_iAmplitude / 100);

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pB++ = (BYTE) ((sin (d) >= 0) ? b1 : b0);
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pW++ = (WORD) ((sin (d) >= 0) ? w1 : w0);
        }
    }
}

//
// CalcCacheSawtooth
//
void CAudioSynth::CalcCacheSawtooth (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;
    double step;
    double curstep;
    BOOL fLastWasNeg = TRUE;
    BOOL fPositive;

    amplitude = ((wfex.wBitsPerSample == 8) ? 255 : 65535 )
                    * m_iAmplitude / 100;

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;
    step = amplitude * m_iFrequency / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    BYTE * pB = m_bWaveCache;
    PWORD pW = (PWORD) m_wWaveCache;

    for (i = 0; i < m_iWaveCacheSize; i++) {
        d = FTwoPIDivSpS * i;

        // OneShot triggered on positive zero crossing
        fPositive = (sin (d) >= 0);

        if (fLastWasNeg && fPositive) {
            if (wfex.wBitsPerSample == 8)
                curstep = 128 - amplitude / 2;
            else
                curstep = 32768 - amplitude / 2;
        }
        fLastWasNeg = !fPositive;

        if (wfex.wBitsPerSample == 8)
            *pB++ = (BYTE) curstep;
        else
            *pW++ = (WORD) (-32767 + curstep);

        curstep += step;
    }
}

//
// CalcCacheSweep
//
void CAudioSynth::CalcCacheSweep (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;
    double CurrentFreq;
    double DeltaFreq;

    amplitude = ((wfex.wBitsPerSample == 8) ? 127 : 32767 )
                    * m_iAmplitude / 100;

    DeltaFreq = ((double) m_iSweepEnd - m_iSweepStart) / m_iWaveCacheSize;
    CurrentFreq = m_iSweepStart;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;
        d = 0.0;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            FTwoPIDivSpS = (int) CurrentFreq * TWOPI / wfex.nSamplesPerSec;
            CurrentFreq += DeltaFreq;
            d += FTwoPIDivSpS;
            *pB++ = (BYTE) (sin (d) * amplitude) + 128;
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;
        d = 0.0;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            FTwoPIDivSpS = (int) CurrentFreq * TWOPI / wfex.nSamplesPerSec;
            CurrentFreq += DeltaFreq;
            d += FTwoPIDivSpS;
            *pW++ = (WORD) (sin (d) * amplitude);
        }
    }
}

//
// get_Frequency
//
STDMETHODIMP CAudioSynth::get_Frequency(int *Frequency) {

    *Frequency = m_iFrequency;

    DbgLog((LOG_TRACE, 3, TEXT("get_Frequency: %d"), *Frequency));

    return NOERROR;
}


//
// put_Frequency
//
STDMETHODIMP CAudioSynth::put_Frequency(int Frequency) {

    CAutoLock l(&m_SynthLock);

    m_iFrequency = Frequency;

    DbgLog((LOG_TRACE, 3, TEXT("put_Frequency: %d"), Frequency));

    return NOERROR;
}

//
// get_Waveform
//
STDMETHODIMP CAudioSynth::get_Waveform(int *Waveform) {

    *Waveform = m_iWaveform;

    DbgLog((LOG_TRACE, 3, TEXT("get_Waveform: %d"), *Waveform));

    return NOERROR;
}


//
// put_Waveform
//
STDMETHODIMP CAudioSynth::put_Waveform(int Waveform) {

    CAutoLock l(&m_SynthLock);

    m_iWaveform = Waveform;

    DbgLog((LOG_TRACE, 3, TEXT("put_Waveform: %d"), Waveform));

    return NOERROR;
}

//
// get_Channels
//
STDMETHODIMP CAudioSynth::get_Channels(int *Channels) {

    *Channels = wfex.nChannels;

    DbgLog((LOG_TRACE, 3, TEXT("get_Channels: %d"), *Channels));

    return NOERROR;
}


//
// get_BitsPerSample
//
STDMETHODIMP CAudioSynth::get_BitsPerSample(int *BitsPerSample) {

    *BitsPerSample = wfex.wBitsPerSample;

    DbgLog((LOG_TRACE, 3, TEXT("get_BitsPerSample: %d"), *BitsPerSample));

    return NOERROR;
}


//
// get_SamplesPerSec
//
STDMETHODIMP CAudioSynth::get_SamplesPerSec(int *SamplesPerSec) {

    *SamplesPerSec = wfex.nSamplesPerSec;

    DbgLog((LOG_TRACE, 3, TEXT("get_SamplesPerSec: %d"), *SamplesPerSec));

    return NOERROR;
}

//
// put_SynthFormat
//
STDMETHODIMP CAudioSynth::put_SynthFormat(int Channels, int BitsPerSample,
					  int SamplesPerSec) {

    CAutoLock l(&m_SynthLock);

    wfex.nChannels = Channels;

    wfex.wBitsPerSample = BitsPerSample;

    wfex.nSamplesPerSec = SamplesPerSec;

    DbgLog((LOG_TRACE, 1, TEXT("put_SynthFormat: %d-bit %d-channel %dHz"),
	    BitsPerSample, Channels, SamplesPerSec));

    return NOERROR;
}


//
// get_Amplitude
//
STDMETHODIMP CAudioSynth::get_Amplitude(int *Amplitude) {

    *Amplitude =  m_iAmplitude;

    DbgLog((LOG_TRACE, 3, TEXT("get_Amplitude: %d"), *Amplitude));

    return NOERROR;
}


//
// put_Amplitude
//
STDMETHODIMP CAudioSynth::put_Amplitude(int Amplitude) {

    CAutoLock l(&m_SynthLock);

    if (Amplitude > MaxAmplitude || Amplitude < MinAmplitude)
        return E_INVALIDARG;

    m_iAmplitude = Amplitude;

    DbgLog((LOG_TRACE, 3, TEXT("put_Amplitude: %d"), Amplitude));

    return NOERROR;
}


//
// get_SweepRange
//
STDMETHODIMP CAudioSynth::get_SweepRange(int *SweepStart, int *SweepEnd) {

    *SweepStart = m_iSweepStart;
    *SweepEnd = m_iSweepEnd;

    DbgLog((LOG_TRACE, 3, TEXT("get_SweepStart: %d %d"), *SweepStart, *SweepEnd));

    return NOERROR;
}


//
// put_SweepRange
//
STDMETHODIMP CAudioSynth::put_SweepRange(int SweepStart, int SweepEnd) {

    CAutoLock l(&m_SynthLock);

    m_iSweepStart = SweepStart;
    m_iSweepEnd = SweepEnd;

    DbgLog((LOG_TRACE, 3, TEXT("put_SweepRange: %d %d"), SweepStart, SweepEnd));

    return NOERROR;
}

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}

