SCOPE.CPP
//==========================================================================; 
// 
//  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. 
// 
//--------------------------------------------------------------------------; 
 
#include <streams.h> 
#include <commctrl.h> 
#include <mmsystem.h> 
#include <initguid.h> 
#include <wxdebug.h> 
#include "scope.h" 
#include "resource.h" 
 
// 
// 
// What this sample illustrates 
// 
// An audio oscilloscope that shows the waveform graphically as the audio is 
// received by the filter. The filter is a renderer that can put where ever 
// the normal runtime renderer goes. We have a single input pin that accepts 
// a number of difference audio formats and renders the data as appropriate. 
// 
// 
// Summary 
// 
// This is an audio oscilloscope renderer - we are basicly an audio renderer 
// When we are created we also create a class to look after the scope window 
// whose constructor creates a worker thread, when it is destroyed it will 
// also terminate the worker thread. On that worker thread a window is looked 
// after that shows the audio waveform for data sent to us. The data is kept 
// in a circular buffer that loops when sufficient data has been received. We 
// support a number of different audio formats such as 8bit mode and stereo. 
// 
// 
// Demonstration Instructions 
// 
// (To really sure of this demonstration the machine must have a sound card) 
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any 
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to 
// the filters in the graph and find the filter (box) titled "Audio Renderer" 
// This is the filter we will be replacing with this oscilloscope renderer. 
// Then click on the box and hit DELETE. After that go to the Graph menu and 
// select "Insert Filters", from the dialog box that pops up find and select 
// "Oscilloscope", then dismiss the dialog. Back in the graph layout find the 
// output pin of the filter that was connected to the input of the audio 
// renderer you just deleted, right click and select "Render". You should 
// see it being connected to the input pin of the oscilloscope you inserted 
// 
// Click Run on GRAPHEDT and you'll see a waveform for the audio soundtrack... 
// 
// 
// Files 
// 
// icon1.ico            The icon for the oscilloscope window 
// makefile             How we build it... 
// resource.h           Microsoft Visual C++ generated file 
// scope.cpp            The main filter and window implementations 
// scope.def            What APIs the DLL imports and exports 
// scope.h              Window and filter class definitions 
// scope.mak            Visual C++ generated makefile 
// scope.rc             Dialog box template for our window 
// scope.reg            What goes in the registry to make us work 
// 
// 
// Base classes we use 
// 
// CBaseInputPin        A generic input pin we use for the filter 
// CCritSec             A wrapper class around a critical section 
// CBaseFilter          The generic ActiveMovie filter object 
// 
// 
 
 
// Setup data 
 
const AMOVIESETUP_MEDIATYPE sudPinTypes = 
{ 
    &MEDIATYPE_Audio,           // Major type 
    &MEDIASUBTYPE_NULL          // Minor type 
}; 
 
 
const AMOVIESETUP_PIN sudPins  = 
{ 
    L"Input",                   // Pin string name 
    FALSE,                      // Is it rendered 
    FALSE,                      // Is it an output 
    FALSE,                      // Allowed zero pins 
    FALSE,                      // Allowed many 
    &CLSID_NULL,                // Connects to filter 
    L"Output",                  // Connects to pin 
    1,                          // Number of pins types 
    &sudPinTypes } ;            // Pin information 
 
 
const AMOVIESETUP_FILTER sudScope = 
{ 
    &CLSID_Scope,               // Filter CLSID 
    L"Oscilloscope",            // String name 
    MERIT_DO_NOT_USE,           // Filter merit 
    1,                          // Number pins 
    &sudPins                    // Pin details 
}; 
 
 
// List of class IDs and creator functions for class factory 
 
