/* -LICENSE-START-
** Copyright (c) 2009 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
** 
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
** 
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/
// SignalGeneratorDlg.cpp : implementation file
//

#include "stdafx.h"
#include "SignalGenerator.h"
#include "SignalGeneratorDlg.h"
#include "PreviewWindow.h"
#include "DeckLinkOutputDevice.h"
#include "DeckLinkDeviceDiscovery.h"
#define _USE_MATH_DEFINES
#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

const unsigned long		kAudioWaterlevel = 48000;

// SD 75% Colour Bars
static DWORD gSD75pcColourBars[8] =
{
	0xeb80eb80, 0xa28ea22c, 0x832c839c, 0x703a7048,
	0x54c654b8, 0x41d44164, 0x237223d4, 0x10801080
};

// HD 75% Colour Bars
static DWORD gHD75pcColourBars[8] =
{
	0xeb80eb80, 0xa888a82c, 0x912c9193, 0x8534853f,
	0x3fcc3fc1, 0x33d4336d, 0x1c781cd4, 0x10801080
};

// Supported number audio channels
static const int gAudioChannels[] = { 2, 8, 16 };

CSignalGeneratorDlg::CSignalGeneratorDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CSignalGeneratorDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	
	// Initialize instance variables
	m_running = false;
	
	m_videoFrameBlack = NULL;
	m_videoFrameBars = NULL;
	m_audioBuffer = NULL;
}

void CSignalGeneratorDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDOK, m_startButton);
	DDX_Control(pDX, IDC_COMBO_DEVICE, m_deviceListCombo);
	DDX_Control(pDX, IDC_COMBO_SIGNAL, m_outputSignalCombo);
	DDX_Control(pDX, IDC_COMBO_CHANNELS, m_audioChannelCombo);
	DDX_Control(pDX, IDC_COMBO_AUDIO_DEPTH, m_audioSampleDepthCombo);
	DDX_Control(pDX, IDC_COMBO_VIDEO_FORMAT, m_videoFormatCombo);
	DDX_Control(pDX, IDC_COMBO_PIXEL_FORMAT, m_pixelFormatCombo);
	DDX_Control(pDX, IDC_PREVIEW_BOX, m_previewBox);
}

BEGIN_MESSAGE_MAP(CSignalGeneratorDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDOK, &CSignalGeneratorDlg::OnBnClickedOk)
	ON_CBN_SELCHANGE(IDC_COMBO_PIXEL_FORMAT, &CSignalGeneratorDlg::OnCbnSelchangeComboPixelFormat)
	ON_CBN_SELCHANGE(IDC_COMBO_DEVICE, &CSignalGeneratorDlg::OnNewDeviceSelected)
	ON_MESSAGE(WM_ADD_DEVICE_MESSAGE, &CSignalGeneratorDlg::OnAddDevice)
	ON_MESSAGE(WM_REMOVE_DEVICE_MESSAGE, &CSignalGeneratorDlg::OnRemoveDevice)
	ON_WM_CLOSE()
END_MESSAGE_MAP()


void CSignalGeneratorDlg::RefreshDisplayModeMenu(void)
{
	// Populate the display mode combo with a list of display modes supported by the installed DeckLink card
	IDeckLinkDisplayModeIterator*	displayModeIterator;
	IDeckLinkDisplayMode*			deckLinkDisplayMode;
	IDeckLinkOutput*                deckLinkOutput;
	BMDPixelFormat					pixelFormat;
	
	pixelFormat = (BMDPixelFormat)m_pixelFormatCombo.GetItemData(m_pixelFormatCombo.GetCurSel());

	for (int i = 1; i < m_videoFormatCombo.GetCount(); i++)
	{
		deckLinkDisplayMode = (IDeckLinkDisplayMode*)m_videoFormatCombo.GetItemDataPtr(i-1);
		deckLinkDisplayMode->Release();
	}
	m_videoFormatCombo.ResetContent();

	deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	if (deckLinkOutput->GetDisplayModeIterator(&displayModeIterator) != S_OK)
		return;
	
	while (displayModeIterator->Next(&deckLinkDisplayMode) == S_OK)
	{
		BSTR					modeName;
		int						newIndex;
		HRESULT					hr;
		BMDDisplayModeSupport	displayModeSupport;
		BMDVideoOutputFlags		videoOutputFlags = bmdVideoOutputDualStream3D;

		if (deckLinkDisplayMode->GetName(&modeName) != S_OK)
		{
			deckLinkDisplayMode->Release();
			continue;
		}
		
		CString modeNameCString(modeName);
		newIndex = m_videoFormatCombo.AddString(modeNameCString);
		m_videoFormatCombo.SetItemDataPtr(newIndex, deckLinkDisplayMode); 
		
		hr = deckLinkOutput->DoesSupportVideoMode(deckLinkDisplayMode->GetDisplayMode(), pixelFormat, videoOutputFlags, &displayModeSupport, NULL);
		if (hr != S_OK || ! displayModeSupport)
		{
			SysFreeString(modeName);
			continue;
		}

		CString modeName3DCString(modeName);
		modeName3DCString += _T(" 3D");
		newIndex = m_videoFormatCombo.AddString(modeName3DCString);
		m_videoFormatCombo.SetItemDataPtr(newIndex, deckLinkDisplayMode);
		deckLinkDisplayMode->AddRef();

		SysFreeString(modeName);
	}
	displayModeIterator->Release();

	m_videoFormatCombo.SetCurSel(0);
}


void CSignalGeneratorDlg::RefreshAudioChannelMenu(void)
{
	IDeckLink*				deckLink;
	IDeckLinkAttributes*	deckLinkAttributes = NULL;
	LONGLONG				maxAudioChannels;

	deckLink = m_selectedDevice->GetDeckLinkInstance();

	// Get DeckLink attributes to determine number of audio channels
	if (deckLink->QueryInterface(IID_IDeckLinkAttributes, (void**)&deckLinkAttributes) != S_OK)
		goto bail;

	// Get max number of audio channels supported by DeckLink device
	if (deckLinkAttributes->GetInt(BMDDeckLinkMaximumAudioChannels, &maxAudioChannels) != S_OK)
		goto bail;

	m_audioChannelCombo.ResetContent();

	// Scan through Audio channel popup menu and disable invalid entries
	for (int i = 0; i < sizeof(gAudioChannels) / sizeof(*gAudioChannels); i++)
	{
		if (maxAudioChannels < (LONGLONG)gAudioChannels[i])
			break;

		CString audioChannelString;
		audioChannelString.Format(_T("%d"), gAudioChannels[i]);

		m_audioChannelCombo.AddString(audioChannelString);
		m_audioChannelCombo.SetItemData(i, gAudioChannels[i]);
	}

	m_audioChannelCombo.SetCurSel(m_audioChannelCombo.GetCount() - 1);

bail:
	if (deckLinkAttributes)
		deckLinkAttributes->Release();
}

// CSignalGeneratorDlg message handlers

BOOL CSignalGeneratorDlg::OnInitDialog()
{
	bool			success = false;
	
	CDialog::OnInitDialog();
	
	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// Set the item data for combo box entries to store audio channel count and sample depth information
	m_outputSignalCombo.SetItemData(0, kOutputSignalPip);
	m_outputSignalCombo.SetItemData(1, kOutputSignalDrop);
	//
	m_audioSampleDepthCombo.SetItemData(0, 16);	// 16-bit samples
	m_audioSampleDepthCombo.SetItemData(1, 32);	// 32-bit samples

	m_pixelFormatCombo.SetItemData(0, bmdFormat8BitYUV);
	m_pixelFormatCombo.SetItemData(1, bmdFormat10BitYUV);
	m_pixelFormatCombo.SetItemData(2, bmdFormat8BitARGB);
	m_pixelFormatCombo.SetItemData(3, bmdFormat10BitRGB);

	// Select the first item in each combo box
	m_outputSignalCombo.SetCurSel(0);
	m_audioSampleDepthCombo.SetCurSel(0);
	m_pixelFormatCombo.SetCurSel(0);

	//
	// Create and initialise DeckLink device discovery and preview objects
	m_deckLinkDiscovery = new DeckLinkDeviceDiscovery(this);
	if (m_deckLinkDiscovery != NULL)
	{
		if (!m_deckLinkDiscovery->enable())
		{
			MessageBox(_T("This application requires the DeckLink drivers installed.\nPlease install the Blackmagic DeckLink drivers to use the features of this application."), _T("Error"));
			goto bail;
		}
	}

	m_previewWindow = new PreviewWindow();
	if (m_previewWindow->init(&m_previewBox) == false)
	{
		MessageBox(_T("This application was unable to initialise the preview window"), _T("Error"));
	}

bail: 
	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CSignalGeneratorDlg::EnableInterface (BOOL enable)
{
	// Set the enable state of user interface elements
	m_deviceListCombo.EnableWindow(enable);
	m_outputSignalCombo.EnableWindow(enable);
	m_audioChannelCombo.EnableWindow(enable);
	m_audioSampleDepthCombo.EnableWindow(enable);
	m_videoFormatCombo.EnableWindow(enable);
	m_pixelFormatCombo.EnableWindow(enable);
}

void CSignalGeneratorDlg::OnBnClickedOk()
{
	if (m_running == false)
		StartRunning();
	else
		StopRunning();
}

void CSignalGeneratorDlg::OnNewDeviceSelected()
{
	int		selectedDeviceIndex;

	selectedDeviceIndex = m_deviceListCombo.GetCurSel();
	if (selectedDeviceIndex < 0)
		return;

	m_selectedDevice = (DeckLinkOutputDevice*)m_deviceListCombo.GetItemDataPtr(selectedDeviceIndex);

	// Update the video mode popup menu
	RefreshDisplayModeMenu();

	// Update available audio channels
	RefreshAudioChannelMenu();

	// Enable the interface
	EnableInterface(true);
}

void CSignalGeneratorDlg::AddDevice(IDeckLink* deckLink)
{
	int deviceIndex;
	DeckLinkOutputDevice* newDevice = new DeckLinkOutputDevice(this, deckLink);

	// Initialise new DeckLinkDevice object
	if (!newDevice->Init())
	{
		// Device does not have IDeckLinkOutput interface, eg it is a DeckLink Mini Recorder
		newDevice->Release();
		return;
	}

	// Add this DeckLink device to the device list
	deviceIndex = m_deviceListCombo.AddString(newDevice->GetDeviceName());
	if (deviceIndex < 0)
		return;

	m_deviceListCombo.SetItemDataPtr(deviceIndex, newDevice);

	if (m_deviceListCombo.GetCount() == 1)
	{
		// We have added our first item, refresh and enable UI
		m_deviceListCombo.SetCurSel(0);
		OnNewDeviceSelected();

		m_startButton.EnableWindow(TRUE);
		EnableInterface(true);
	}
}

void CSignalGeneratorDlg::RemoveDevice(IDeckLink* deckLink)
{
	int deviceIndex = -1;
	DeckLinkOutputDevice* deviceToRemove = NULL;

	// Find the combo box entry to remove (there may be multiple entries with the same name, but each
	// will have a different data pointer).
	for (deviceIndex = 0; deviceIndex < m_deviceListCombo.GetCount(); ++deviceIndex)
	{
		deviceToRemove = (DeckLinkOutputDevice*)m_deviceListCombo.GetItemDataPtr(deviceIndex);
		if (deviceToRemove->GetDeckLinkInstance() == deckLink)
			break;
	}

	if (deviceToRemove == NULL)
		return;

	// Remove device from list
	m_deviceListCombo.DeleteString(deviceIndex);

	// If playback is ongoing, stop it
	if ((m_selectedDevice == deviceToRemove) && m_running)
		StopRunning();

	// Check how many devices are left
	if (m_deviceListCombo.GetCount() == 0)
	{
		// We have removed the last device, disable the interface.
		m_startButton.EnableWindow(FALSE);
		EnableInterface(false);
		m_selectedDevice = NULL;
	}
	else if (m_selectedDevice == deviceToRemove)
	{
		// The device that was removed was the one selected in the UI.
		// Select the first available device in the list and reset the UI.
		m_deviceListCombo.SetCurSel(0);
		OnNewDeviceSelected();

		m_startButton.EnableWindow(TRUE);
	}

	// Release DeckLinkDevice instance
	deviceToRemove->Release();
}

static int GetBytesPerPixel(BMDPixelFormat pixelFormat)
{
	int bytesPerPixel = 2;

	switch (pixelFormat)
	{
	case bmdFormat8BitYUV:
		bytesPerPixel = 2;
		break;
	case bmdFormat8BitARGB:
	case bmdFormat10BitYUV:
	case bmdFormat10BitRGB:
		bytesPerPixel = 4;
		break;
	}

	return bytesPerPixel;
}

SignalGenerator3DVideoFrame* CSignalGeneratorDlg::CreateBlackFrame ()
{
	IDeckLinkOutput*                deckLinkOutput;
	IDeckLinkMutableVideoFrame*		referenceBlack = NULL;
	IDeckLinkMutableVideoFrame*		scheduleBlack = NULL;
	HRESULT							hr;
	BMDPixelFormat					pixelFormat;
	int								bytesPerPixel;
	IDeckLinkVideoConversion*		frameConverter = NULL;
	SignalGenerator3DVideoFrame*	ret = NULL;

	pixelFormat = (BMDPixelFormat)m_pixelFormatCombo.GetItemData(m_pixelFormatCombo.GetCurSel());
	bytesPerPixel = GetBytesPerPixel(pixelFormat);

	deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*bytesPerPixel, pixelFormat, bmdFrameFlagDefault, &scheduleBlack);
	if (hr != S_OK)
		goto bail;

	if (pixelFormat == bmdFormat8BitYUV)
	{
		FillBlack(scheduleBlack);
	}
	else
	{
		hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBlack);
		if (hr != S_OK)
			goto bail;
		FillBlack(referenceBlack);

		hr = CoCreateInstance(CLSID_CDeckLinkVideoConversion, NULL, CLSCTX_ALL, IID_IDeckLinkVideoConversion, (void**)&frameConverter);
		if (hr != S_OK)
			goto bail;

		hr = frameConverter->ConvertFrame(referenceBlack, scheduleBlack);
		if (hr != S_OK)
			goto bail;
	}

	ret = new SignalGenerator3DVideoFrame(scheduleBlack);

bail:
	if (referenceBlack)
		referenceBlack->Release();
	if (scheduleBlack)
		scheduleBlack->Release();
	if (frameConverter)
		frameConverter->Release();

	return ret;
}

SignalGenerator3DVideoFrame* CSignalGeneratorDlg::CreateBarsFrame ()
{
	IDeckLinkOutput*                deckLinkOutput;
	IDeckLinkMutableVideoFrame*		referenceBarsLeft = NULL;
	IDeckLinkMutableVideoFrame*		referenceBarsRight = NULL;
	IDeckLinkMutableVideoFrame*		scheduleBarsLeft = NULL;
	IDeckLinkMutableVideoFrame*		scheduleBarsRight = NULL;
	HRESULT							hr;
	BMDPixelFormat					pixelFormat;
	int								bytesPerPixel;
	IDeckLinkVideoConversion*		frameConverter = NULL;
	SignalGenerator3DVideoFrame*	ret = NULL;

	pixelFormat = (BMDPixelFormat)m_pixelFormatCombo.GetItemData(m_pixelFormatCombo.GetCurSel());
	bytesPerPixel = GetBytesPerPixel(pixelFormat);

	deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*bytesPerPixel, pixelFormat, bmdFrameFlagDefault, &scheduleBarsLeft);
	if (hr != S_OK)
		goto bail;

	hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*bytesPerPixel, pixelFormat, bmdFrameFlagDefault, &scheduleBarsRight);
	if (hr != S_OK)
		goto bail;

	if (pixelFormat == bmdFormat8BitYUV)
	{
		FillColourBars(scheduleBarsLeft, false);
		FillColourBars(scheduleBarsRight, true);
	}
	else
	{
		hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBarsLeft);
		if (hr != S_OK)
			goto bail;

		hr = deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBarsRight);
		if (hr != S_OK)
			goto bail;

		FillColourBars(referenceBarsLeft, false);
		FillColourBars(referenceBarsRight, true);

		hr = CoCreateInstance(CLSID_CDeckLinkVideoConversion, NULL, CLSCTX_ALL, IID_IDeckLinkVideoConversion, (void**)&frameConverter);
		if (hr != S_OK)
			goto bail;

		hr = frameConverter->ConvertFrame(referenceBarsLeft, scheduleBarsLeft);
		if (hr != S_OK)
			goto bail;

		hr = frameConverter->ConvertFrame(referenceBarsRight, scheduleBarsRight);
		if (hr != S_OK)
			goto bail;
	}

	ret = new SignalGenerator3DVideoFrame(scheduleBarsLeft, scheduleBarsRight);

bail:
	if (referenceBarsLeft)
		referenceBarsLeft->Release();
	if (referenceBarsRight)
		referenceBarsRight->Release();
	if (scheduleBarsLeft)
		scheduleBarsLeft->Release();
	if (scheduleBarsRight)
		scheduleBarsRight->Release();
	if (frameConverter)
		frameConverter->Release();

	return ret;
}

void	CSignalGeneratorDlg::StartRunning ()
{
	IDeckLinkOutput*        deckLinkOutput;
	IDeckLinkDisplayMode*	videoDisplayMode = NULL;
	BMDVideoOutputFlags		videoOutputFlags = bmdVideoOutputFlagDefault;
	int						curSelection;
	CString					videoFormatName;

	curSelection = m_videoFormatCombo.GetCurSel();
	m_videoFormatCombo.GetLBText(curSelection, videoFormatName);
	if (videoFormatName.Find(_T(" 3D"), 0) != -1)
		videoOutputFlags = bmdVideoOutputDualStream3D;
	
	// Determine the audio and video properties for the output stream
	m_outputSignal = (OutputSignal)m_outputSignalCombo.GetCurSel();
	m_audioChannelCount = m_audioChannelCombo.GetItemData(m_audioChannelCombo.GetCurSel());
	m_audioSampleDepth = (BMDAudioSampleType)m_audioSampleDepthCombo.GetItemData(m_audioSampleDepthCombo.GetCurSel());
	m_audioSampleRate = bmdAudioSampleRate48kHz;
	//
	// - Extract the IDeckLinkDisplayMode from the display mode popup menu (stashed in the item's tag)
	videoDisplayMode = (IDeckLinkDisplayMode*)m_videoFormatCombo.GetItemDataPtr(m_videoFormatCombo.GetCurSel());
	m_frameWidth = videoDisplayMode->GetWidth();
	m_frameHeight = videoDisplayMode->GetHeight();
	videoDisplayMode->GetFrameRate(&m_frameDuration, &m_frameTimescale);
	// Calculate the number of frames per second, rounded up to the nearest integer.  For example, for NTSC (29.97 FPS), framesPerSecond == 30.
	m_framesPerSecond = (unsigned long)((m_frameTimescale + (m_frameDuration-1))  /  m_frameDuration);
	
	// Set the video output mode
	deckLinkOutput = m_selectedDevice->GetDeviceOutput();
	if (deckLinkOutput->EnableVideoOutput(videoDisplayMode->GetDisplayMode(), videoOutputFlags) != S_OK)
		goto bail;

	// Set the audio output mode
	if (deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, m_audioSampleDepth, m_audioChannelCount, bmdAudioOutputStreamTimestamped) != S_OK)
		goto bail;
	
	// Set Video, audio and screen preview callbacks with DeckLinkOutputDevice delegate
	deckLinkOutput->SetScheduledFrameCompletionCallback(m_selectedDevice);
	deckLinkOutput->SetAudioCallback(m_selectedDevice);
	deckLinkOutput->SetScreenPreviewCallback(m_previewWindow);

	// Generate one second of audio tone
	m_audioSamplesPerFrame = (unsigned long)((m_audioSampleRate * m_frameDuration) / m_frameTimescale);
	m_audioBufferSampleLength = (unsigned long)((m_framesPerSecond * m_audioSampleRate * m_frameDuration) / m_frameTimescale);
	m_audioBuffer = HeapAlloc(GetProcessHeap(), 0, (m_audioBufferSampleLength * m_audioChannelCount * (m_audioSampleDepth / 8)));
	if (m_audioBuffer == NULL)
		goto bail;
	FillSine(m_audioBuffer, m_audioBufferSampleLength, m_audioChannelCount, m_audioSampleDepth);
	
	// Generate a frame of black
	m_videoFrameBlack = CreateBlackFrame();
	if (! m_videoFrameBlack)
		goto bail;

	// Generate a frame of colour bars
	m_videoFrameBars = CreateBarsFrame();
	if (! m_videoFrameBars)
		goto bail;

	// Begin video preroll by scheduling a second of frames in hardware
	m_totalFramesScheduled = 0;
	for (unsigned i = 0; i < m_framesPerSecond; i++)
		ScheduleNextFrame(true);
	
	// Begin audio preroll.  This will begin calling our audio callback, which will start the DeckLink output stream.
	m_totalAudioSecondsScheduled = 0;
	if (deckLinkOutput->BeginAudioPreroll() != S_OK)
		goto bail;
	
	// Success; update the UI
	m_running = true;
	m_startButton.SetWindowText(_T("Stop"));
	// Disable the user interface while running (prevent the user from making changes to the output signal)
	EnableInterface(FALSE);
	
	return;
	
bail:
	// *** Error-handling code.  Cleanup any resources that were allocated. *** //
	StopRunning();
}


void	CSignalGeneratorDlg::StopRunning ()
{
	IDeckLinkOutput* deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	// Stop the audio and video output streams immediately
	deckLinkOutput->StopScheduledPlayback(0, NULL, 0);

	// Dereference DeckLinkOutputDevice delegate from callbacks
	deckLinkOutput->SetScheduledFrameCompletionCallback(NULL);
	deckLinkOutput->SetAudioCallback(NULL);
	deckLinkOutput->SetScreenPreviewCallback(NULL);

	// Disable video and audio outputs
	deckLinkOutput->DisableAudioOutput();
	deckLinkOutput->DisableVideoOutput();

	if (m_videoFrameBlack != NULL)
		m_videoFrameBlack->Release();
	m_videoFrameBlack = NULL;
	
	if (m_videoFrameBars != NULL)
		m_videoFrameBars->Release();
	m_videoFrameBars = NULL;
	
	if (m_audioBuffer != NULL)
		HeapFree(GetProcessHeap(), 0, m_audioBuffer);
	m_audioBuffer = NULL;
	
	// Success; update the UI
	m_running = false;
	m_startButton.SetWindowText(_T("Start"));
	// Re-enable the user interface when stopped
	EnableInterface(TRUE);
}


void	CSignalGeneratorDlg::ScheduleNextFrame (bool prerolling)
{
	IDeckLinkOutput* deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	if (prerolling == false)
	{
		// If not prerolling, make sure that playback is still active
		if (m_running == false)
			return;
	}
	
	if (m_outputSignal == kOutputSignalPip)
	{
		if ((m_totalFramesScheduled % m_framesPerSecond) == 0)
		{
			// On each second, schedule a frame of bars
			if (deckLinkOutput->ScheduleVideoFrame(m_videoFrameBars, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of black
			if (deckLinkOutput->ScheduleVideoFrame(m_videoFrameBlack, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
	}
	else
	{
		if ((m_totalFramesScheduled % m_framesPerSecond) == 0)
		{
			// On each second, schedule a frame of black
			if (deckLinkOutput->ScheduleVideoFrame(m_videoFrameBlack, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of color bars
			if (deckLinkOutput->ScheduleVideoFrame(m_videoFrameBars, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
	}
	
	m_totalFramesScheduled += 1;
}

void	CSignalGeneratorDlg::WriteNextAudioSamples ()
{
	IDeckLinkOutput* deckLinkOutput = m_selectedDevice->GetDeviceOutput();

	// Write one second of audio to the DeckLink API.
	
	if (m_outputSignal == kOutputSignalPip)
	{
		// Schedule one-frame of audio tone
		if (deckLinkOutput->ScheduleAudioSamples(m_audioBuffer, m_audioSamplesPerFrame, (m_totalAudioSecondsScheduled * m_audioBufferSampleLength), m_audioSampleRate, NULL) != S_OK)
			return;
	}
	else
	{
		// Schedule one-second (minus one frame) of audio tone
		if (deckLinkOutput->ScheduleAudioSamples(m_audioBuffer, (m_audioBufferSampleLength - m_audioSamplesPerFrame), (m_totalAudioSecondsScheduled * m_audioBufferSampleLength) + m_audioSamplesPerFrame, m_audioSampleRate, NULL) != S_OK)
			return;
	}
	
	m_totalAudioSecondsScheduled += 1;
}

LRESULT CSignalGeneratorDlg::OnAddDevice(WPARAM wParam, LPARAM lParam)
{
	// A new device has been connected
	AddDevice((IDeckLink*)wParam);
	return 0;
}

LRESULT	CSignalGeneratorDlg::OnRemoveDevice(WPARAM wParam, LPARAM lParam)
{
	// An existing device has been disconnected
	RemoveDevice((IDeckLink*)wParam);
	return 0;
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CSignalGeneratorDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

void CSignalGeneratorDlg::OnClose()
{
	if (m_running)
		StopRunning();

	// Disable DeckLink device discovery
	m_deckLinkDiscovery->disable();

	// Release all DeckLinkOutputDevice instances
	while (m_deviceListCombo.GetCount() > 0)
	{
		DeckLinkOutputDevice* device = (DeckLinkOutputDevice*)m_deviceListCombo.GetItemDataPtr(0);
		device->Release();
		m_deviceListCombo.DeleteString(0);
	}

	// Release all DisplayMode instances
	while (m_videoFormatCombo.GetCount() > 0)
	{
		IDeckLinkDisplayMode* deckLinkDisplayMode = (IDeckLinkDisplayMode*)m_videoFormatCombo.GetItemDataPtr(0);
		deckLinkDisplayMode->Release();
		m_videoFormatCombo.DeleteString(0);
	}

	// Release PreviewWindow instance
	if (m_previewWindow != NULL)
	{
		m_previewWindow->Release();
	}

	// Release DeckLinkDeviceDiscovery instance
	if (m_deckLinkDiscovery != NULL)
	{
		m_deckLinkDiscovery->Release();
	}

	CDialog::OnClose();
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSignalGeneratorDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



/*****************************************/