CFactoryTemplate g_Templates []  = { 
    { L"Oscilloscope" 
    , &CLSID_Scope 
    , CScopeFilter::CreateInstance 
    , NULL 
    , &sudScope } 
}; 
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); 
 
 
// 
// CreateInstance 
// 
// This goes in the factory template table to create new instances 
// 
CUnknown * WINAPI CScopeFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) 
{ 
    return new CScopeFilter(pUnk, phr); 
 
} // CreateInstance 
 
 
// 
// Constructor 
// 
// Create the filter, scope window, and input pin 
// 
#pragma warning(disable:4355) 
// 
CScopeFilter::CScopeFilter(LPUNKNOWN pUnk,HRESULT *phr) : 
    CBaseFilter(NAME("Oscilloscope"), pUnk, (CCritSec *) this, CLSID_Scope), 
    m_Window(NAME("Oscilloscope"), this, phr) 
{ 
    // Create the single input pin 
 
    m_pInputPin = new CScopeInputPin(this,phr,L"Scope Input Pin"); 
    if (m_pInputPin == NULL) { 
        *phr = E_OUTOFMEMORY; 
    } 
 
} // (Constructor) 
 
 
// 
// Destructor 
// 
CScopeFilter::~CScopeFilter() 
{ 
    // Delete the contained interfaces 
 
    ASSERT(m_pInputPin); 
    delete m_pInputPin; 
    m_pInputPin = NULL; 
 
} // (Destructor) 
 
 
// 
// GetPinCount 
// 
// Return the number of input pins we support 
// 
int CScopeFilter::GetPinCount() 
{ 
    return 1; 
 
} // GetPinCount 
 
 
// 
// GetPin 
// 
// Return our single input pin - not addrefed 
// 
CBasePin *CScopeFilter::GetPin(int n) 
{ 
    // We only support one input pin and it is numbered zero 
 
    ASSERT(n == 0); 
    if (n != 0) { 
        return NULL; 
    } 
    return m_pInputPin; 
 
} // GetPin 
 
 
// 
// JoinFilterGraph 
// 
// Show our window when we join a filter graph 
//   - and hide it when we are annexed from it 
// 
STDMETHODIMP CScopeFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName) 
{ 
    HRESULT hr = CBaseFilter::JoinFilterGraph(pGraph, pName); 
    if (FAILED(hr)) { 
        return hr; 
    } 
 
    // Hide or show the scope as appropriate 
 
    if (pGraph == NULL) { 
        m_Window.InactivateWindow(); 
    } else { 
        m_Window.ActivateWindow(); 
    } 
    return hr; 
 
} // JoinFilterGraph 
 
 
// 
// Stop 
// 
// Switch the filter into stopped mode. 
// 
STDMETHODIMP CScopeFilter::Stop() 
{ 
    CAutoLock lock(this); 
 
    if (m_State != State_Stopped) { 
 
        // Pause the device if we were running 
        if (m_State == State_Running) { 
            HRESULT hr = Pause(); 
            if (FAILED(hr)) { 
                return hr; 
            } 
        } 
 
        DbgLog((LOG_TRACE,1,TEXT("Stopping...."))); 
 
        // Base class changes state and tells pin to go to inactive 
        // the pin Inactive method will decommit our allocator which 
        // we need to do before closing the device 
 
        HRESULT hr = CBaseFilter::Stop(); 
        if (FAILED(hr)) { 
            return hr; 
        } 
    } 
    return NOERROR; 
 
} // Stop 
 
 
// 
// Pause 
// 
// Override Pause to stop the window streaming 
// 
STDMETHODIMP CScopeFilter::Pause() 
{ 
    CAutoLock lock(this); 
 
    // Check we can PAUSE given our current state 
 
    if (m_State == State_Running) { 
        m_Window.StopStreaming(); 
    } 
 
    // tell the pin to go inactive and change state 
    return CBaseFilter::Pause(); 
 
} // Pause 
 
 
// 
// Run 
// 
// Overriden to start the window streaming 
// 
STDMETHODIMP CScopeFilter::Run(REFERENCE_TIME tStart) 
{ 
    CAutoLock lock(this); 
    HRESULT hr = NOERROR; 
    FILTER_STATE fsOld = m_State; 
 
    // This will call Pause if currently stopped 
 
    hr = CBaseFilter::Run(tStart); 
    if (FAILED(hr)) { 
        return hr; 
    } 
 
    m_Window.ActivateWindow(); 
 
    if (fsOld != State_Running) { 
        m_Window.StartStreaming(); 
    } 
    return NOERROR; 
 
} // Run 
 
 
// 
// Constructor 
// 
CScopeInputPin::CScopeInputPin(CScopeFilter *pFilter, 
                               HRESULT *phr, 
                               LPCWSTR pPinName) : 
    CBaseInputPin(NAME("Scope Input Pin"), pFilter, pFilter, phr, pPinName) 
{ 
    m_pFilter = pFilter; 
 
} // (Constructor) 
 
 
// 
// Destructor does nothing 
// 
CScopeInputPin::~CScopeInputPin() 
{ 
} // (Destructor) 
 
 
// 
// BreakConnect 
// 
// This is called when a connection or an attempted connection is terminated 
// and allows us to reset the connection media type to be invalid so that 
// we can always use that to determine whether we are connected or not. We 
// leave the format block alone as it will be reallocated if we get another 
// connection or alternatively be deleted if the filter is finally released 
// 
HRESULT CScopeInputPin::BreakConnect() 
{ 
    // Check we have a valid connection 
 
    if (m_mt.IsValid() == FALSE) { 
        return E_FAIL; 
    } 
 
    m_pFilter->Stop(); 
 
    // Reset the CLSIDs of the connected media type 
 
    m_mt.SetType(&GUID_NULL); 
    m_mt.SetSubtype(&GUID_NULL); 
    return CBaseInputPin::BreakConnect(); 
 
} // BreakConnect 
 
 
// 
// CheckMediaType 
// 
// Check that we can support a given proposed type 
// 
HRESULT CScopeInputPin::CheckMediaType(const CMediaType *pmt) 
{ 
    WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmt->Format(); 
 
    if (pwfx == NULL) 
        return E_INVALIDARG; 
 
    // Reject non-PCM Audio type 
 
    if (pmt->majortype != MEDIATYPE_Audio) { 
        return E_INVALIDARG; 
    } 
 
    if (pmt->formattype != FORMAT_WaveFormatEx) { 
        return E_INVALIDARG; 
    } 
 
    if (pwfx->wFormatTag != WAVE_FORMAT_PCM) { 
        return E_INVALIDARG; 
    } 
    return NOERROR; 
 
} // CheckMediaType 
 
 
// 
// SetMediaType 
// 
// Actually set the format of the input pin 
// 
HRESULT CScopeInputPin::SetMediaType(const CMediaType *pmt) 
{ 
    CAutoLock lock(m_pFilter); 
 
    // Pass the call up to my base class 
 
    HRESULT hr = CBaseInputPin::SetMediaType(pmt); 
    if (SUCCEEDED(hr)) { 
 
        WAVEFORMATEX *pwf = (WAVEFORMATEX *) pmt->Format(); 
 
        m_pFilter->m_Window.m_nChannels = pwf->nChannels; 
        m_pFilter->m_Window.m_nSamplesPerSec = pwf->nSamplesPerSec; 
        m_pFilter->m_Window.m_nBitsPerSample = pwf->wBitsPerSample; 
        m_pFilter->m_Window.m_nBlockAlign = pwf->nBlockAlign; 
 
        m_pFilter->m_Window.m_MaxValue = 128; 
        m_pFilter->m_Window.m_nIndex = 0; 
 
        if (!m_pFilter->m_Window.AllocWaveBuffers ()) 
            return E_FAIL; 
 
        // Reset the horizontal scroll bar 
        m_pFilter->m_Window.SetHorizScrollRange(m_pFilter->m_Window.m_hwndDlg); 
    } 
    return hr; 
 
} // SetMediaType 
 
 
// 
// Active 
// 
// Implements the remaining IMemInputPin virtual methods 
// 
HRESULT CScopeInputPin::Active(void) 
{ 
    return NOERROR; 
 
} // Active 
 
 
// 
// Inactive 
// 
// Called when the filter is stopped 
// 
HRESULT CScopeInputPin::Inactive(void) 
{ 
    return NOERROR; 
 
} // Inactive 
 
 
// 
// Receive 
// 
// Here's the next block of data from the stream 
// 
HRESULT CScopeInputPin::Receive(IMediaSample * pSample) 
{ 
    // Lock this with the filter-wide lock 
    CAutoLock lock(m_pFilter); 
 
    // If we're stopped, then reject this call 
    // (the filter graph may be in mid-change) 
    if (m_pFilter->m_State == State_Stopped) { 
        return E_FAIL; 
    } 
 
    // Check all is well with the base class 
    HRESULT hr = CBaseInputPin::Receive(pSample); 
    if (FAILED(hr)) { 
        return hr; 
    } 
 
    // Send the sample to the video window object for rendering 
    return m_pFilter->m_Window.Receive(pSample); 
 
} // Receive 
 
 
// 
// CScopeWindow Constructor 
// 
CScopeWindow::CScopeWindow(TCHAR *pName, CScopeFilter *pRenderer,HRESULT *phr) : 
    m_hInstance(g_hInst), 
    m_pRenderer(pRenderer), 
    m_hThread(INVALID_HANDLE_VALUE), 
    m_ThreadID(0), 
    m_hwndDlg(NULL), 
    m_hwnd(NULL), 
    m_pPoints1(NULL), 
    m_pPoints2(NULL), 
    m_nPoints(0), 
    m_bStreaming(FALSE), 
    m_bActivated(FALSE), 
    m_LastMediaSampleSize(0) 
{ 
    // Create a thread to look after the window 
 
    ASSERT(m_pRenderer); 
    m_hThread = CreateThread(NULL,                  // Security attributes 
                             (DWORD) 0,             // Initial stack size 
                             WindowMessageLoop,     // Thread start address 
                             (LPVOID) this,         // Thread parameter 
                             (DWORD) 0,             // Creation flags 
                             &m_ThreadID);          // Thread identifier 
 
    // If we couldn't create a thread the whole thing's off 
 
    ASSERT(m_hThread); 
    if (m_hThread == NULL) { 
        *phr = E_FAIL; 
        return; 
    } 
 
    // Wait until the window has been initialised 
    m_SyncWorker.Wait(); 
 
} // (Constructor) 
 
 
// 
// Destructor 
// 
CScopeWindow::~CScopeWindow() 
{ 
    // Ensure we stop streaming and release any samples 
 
    StopStreaming(); 
    InactivateWindow(); 
 
    // Tell the thread to destroy the window 
    SendMessage(m_hwndDlg, WM_GOODBYE, (WPARAM)0, (LPARAM)0); 
 
    // Make sure it has finished 
 
    ASSERT(m_hThread != NULL); 
    WaitForSingleObject(m_hThread,INFINITE); 
    CloseHandle(m_hThread); 
 
    if (m_pPoints1 != NULL) delete [] m_pPoints1; 
    if (m_pPoints2 != NULL) delete [] m_pPoints2; 
 
} // (Destructor) 
 
 
// 
// ResetStreamingTimes 
// 
// This resets the latest sample stream times 
// 
HRESULT CScopeWindow::ResetStreamingTimes() 
{ 
    m_StartSample = 0; 
    m_EndSample = 0; 
    return NOERROR; 
 
} // ResetStreamingTimes 
 
 
// 
// StartStreaming 
// 
// This is called when we start running state 
// 
HRESULT CScopeWindow::StartStreaming() 
{ 
    CAutoLock cAutoLock(this); 
 
    // Are we already streaming 
 
    if (m_bStreaming == TRUE) { 
        return NOERROR; 
    } 
 
    m_bStreaming = TRUE; 
    return NOERROR; 
 
} // StartStreaming 
 
 
// 
// StopStreaming 
// 
// This is called when we stop streaming 
// 
HRESULT CScopeWindow::StopStreaming() 
{ 
    CAutoLock cAutoLock(this); 
 
    // Have we been stopped already 
 
    if (m_bStreaming == FALSE) { 
        return NOERROR; 
    } 
 
    m_bStreaming = FALSE; 
    return NOERROR; 
 
} // StopStreaming 
 
 
// 
// InactivateWindow 
// 
// Called at the end to put the window in an inactive state 
// 
HRESULT CScopeWindow::InactivateWindow() 
{ 
    // Has the window been activated 
    if (m_bActivated == FALSE) { 
        return S_FALSE; 
    } 
 
    // Now hide the scope window 
 
    ShowWindow(m_hwndDlg,SW_HIDE); 
    m_bActivated = FALSE; 
    return NOERROR; 
 
} // InactivateWindow 
 
 
// 
// ActivateWindow 
// 
// Show the scope window 
// 
HRESULT CScopeWindow::ActivateWindow() 
{ 
    // Has the window been activated 
    if (m_bActivated == TRUE) { 
        return S_FALSE; 
    } 
 
    m_bActivated = TRUE; 
    ASSERT(m_bStreaming == FALSE); 
 
    ShowWindow(m_hwndDlg,SW_SHOWNORMAL); 
    return NOERROR; 
 
} // ActivateWindow 
 
 
// 
// OnClose 
// 
// This function handles the WM_CLOSE message 
// 
BOOL CScopeWindow::OnClose() 
{ 
    InactivateWindow(); 
    return TRUE; 
 
} // OnClose 
 
 
typedef struct GainEntry_tag { 
    double GainValue; 
    TCHAR GainText[8]; 
} GainEntry; 
 