void	FillSine (void* audioBuffer, unsigned long samplesToWrite, unsigned long channels, unsigned long sampleDepth)
{
	if (sampleDepth == 16)
	{
		INT16*		nextBuffer;
		
		nextBuffer = (INT16*)audioBuffer;
		for (unsigned i = 0; i < samplesToWrite; i++)
		{
			INT16		sample;
			
			sample = (INT16)(24576.0 * sin((i * 2.0 * M_PI) / 48.0));
			for (unsigned ch = 0; ch < channels; ch++)
				*(nextBuffer++) = sample;
		}
	}
	else if (sampleDepth == 32)
	{
		INT32*		nextBuffer;
		
		nextBuffer = (INT32*)audioBuffer;
		for (unsigned i = 0; i < samplesToWrite; i++)
		{
			INT32		sample;
			
			sample = (INT32)(1610612736.0 * sin((i * 2.0 * M_PI) / 48.0));
			for (unsigned ch = 0; ch < channels; ch++)
				*(nextBuffer++) = sample;
		}
	}
}

void	FillColourBars (IDeckLinkVideoFrame* theFrame, bool reversed)
{
	DWORD*			nextWord;
	unsigned long	width;
	unsigned long	height;
	DWORD*			bars;
	unsigned long	colourBarCount;
	
	theFrame->GetBytes((void**)&nextWord);
	width = theFrame->GetWidth();
	height = theFrame->GetHeight();
	
	if (width > 720)
	{
		bars = gHD75pcColourBars;
		colourBarCount = sizeof(gHD75pcColourBars) / sizeof(gHD75pcColourBars[0]);
	}
	else
	{
		bars = gSD75pcColourBars;
		colourBarCount = sizeof(gSD75pcColourBars) / sizeof(gSD75pcColourBars[0]);
	}

	for (unsigned y = 0; y < height; y++)
	{
		for (unsigned x = 0; x < width; x+=2)
		{
			int pos = x * colourBarCount / width;

			if (reversed)
				pos = colourBarCount - pos - 1;

			*(nextWord++) = bars[pos];
		}
	}
}

void	FillBlack (IDeckLinkVideoFrame* theFrame)
{
	DWORD*			nextWord;
	unsigned long	width;
	unsigned long	height;
	unsigned long	wordsRemaining;
	
	theFrame->GetBytes((void**)&nextWord);
	width = theFrame->GetWidth();
	height = theFrame->GetHeight();
	
	wordsRemaining = (width*2 * height) / 4;
	
	while (wordsRemaining-- > 0)
		*(nextWord++) = 0x10801080;
}

void CSignalGeneratorDlg::OnCbnSelchangeComboPixelFormat()
{
	RefreshDisplayModeMenu();
}