GainEntry GainEntries[] = 
{ 
    128.,  TEXT ("*128"), 
    64.,   TEXT ("*64"), 
    32.,   TEXT ("*32"), 
    16.,   TEXT ("*16"), 
    8.,    TEXT ("*8"), 
    4.,    TEXT ("*4"), 
    2.,    TEXT ("*2"), 
    1.,    TEXT ("*1"), 
    1./2,  TEXT ("/2"), 
    1./4,  TEXT ("/4"), 
    1./8,  TEXT ("/8"), 
    1./16, TEXT ("/16"), 
    1./32, TEXT ("/32"), 
    1./64, TEXT ("/64"), 
    1./128,TEXT ("/128"), 
    1./256,TEXT ("/256"), 
}; 
 
#define N_GAINENTRIES (sizeof(GainEntries) / sizeof (GainEntries[0])) 
#define GAIN_DEFAULT_INDEX 7 
 
typedef struct TBEntry_tag { 
    int TBDivisor; 
    TCHAR TBText[16]; 
} TBEntry; 
 
TBEntry Timebases[] = 
{ 
    10000,  TEXT ("10 uS/Div"), 
     5000,  TEXT ("20 uS/Div"), 
     2000,  TEXT ("50 uS/Div"), 
     1000,  TEXT ("100 uS/Div"), 
      500,  TEXT ("200 uS/Div"), 
      200,  TEXT ("500 uS/Div"), 
      100,  TEXT ("1 mS/Div"), 
       50,  TEXT ("2 mS/Div"), 
       20,  TEXT ("5 mS/Div"), 
       10,  TEXT ("10 mS/Div"), 
        5,  TEXT ("20 mS/Div"), 
        2,  TEXT ("50 mS/Div"), 
        1,  TEXT ("100 mS/Div") 
}; 
 
#define N_TIMEBASES (sizeof(Timebases) / sizeof (Timebases[0])) 
#define TIMEBASE_DEFAULT_INDEX 9 
 
 
// 
// SetControlRanges 
// 
// Set the scroll ranges for all of the vertical trackbars 
// 
void CScopeWindow::SetControlRanges(HWND hDlg) 
{ 
    SendMessage(m_hwndLGain, TBM_SETRANGE, TRUE, MAKELONG(0, N_GAINENTRIES - 1) ); 
    SendMessage(m_hwndLGain, TBM_SETPOS, TRUE, (LPARAM) GAIN_DEFAULT_INDEX); 
    SetDlgItemText (hDlg, IDC_L_GAIN_TEXT, GainEntries[m_LGain].GainText); 
 
    SendMessage(m_hwndLOffset, TBM_SETRANGE, TRUE, MAKELONG(0, m_Height - 1)); 
    SendMessage(m_hwndLOffset, TBM_SETPOS, TRUE, (LPARAM) m_Height / 2); 
    SetDlgItemInt (hDlg, IDC_L_OFFSET_TEXT, -m_LOffset, TRUE); 
 
    SendMessage(m_hwndRGain, TBM_SETRANGE, TRUE, MAKELONG(0, N_GAINENTRIES - 1) ); 
    SendMessage(m_hwndRGain, TBM_SETPOS, TRUE, (LPARAM) GAIN_DEFAULT_INDEX); 
    SetDlgItemText (hDlg, IDC_R_GAIN_TEXT, GainEntries[m_RGain].GainText); 
 
    SendMessage(m_hwndROffset, TBM_SETRANGE, TRUE, MAKELONG(0, m_Height - 1) ); 
    SendMessage(m_hwndROffset, TBM_SETPOS, TRUE, (LPARAM) m_Height / 2); 
    SetDlgItemInt (hDlg, IDC_R_OFFSET_TEXT, -m_ROffset, TRUE); 
 
    SendMessage(m_hwndTimebase, TBM_SETRANGE, TRUE, MAKELONG(0, N_TIMEBASES - 1) ); 
    SendMessage(m_hwndTimebase, TBM_SETPOS, TRUE, (LPARAM) m_nTimebase); 
    SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText); 
 
} // SetControlRanges 
 
 
// 
// SetHorizScrollRange 
// 
// The horizontal scrollbar handles scrolling through the 1 second circular buffer 
// 
void CScopeWindow::SetHorizScrollRange(HWND hDlg) 
{ 
    SendMessage(m_hwndTBScroll, TBM_SETRANGE, TRUE, MAKELONG(0, (m_nPoints - 1) / 2) ); 
    SendMessage(m_hwndTBScroll, TBM_SETPOS, TRUE, (LPARAM) (m_nPoints - 1) / 2); 
 
    m_TBScroll = m_nPoints - 1; 
 
    TCHAR szFormat[80]; 
 
    switch (m_nBitsPerSample + m_nChannels) { 
        case 9: 
            // Mono, 8-bit 
            lstrcpy (szFormat, TEXT ("M-8-")); 
            break; 
 
        case 10: 
            // Stereo, 8-bit 
            lstrcpy (szFormat, TEXT ("S-8-")); 
            break; 
 
        case 17: 
            // Mono, 16-bit 
            lstrcpy (szFormat, TEXT ("M-16-")); 
            break; 
 
        case 18: 
            // Stereo, 16-bit 
            lstrcpy (szFormat, TEXT ("S-16-")); 
            break; 
 
        default: 
            lstrcpy (szFormat, TEXT (" ")); 
            SetDlgItemText (hDlg, IDC_FORMAT, szFormat); 
            return; 
 
    } // End of format switch 
 
    TCHAR szSamplingFreq[80]; 
    wsprintf (szSamplingFreq, "%d", m_nSamplesPerSec); 
    lstrcat (szFormat, szSamplingFreq); 
    SetDlgItemText (hDlg, IDC_FORMAT, szFormat); 
 
} // SetHorizScrollRange 
 
 
// 
// ProcessHorizScrollCommands 
// 
// Called when we get a horizontal scroll bar message 
// 
void CScopeWindow::ProcessHorizScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam) 
{ 
    int pos; 
    int command = LOWORD (wParam); 
 
    if (command != TB_ENDTRACK && 
        command != TB_THUMBTRACK && 
        command != TB_LINEDOWN && 
        command != TB_LINEUP && 
        command != TB_PAGEUP && 
        command != TB_PAGEDOWN) 
            return; 
 
    ASSERT (IsWindow ((HWND) lParam)); 
 
    pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L); 
 
    if ((HWND) lParam == m_hwndTBScroll) { 
        m_TBScroll = ((m_nPoints - 1) / 2 - pos) * 2; 
    } 
    OnPaint(); 
 
} // ProcessHorizScrollCommands 
 
 
// 
// ProcessVertScrollCommands 
// 
// Called when we get a vertical scroll bar message 
// 
void CScopeWindow::ProcessVertScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam) 
{ 
    int pos; 
    int command = LOWORD (wParam); 
 
    if (command != TB_ENDTRACK && 
        command != TB_THUMBTRACK && 
        command != TB_LINEDOWN && 
        command != TB_LINEUP && 
        command != TB_PAGEUP && 
        command != TB_PAGEDOWN) 
            return; 
 
    ASSERT (IsWindow ((HWND) lParam)); 
 
    pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L); 
 
    if ((HWND) lParam == m_hwndLGain) { 
        m_LGain = pos; 
        SetDlgItemText (hDlg, IDC_L_GAIN_TEXT, GainEntries[m_LGain].GainText); 
    } else if ((HWND) lParam == m_hwndLOffset) { 
        m_LOffset = pos - m_Height / 2; 
        SetDlgItemInt (hDlg, IDC_L_OFFSET_TEXT, -m_LOffset, TRUE); 
    } else if ((HWND) lParam == m_hwndRGain) { 
        m_RGain = pos; 
        SetDlgItemText (hDlg, IDC_R_GAIN_TEXT, GainEntries[m_RGain].GainText); 
    } else if ((HWND) lParam == m_hwndROffset) { 
        m_ROffset = pos - m_Height / 2; 
        SetDlgItemInt (hDlg, IDC_R_OFFSET_TEXT, -m_ROffset, TRUE); 
    } else if ((HWND) lParam == m_hwndTimebase) { 
        m_nTimebase = pos ; 
        SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText); 
    } 
    OnPaint(); 
 
} // ProcessVertScrollCommands 
 
 
// 
// InitialiseWindow 
// 
// This is called by the worker window thread after it has created the main 
// window and it wants to initialise the rest of the owner objects window 
// variables such as the device contexts. We execute this function with the 
// critical section still locked. 
// 
HRESULT CScopeWindow::InitialiseWindow(HWND hDlg) 
{ 
    RECT rR; 
 
    // Initialise the window variables 
    m_hwnd = GetDlgItem (hDlg, IDC_SCOPEWINDOW); 
 
    // Quick sanity check 
    ASSERT(m_hwnd != NULL); 
 
    m_nTimebase = TIMEBASE_DEFAULT_INDEX; 
    m_fTriggerPosZeroCrossing = 1; 
    m_fFreeze = 0; 
 
    m_LGain = GAIN_DEFAULT_INDEX; 
    m_RGain = GAIN_DEFAULT_INDEX; 
    m_LOffset = 0; 
    m_ROffset = 0; 
 
    m_TBScroll = 0; 
 
    GetWindowRect (m_hwnd, &rR); 
    m_Width = rR.right - rR.left; 
    m_Height = rR.bottom - rR.top; 
 
    m_hwndLGain =       GetDlgItem (hDlg, IDC_L_GAIN); 
    m_hwndLOffset =     GetDlgItem (hDlg, IDC_L_OFFSET); 
    m_hwndLGainText =   GetDlgItem (hDlg, IDC_L_GAIN_TEXT); 
    m_hwndLTitle =      GetDlgItem (hDlg, IDC_L_TITLE); 
 
    m_hwndRGain =       GetDlgItem (hDlg, IDC_R_GAIN); 
    m_hwndROffset =     GetDlgItem (hDlg, IDC_R_OFFSET); 
    m_hwndRGainText =   GetDlgItem (hDlg, IDC_R_GAIN_TEXT); 
    m_hwndRTitle =      GetDlgItem (hDlg, IDC_R_TITLE); 
 
    m_hwndTimebase =    GetDlgItem (hDlg, IDC_TIMEBASE); 
    m_hwndFreeze =      GetDlgItem (hDlg, IDC_FREEZE); 
    m_hwndTBStart =     GetDlgItem (hDlg, IDC_TS_START); 
    m_hwndTBEnd   =     GetDlgItem (hDlg, IDC_TS_LAST); 
    m_hwndTBDelta =     GetDlgItem (hDlg, IDC_TS_DELTA); 
    m_hwndTBScroll =    GetDlgItem (hDlg, IDC_TB_SCROLL); 
 
    SetControlRanges(hDlg); 
    SetHorizScrollRange(hDlg); 
 
    CheckDlgButton( 
            hDlg,                // handle of dialog box 
            IDC_FREEZE,                // button-control identifier 
            m_fFreeze);                // check state 
 
     CheckDlgButton( 
            hDlg,                // handle of dialog box 
            IDC_TRIGGER,        // button-control identifier 
            m_fTriggerPosZeroCrossing);// check state 
 
    m_hPen1 = CreatePen (PS_SOLID, 0, RGB (0, 0xff, 0)); 
    m_hPen2 = CreatePen (PS_SOLID, 0, RGB (0x40, 0x40, 0xff)); 
    m_hPenTicks = CreatePen (PS_SOLID, 0, RGB (0x80, 0x80, 0x80)); 
    m_hBrushBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); 
 
    HDC hdc = GetDC (NULL); 
    m_hBitmap = CreateCompatibleBitmap (hdc, m_Width, m_Height); 
    ReleaseDC (NULL, hdc); 
    return NOERROR; 
 
} // InitialiseWindow 
 
 
// 
// UninitialiseWindow 
// 
// This is called by the worker window thread when it receives a WM_GOODBYE 
// message from the window object destructor to delete all the resources we 
// allocated during initialisation 
// 
HRESULT CScopeWindow::UninitialiseWindow() 
{ 
    // Reset the window variables 
    DeleteObject (m_hPen1); 
    DeleteObject (m_hPen2); 
    DeleteObject (m_hPenTicks); 
    DeleteObject (m_hBitmap); 
 
    m_hwnd = NULL; 
    return NOERROR; 
 
} // UninitialiseWindow 
 
 
// 
// ScopeDlgProc 
// 
// The Scope window is actually a dialog box, and this is its window proc. 
// The only thing tricky about this is that the "this" pointer to the 
// CScopeWindow is passed during the WM_INITDIALOG message and is stored 
// in the window user data. This lets us access methods in the class 
// from within the dialog. 
// 
BOOL CALLBACK ScopeDlgProc(HWND hDlg,        // Handle of dialog box 
                           UINT uMsg,        // Message identifier 
                           WPARAM wParam,// First message parameter 
                           LPARAM lParam)// Second message parameter 
{ 
    CScopeWindow *pScopeWindow;      // Pointer to the owning object 
 
    // Get the window long that holds our owner pointer 
    pScopeWindow = (CScopeWindow *) GetWindowLong(hDlg, GWL_USERDATA); 
 
    switch (uMsg) { 
        case WM_INITDIALOG: 
            pScopeWindow = (CScopeWindow *) lParam; 
            SetWindowLong(hDlg, (DWORD) GWL_USERDATA, (LONG) pScopeWindow); 
            return TRUE; 
 
        case WM_COMMAND: 
            switch (wParam) { 
                case IDOK: 
                case IDCANCEL: 
                    EndDialog (hDlg, 0); 
                    return TRUE; 
 
                case IDC_FREEZE: 
                    pScopeWindow->m_fFreeze = 
                        (BOOL) IsDlgButtonChecked(hDlg,IDC_FREEZE); 
                    pScopeWindow->DrawWaveform(); 
                    break; 
 
                case IDC_TRIGGER: 
                    pScopeWindow->m_fTriggerPosZeroCrossing = 
                        (BOOL) IsDlgButtonChecked(hDlg,IDC_TRIGGER); 
                    pScopeWindow->DrawWaveform(); 
                    break; 
 
                default: 
                    break; 
            } 
 
        case WM_VSCROLL: 
            pScopeWindow->ProcessVertScrollCommands(hDlg, wParam, lParam); 
            break; 
 
        case WM_HSCROLL: 
            pScopeWindow->ProcessHorizScrollCommands(hDlg, wParam, lParam); 
            break; 
 
        case WM_PAINT: 
            ASSERT(pScopeWindow != NULL); 
            pScopeWindow->OnPaint(); 
            break; 
 
        // We stop WM_CLOSE messages going any further by intercepting them 
        // and then setting an abort signal flag in the owning renderer so 
        // that it knows the user wants to quit. The renderer can then 
        // go about deleting it's interfaces and the window helper object 
        // which will eventually cause a WM_DESTROY message to arrive. To 
        // make it look as though the window has been immediately closed 
        // we hide it and then wait for the renderer to catch us up 
 
        case WM_CLOSE: 
            ASSERT(pScopeWindow != NULL); 
            pScopeWindow->OnClose(); 
            return (LRESULT) 0; 
 
        // We receive a WM_GOODBYE window message (synchronously) from the 
        // window object destructor in which case we do actually destroy 
        // the window and complete the process in the WM_DESTROY message 
 
        case WM_GOODBYE: 
            ASSERT(pScopeWindow != NULL); 
            pScopeWindow->UninitialiseWindow(); 
            PostQuitMessage(FALSE); 
            EndDialog (hDlg, 0); 
            return (LRESULT) 0; 
 
        default: 
            break; 
    } 
    return (LRESULT) 0; 
 
} // ScopeDlgProc 
 
 
// 
// MessageLoop 
// 
// This is the standard windows message loop for our worker thread. It sits 
// in a normal processing loop dispatching messages until it receives a quit 
// message, which may be generated through the owning object's destructor 
// 
HRESULT CScopeWindow::MessageLoop() 
{ 
    MSG Message;        // Windows message structure 
    DWORD dwResult;     // Wait return code value 
 
    HANDLE hWait[] = { (HANDLE) m_RenderEvent }; 
 
    // Enter the modified message loop 
 
    while (TRUE) { 
 
        // We use this to wait for two different kinds of events, the first 
// are the normal windows messages, the other is an event that will 
        // be signaled when a sample is ready 
 
        dwResult = MsgWaitForMultipleObjects((DWORD) 1,     // Number events 
                                             hWait,         // Event handle 
                                             FALSE,         // Wait for either 
                                             INFINITE,      // No timeout 
                                             QS_ALLINPUT);  // All messages 
 
        // Has a sample become ready to render 
        if (dwResult == WAIT_OBJECT_0) { 
            DrawWaveform(); 
        } 
 
        // Process the thread's window message 
 
        while (PeekMessage(&Message,NULL,(UINT) 0,(UINT) 0,PM_REMOVE)) { 
 
            // Check for the WM_QUIT message 
 
            if (Message.message == WM_QUIT) { 
                return NOERROR; 
            } 
 
            // Send the message to the window procedure 
 
            TranslateMessage(&Message); 
            DispatchMessage(&Message); 
        } 
    } 
 
} // MessageLoop 
 
 
// 
// WindowMessageLoop 
// 
// This creates a window and processes it's messages on a separate thread 
// 
DWORD __stdcall CScopeWindow::WindowMessageLoop(LPVOID lpvThreadParm) 
{ 
    CScopeWindow *pScopeWindow;     // The owner renderer object 
 
    // Cast the thread parameter to be our owner object 
    pScopeWindow = (CScopeWindow *) lpvThreadParm; 
 
    pScopeWindow->m_hwndDlg = 
        CreateDialogParam( 
            pScopeWindow->m_hInstance,        // Handle of app instance 
            MAKEINTRESOURCE (IDD_SCOPEDIALOG),// Dialog box template 
            NULL,                        // Handle of owner window 
            (DLGPROC) ScopeDlgProc,        // Address of dialog procedure 
            (LONG) pScopeWindow                 // Initialization value 
        ); 
 
    if (pScopeWindow->m_hwndDlg != NULL) 
    { 
        // Initialise the window, then signal the constructor that it can 
        // continue and then unlock the object's critical section and 
        // process messages 
 
        pScopeWindow->InitialiseWindow(pScopeWindow->m_hwndDlg); 
    } 
 
    pScopeWindow->m_SyncWorker.Set(); 
 
    if (pScopeWindow->m_hwndDlg != NULL) 
    { 
        pScopeWindow->MessageLoop(); 
    } 
 
    ExitThread(TRUE); 
    return TRUE; 
 
} // WindowMessageLoop 
 
 
// 
// OnPaint 
// 
// WM_PAINT message 
// 
BOOL CScopeWindow::OnPaint() 
{ 
    DrawWaveform(); 
    return TRUE; 
 
} // OnPaint 
 
 
// 
// ClearWindow 
// 
// Clear the scope to black and draw the center tickmarks 
// 
void CScopeWindow::ClearWindow(HDC hdc) 
{ 
    int y = m_Height / 2; 
 
    SetMapMode (hdc, MM_TEXT); 
    SetWindowOrgEx (hdc, 0, 0, NULL); 
    SetViewportOrgEx (hdc, 0, 0, NULL); 
 
    // Paint the entire window black 
    PatBlt(hdc,            // Handle of device context 
           (INT) 0,        // x-coord of upper-left corner 
           (INT) 0,        // y-coord of upper-left corner 
           m_Width,        // Width of rectangle to be filled 
           m_Height,       // Height of rectangle to be filled 
           BLACKNESS);     // Raster operation code 
 
    // Draw the horizontal line 
    HPEN hPenOld = (HPEN) SelectObject (hdc, m_hPenTicks); 
    MoveToEx (hdc, 0, y, NULL); 
    LineTo (hdc, m_Width, y); 
 
    // Draw the tickmarks 
    float inc = (float) m_Width / 10; 
    int pos, j; 
    int TickPoint; 
    for (j = 0; j <= 10; j++) { 
        if (j == 0 || j == 5 || j == 10) 
            TickPoint =  m_Height / 15; 
        else 
            TickPoint = m_Height / 30; 
        pos = (int) (j * inc); 
        MoveToEx (hdc, pos, y + TickPoint, NULL); 
        LineTo (hdc, pos, y - TickPoint); 
    } 
    SelectObject (hdc, hPenOld); 
 
} // ClearWindow 
 
 
// 
// DrawPartialWaveform 
// 
// Draw a part of the Oscilloscope waveform - IndexStart and IndexEnd 
// are pointers into the m_pPoints array (in LOGICAL COORDINATES) 
// while ViewpointStart and ViewpointEnd are in SCREEN COORDINATES 
// 
void CScopeWindow::DrawPartialWaveform(HDC hdc, 
                                       int IndexStart, 
                                       int IndexEnd, 
                                       int ViewportStart, 
                                       int ViewportEnd) 
{ 
    int nPoints = IndexEnd - IndexStart; 
    int nViewportWidth = ViewportEnd - ViewportStart; 
    ASSERT (IndexStart + nPoints < m_nPoints); 
 
    // Origin at lower left, x increases up, y increases to right 
    SetMapMode (hdc, MM_ANISOTROPIC); 
 
    SetWindowOrgEx (hdc, IndexStart, 0, NULL); 
    SetWindowExtEx (hdc, nPoints, (int) (m_MaxValue / GainEntries[m_LGain].GainValue), NULL); 
    SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL); 
    SetViewportOrgEx (hdc, ViewportStart, m_LOffset + m_Height / 2, NULL); 
 
    HPEN OldPen = (HPEN) SelectObject (hdc, m_hPen1); 
    Polyline (hdc, m_pPoints1 + IndexStart, nPoints + 1); 
    SelectObject (hdc, OldPen); 
 
    if (m_pPoints2) { 
        SetWindowOrgEx (hdc, IndexStart, 0, NULL); 
        SetWindowExtEx (hdc, nPoints, (int) (m_MaxValue / GainEntries[m_RGain].GainValue), NULL); 
        SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL); 
        SetViewportOrgEx (hdc, ViewportStart, m_ROffset + m_Height / 2, NULL); 
 
        HPEN OldPen = (HPEN) SelectObject (hdc, m_hPen2); 
        Polyline (hdc, m_pPoints2 + IndexStart, nPoints + 1); 
        SelectObject (hdc, OldPen); 
    } 
 
} // DrawPartialWaveform 
 
 
// 
// DrawWaveform 
// 
// Draw the full Oscilloscope waveform 
// 
void CScopeWindow::DrawWaveform(void) 
{ 
    CAutoLock lock(m_pRenderer); 
    TCHAR szT[40]; 
 
    if (m_pPoints1 == NULL) 
        return; 
 
    HDC hdc = GetWindowDC (m_hwnd);  // WindowDC has clipping region 
    HDC hdcT = CreateCompatibleDC (hdc); 
    HBITMAP hBitmapOld = (HBITMAP) SelectObject (hdcT, m_hBitmap); 
 
    ClearWindow (hdcT); 
 
    int StartOffset; 
    int IndexEdge; 
    int IndexStart1, IndexEnd1; 
    int IndexStart2, IndexEnd2; 
    int PointsToDisplay, PointsToDisplay1, PointsToDisplay2; 
    int ViewportBreak; 
    int OffsetTimeMS; 
    BOOL fWraps;                // If segment to display wraps around 0 
 
    PointsToDisplay = m_nPoints / Timebases [m_nTimebase].TBDivisor; 
 
    StartOffset = (m_nIndex - 1) - m_TBScroll; 
    if (StartOffset < 0) 
        StartOffset += m_nPoints; 
 
    if (m_fTriggerPosZeroCrossing) { 
        SearchForPosZeroCrossing(StartOffset, &IndexEdge); 
        IndexEnd2 = IndexEdge; 
    } else { 
        IndexEnd2 = StartOffset; 
    } 
    IndexStart2 = IndexEnd2 - PointsToDisplay;    // can be negative 
 
    if (IndexEnd2 > m_nIndex) 
        OffsetTimeMS = (m_nIndex + (m_nPoints - IndexEnd2)) * 1000 / m_nSamplesPerSec; 
    else 
        OffsetTimeMS = (m_nIndex - IndexEnd2) * 1000 / m_nSamplesPerSec; 
 
    if (fWraps = (IndexStart2 < 0)) { 
        IndexStart1 = IndexStart2 + m_nPoints; 
        IndexEnd1 = m_nPoints - 1; 
        IndexStart2 = 0; 
 
        PointsToDisplay1 = IndexEnd1 - IndexStart1; 
    } 
 
    PointsToDisplay2 = IndexEnd2 - IndexStart2; 
 
    if (fWraps) { 
        ViewportBreak = (int) (m_Width * (float) PointsToDisplay1 / PointsToDisplay); 
 
        // Draw the first section (from the end of the POINT array) 
        DrawPartialWaveform(hdcT, 
            IndexStart1, IndexEnd1,     // Index start, Index end 
            0, ViewportBreak);          // Window start, Window end 
 
        // Draw the second section (from the beginning of the POINT array) 
        DrawPartialWaveform(hdcT, 
            IndexStart2, IndexEnd2,     // Index start, Index end 
            ViewportBreak, m_Width);    // Window start, Window end 
    } else { 
        DrawPartialWaveform(hdcT, 
            IndexStart2, IndexEnd2,     // Index start, Index end 
            0, m_Width);                // Window start, Window end 
    } 
 
    SetMapMode (hdcT, MM_TEXT); 
    SetWindowOrgEx (hdcT, 0, 0, NULL); 
    SetViewportOrgEx (hdcT, 0, 0, NULL); 
 
    BitBlt(hdc,        // Handle of destination device context 
            0,        // x-coordinate of upper-left corner 
            0,         // y-coordinate of upper-left corner 
            m_Width,// Wwidth of destination rectangle 
            m_Height,// Height of destination rectangle 
            hdcT,// Handle of source device context 
            0,          // x-coordinate of source rectangle 
            0,          // y-coordinate of source rectangle 
            SRCCOPY); // Raster operation code 
 
    SelectObject (hdcT, hBitmapOld); 
    DeleteDC (hdcT); 
    GdiFlush(); 
    ReleaseDC (m_hwnd, hdc); 
 
    // Show the size of the last buffer received 
    wsprintf (szT, "%d", m_LastMediaSampleSize); 
    SetDlgItemText (m_hwndDlg, IDC_BUFSIZE, szT); 
 
    // Show the timestamps 
    LONG mSStart; 
    LONG mSEnd = m_EndSample.Millisecs(); 
 
    CRefTime rt; 
    m_pRenderer->StreamTime (rt); 
 
    // Delta is the difference between the last sample received and 
    // the current sample playing according to the StreamTime 
    LONG mSDelta = mSEnd - rt.Millisecs(); 
    wsprintf (szT, "%d.%d", mSDelta / 1000, abs (mSDelta) % 1000); 
    SetDlgItemText (m_hwndDlg, IDC_TS_DELTA, szT); 
 
    // Show the Delta point on the horizontal trackbar as the selection 
    if (mSDelta < 1000) { 
        int SelectStart = m_nPoints - (m_nPoints * mSDelta / 1000); 
        SelectStart /= 2; 
        int SelectEnd = SelectStart + m_nPoints / 100; 
        SendMessage(m_hwndTBScroll, TBM_SETSEL, TRUE, MAKELONG (SelectStart, SelectEnd)); 
    } 
    else 
        SendMessage(m_hwndTBScroll, TBM_SETSEL, TRUE, 0L); // hide the selection 
 
    // Display the begin and end times of the sweep 
    mSEnd -= OffsetTimeMS; 
    mSStart = mSEnd - PointsToDisplay * 1000 / m_nSamplesPerSec; 
 
    wsprintf (szT, "%d.%d", mSStart / 1000, abs (mSStart) % 1000); 
    SetDlgItemText (m_hwndDlg, IDC_TS_START, szT); 
 
    wsprintf (szT, "%d.%d", mSEnd / 1000, abs (mSEnd) % 1000); 
    SetDlgItemText (m_hwndDlg, IDC_TS_LAST, szT); 
 
} // DrawWaveform 
 
 
// 
// AllocWaveBuffers 
// 
// Allocate a 1 second buffer for each channel 
// This is only called when the format changes 
// Return TRUE if allocations succeed 
// 
BOOL CScopeWindow::AllocWaveBuffers() 
{ 
    int j; 
 
    if (m_pPoints1) delete [] m_pPoints1; 
    if (m_pPoints2) delete [] m_pPoints2; 
 
    m_pPoints1 = NULL; 
    m_pPoints2 = NULL; 
    m_nPoints = 0; 
 
    m_nPoints = m_nSamplesPerSec; 
 
    if (m_pPoints1 = new POINT [m_nSamplesPerSec]) { 
        m_nPoints = m_nSamplesPerSec; 
        for (j = 0; j < m_nSamplesPerSec; j++) 
            m_pPoints1[j].x = j; 
    } 
 
    if (m_nChannels == 2) { 
        if (m_pPoints2 = new POINT [m_nSamplesPerSec]) 
            for (j = 0; j < m_nSamplesPerSec; j++) 
                m_pPoints2[j].x = j; 
    } 
 
    // Return TRUE if allocations succeeded 
    ASSERT ((m_pPoints1 != NULL) && ((m_nChannels == 2) ? (m_pPoints2 != NULL) : TRUE)); 
    return ((m_pPoints1 != NULL) && ((m_nChannels == 2) ? (m_pPoints2 != NULL) : TRUE)); 
 
} // AllocWaveBuffers 
 
 
// 
// SearchForPosZeroCrossing 
// 
// Searches backward for a positive going zero crossing in the waveform 
// 
void CScopeWindow::SearchForPosZeroCrossing(int StartPoint, int * IndexEdge) 
{ 
    if (StartPoint < 0) 
        StartPoint = 0; 
 
    int cur, last, j; 
 
    *IndexEdge = StartPoint; 
 
    last = m_pPoints1[StartPoint].y; 
 
    for (j = m_nPoints; j > 0; j--) { 
        if (--StartPoint < 0) 
            StartPoint = m_nPoints - 1; 
        cur = m_pPoints1[StartPoint].y; 
        if (cur < 0 && last >= 0) { 
            *IndexEdge = StartPoint; 
            break; 
        } 
        last = cur; 
    } 
 
} // SearchForPosZeroCrossing 
 
 
// 
// CopyWaveform 
// 
// Copy the current MediaSample into a POINT array so we can use GDI 
// to paint the waveform.  The POINT array contains a 1 second history 
// of the past waveform.  The "Y" values are normalized to a range of 
// +128 to -127 within the POINT array. 
// 
void CScopeWindow::CopyWaveform(IMediaSample *pMediaSample) 
{ 
    BYTE *pWave;                // Pointer to image data 
    int  nBytes; 
    int  nSamplesPerChan; 
 
    pMediaSample->GetPointer(&pWave); 
    ASSERT(pWave != NULL); 
 
    nBytes = pMediaSample->GetActualDataLength(); 
    nSamplesPerChan = nBytes / (m_nChannels * m_nBlockAlign); 
 
    switch (m_nBitsPerSample + m_nChannels) { 
        BYTE * pb; 
        WORD * pw; 
 
        case 9: 
        {   // Mono, 8-bit 
            pb = pWave; 
            while (nSamplesPerChan--) { 
                m_pPoints1[m_nIndex].y = (int)*pb++ - 127;  // Make zero centered 
                if (++m_nIndex == m_nSamplesPerSec) 
                    m_nIndex = 0; 
            } 
        } 
        break; 
 
        case 10: 
        {   // Stereo, 8-bit 
            pb = pWave; 
            while (nSamplesPerChan--) { 
                m_pPoints1[m_nIndex].y = (int)*pb++ - 127; // Make zero centered 
                m_pPoints2[m_nIndex].y = (int)*pb++ - 127; 
                if (++m_nIndex == m_nSamplesPerSec) 
                    m_nIndex = 0; 
            } 
        } 
        break; 
 
        case 17: 
        { // Mono, 16-bit 
            pw = (WORD *) pWave; 
            while (nSamplesPerChan--) { 
                m_pPoints1[m_nIndex].y = (int) ((short) *pw++) / 256; 
                if (++m_nIndex == m_nSamplesPerSec) 
                    m_nIndex = 0; 
            } 
        } 
        break; 
 
        case 18: 
        { // Stereo, 16-bit 
            pw = (WORD *)pWave; 
            while (nSamplesPerChan--) { 
                m_pPoints1[m_nIndex].y = (int) ((short) *pw++) / 256; 
                m_pPoints2[m_nIndex].y = (int) ((short) *pw++) / 256; 
                if (++m_nIndex == m_nSamplesPerSec) 
                    m_nIndex = 0; 
            } 
        } 
        break; 
 
        default: 
            ASSERT(0); 
            break; 
 
    } // End of format switch 
 
} // CopyWaveform 
 
 
// 
// Receive 
// 
// Called when the input pin receives another sample. 
// Copy the waveform to our circular 1 second buffer 
// 
HRESULT CScopeWindow::Receive(IMediaSample *pSample) 
{ 
    CAutoLock cAutoLock(this); 
    ASSERT(pSample != NULL); 
 
    // Has our UI been frozen 
 
    if (m_fFreeze) { 
        return NOERROR; 
    } 
 
    REFERENCE_TIME tStart, tStop; 
    pSample->GetTime (&tStart,&tStop); 
    m_StartSample = tStart; 
    m_EndSample = tStop; 
 
    // Ignore zero-length samples 
    if ((m_LastMediaSampleSize = pSample->GetActualDataLength()) == 0) 
        return NOERROR; 
 
    if (m_bStreaming == TRUE) { 
        CopyWaveform (pSample);     // Copy data to our circular buffer 
        SetEvent(m_RenderEvent);    // Set an event to display the 
                                    // new data on another thread 
        return NOERROR; 
    } 
    return NOERROR; 
 
} // Receive 
 
 
// 
// DllRegisterServer 
// 
// Handles DLL registry 
// 
STDAPI DllRegisterServer() 
{ 
    return AMovieDllRegisterServer2( TRUE ); 
 
} // DllRegisterServer 
 
 
// 
// DllUnregisterServer 
// 
STDAPI DllUnregisterServer() 
{ 
    return AMovieDllRegisterServer2( FALSE ); 
 
} // DllUnregisterServer