/* -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-
*/
//
//  SyncController.mm
//  Signal Generator
//

#import <CoreFoundation/CFString.h>
#include <libkern/OSAtomic.h>

#import "SyncController.h"
#import "SignalGenerator3DVideoFrame.h"

const uint32_t		kAudioWaterlevel = 48000;

#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))

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

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

@implementation SyncController

@synthesize window;

- (void)addDevice:(IDeckLink*)deckLink
{
	// Create new PlaybackDelegate object to wrap around new IDeckLink instance
	PlaybackDelegate* device = new PlaybackDelegate(self, deckLink);

	// Initialise new PlaybackDelegate object
	if (! device->init())
	{
		[self showErrorMessage:@"Error initialising the new device" title:@"This application is unable to initialise the new device"];
		device->Release();
		return;
	}

	[[deviceListPopup menu] addItemWithTitle:(NSString*)device->getDeviceName() action:nil keyEquivalent:@""];
	[[deviceListPopup lastItem] setTag:(NSInteger)device];

	if ([deviceListPopup numberOfItems] == 1)
	{
		// We have added our first item, enable the interface
		[deviceListPopup selectItemAtIndex:0];
		[self newDeviceSelected:nil];
		
		[startButton setEnabled:YES];
		[self enableInterface:YES];
	}
}

- (void)removeDevice:(IDeckLink*)deckLink
{
	PlaybackDelegate* deviceToRemove = NULL;
	PlaybackDelegate* removalCandidate = NULL;
	NSInteger index = 0;

	// Find the DeckLinkDevice that wraps the IDeckLink being removed
	for (NSMenuItem* item in [deviceListPopup itemArray])
	{
		removalCandidate = (PlaybackDelegate*)[item tag];
		
		if (removalCandidate->getDeckLinkDevice() == deckLink)
		{
			deviceToRemove = removalCandidate;
			break;
		}
		++index;
	}

	if (deviceToRemove == NULL)
		return;

	// If playback is ongoing, stop it
	if ( (selectedDevice == deviceToRemove) && (running == YES) )
		[self stopRunning];

	[deviceListPopup removeItemAtIndex:index];

	if ([deviceListPopup numberOfItems] == 0)
	{
		// We have removed the last item, disable the interface
		[startButton setEnabled:NO];
		[self enableInterface:NO];
		selectedDevice = NULL;
	}
	else if (selectedDevice == deviceToRemove)
	{
		// Select the first device in the list and enable the interface
		[deviceListPopup selectItemAtIndex:0];
		[self newDeviceSelected:nil];
		
		[startButton setEnabled:YES];
	}

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

- (void) refreshDisplayModeMenu
{
	// Populate the display mode menu with a list of display modes supported by the installed DeckLink card
	IDeckLinkDisplayModeIterator*		displayModeIterator;
	IDeckLinkDisplayMode*				deckLinkDisplayMode;
	IDeckLinkOutput*					deckLinkOutput;
	BMDPixelFormat						pixelFormat;
	int									i;

	pixelFormat = [[pixelFormatPopup selectedItem] tag];

	for (i = 0; i < [videoFormatPopup numberOfItems]; i++)
	{
		deckLinkDisplayMode = (IDeckLinkDisplayMode*)[[videoFormatPopup itemAtIndex:i] tag];
		if (! deckLinkDisplayMode)
			continue;
		deckLinkDisplayMode->Release();
	}

	[videoFormatPopup removeAllItems];

	deckLinkOutput = selectedDevice->getDeviceOutput();

	if (deckLinkOutput->GetDisplayModeIterator(&displayModeIterator) != S_OK)
		return;

	while (displayModeIterator->Next(&deckLinkDisplayMode) == S_OK)
	{
		CFStringRef				modeName;
		CFStringRef				modeName3D;
		BMDDisplayModeSupport	displayModeSupport;
		BMDVideoOutputFlags		videoOutputFlags;
		HRESULT					hr;

		if (deckLinkDisplayMode->GetName(&modeName) != S_OK)
		{
			deckLinkDisplayMode->Release();
			deckLinkDisplayMode = NULL;
			continue;
		}

		// Add this item to the video format poup menu
		[videoFormatPopup addItemWithTitle:(NSString*)modeName];

		// Save the IDeckLinkDisplayMode in the menu item's tag
		[[videoFormatPopup lastItem] setTag:(NSInteger)deckLinkDisplayMode];

		videoOutputFlags = deckLinkDisplayMode->GetFlags() | bmdVideoOutputDualStream3D;

		hr = deckLinkOutput->DoesSupportVideoMode(deckLinkDisplayMode->GetDisplayMode(), pixelFormat, videoOutputFlags, &displayModeSupport, NULL);
		if (hr != S_OK || displayModeSupport == bmdDisplayModeNotSupported)
		{
			CFRelease(modeName);
			continue;
		}

		modeName3D = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ 3D"), modeName);
		[videoFormatPopup addItemWithTitle:(NSString*)modeName3D];

		// Save the IDeckLinkDisplayMode in the menu item's tag
		deckLinkDisplayMode->AddRef();
		[[videoFormatPopup lastItem] setTag:(NSInteger)deckLinkDisplayMode];

		CFRelease(modeName3D);
		CFRelease(modeName);
	}

	displayModeIterator->Release();
	displayModeIterator = NULL;

	if ([videoFormatPopup numberOfItems] == 0)
		[startButton setEnabled:false];
	else
		[startButton setEnabled:true];
}

- (void)refreshAudioChannelMenu
{
	IDeckLink*				deckLink;
	IDeckLinkAttributes*	deckLinkAttributes;
	int64_t					maxAudioChannels;
	NSMenuItem*				audioChannelPopupItem;
	int						audioChannelSelected;
	int						currentAudioChannel;

	audioChannelSelected = [[audioChannelPopup selectedItem] tag];

	deckLink = selectedDevice->getDeckLinkDevice();

	// 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;
	
	// Scan through Audio channel popup menu and disable invalid entries
	for (int i = 0; i < [audioChannelPopup numberOfItems]; i++)
	{
		audioChannelPopupItem = [audioChannelPopup itemAtIndex:i];
		currentAudioChannel = [audioChannelPopupItem tag];
		
		if ( maxAudioChannels >= (int64_t) currentAudioChannel )
		{
			[audioChannelPopupItem setHidden:NO];
			if ( audioChannelSelected >= currentAudioChannel )
				[audioChannelPopup selectItemAtIndex:i];
		}
		else
			[audioChannelPopupItem setHidden:YES];
	}

bail:
	if (deckLinkAttributes)
	{
		deckLinkAttributes->Release();
		deckLinkAttributes = NULL;
	}
}

- (IBAction)newDeviceSelected:(id)sender
{
	IDeckLinkOutput*	deckLinkOutput;

	// Get the DeckLinkDevice object for the selected menu item.
	selectedDevice = (PlaybackDelegate*)[[deviceListPopup selectedItem] tag];
	
	// Update the display mode popup menu
	[self refreshDisplayModeMenu];

	// Set Screen Preview callback for selected device
	deckLinkOutput = selectedDevice->getDeviceOutput();
	deckLinkOutput->SetScreenPreviewCallback(CreateCocoaScreenPreview(previewView));
	
	// Update available audio channels
	[self refreshAudioChannelMenu];

	// Enable the interface
	[self enableInterface:YES];
}

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

	pixelFormat = [[pixelFormatPopup selectedItem] tag];
	bytesPerPixel = GetBytesPerPixel(pixelFormat);

	deckLinkOutput = selectedDevice->getDeviceOutput();
	
	hr = deckLinkOutput->CreateVideoFrame(frameWidth, frameHeight, frameWidth*bytesPerPixel, pixelFormat, bmdFrameFlagDefault, &scheduleBlack);
	if (hr != S_OK)
		goto bail;

	// 8-bit YUV pixels can be filled directly without conversion
	if (pixelFormat == bmdFormat8BitYUV)
	{
		FillBlack(scheduleBlack);
	}
	else
	{
		// If the pixel formats are different create and fill an 8 bit YUV reference frame
		hr = deckLinkOutput->CreateVideoFrame(frameWidth, frameHeight, frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBlack);
		if (hr != S_OK)
			goto bail;
		FillBlack(referenceBlack);

		frameConverter = CreateVideoConversionInstance();

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

	ret = new SignalGenerator3DVideoFrame(scheduleBlack);

bail:
	if (referenceBlack)
	{
		referenceBlack->Release();
		referenceBlack = NULL;
	}
	if (scheduleBlack)
	{
		scheduleBlack->Release();
		scheduleBlack = NULL;
	}
	if (frameConverter)
	{
		frameConverter->Release();
		frameConverter = NULL;
	}

	return ret;
}

- (SignalGenerator3DVideoFrame*) 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 = [[pixelFormatPopup selectedItem] tag];
	bytesPerPixel = GetBytesPerPixel(pixelFormat);

	deckLinkOutput = selectedDevice->getDeviceOutput();

	// Request a left and right frame from the device
	hr = deckLinkOutput->CreateVideoFrame(frameWidth, frameHeight, frameWidth*bytesPerPixel, pixelFormat, bmdFrameFlagDefault, &scheduleBarsLeft);
	if (hr != S_OK)
		goto bail;

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

	// 8-bit YUV pixels can be filled directly without conversion
	if (pixelFormat == bmdFormat8BitYUV)
	{
		FillColourBars(scheduleBarsLeft, false);
		FillColourBars(scheduleBarsRight, true);
	}
	else
	{
		// If the pixel formats are different create and fill an 8 bit YUV reference frame
		hr = deckLinkOutput->CreateVideoFrame(frameWidth, frameHeight, frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBarsLeft);
		if (hr != S_OK)
			goto bail;
		hr = deckLinkOutput->CreateVideoFrame(frameWidth, frameHeight, frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &referenceBarsRight);
		if (hr != S_OK)
			goto bail;

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

		frameConverter = CreateVideoConversionInstance();

		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();
		referenceBarsLeft = NULL;
	}
	if (referenceBarsRight)
	{
		referenceBarsRight->Release();
		referenceBarsRight = NULL;
	}
	if (scheduleBarsLeft)
	{
		scheduleBarsLeft->Release();
		scheduleBarsLeft = NULL;
	}
	if (scheduleBarsRight)
	{
		scheduleBarsRight->Release();
		scheduleBarsRight = NULL;
	}
	if (frameConverter)
	{
		frameConverter->Release();
		frameConverter = NULL;
	}

	return ret;
}

- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
	//
	// Setup UI

	// Empty popup menus
	[deviceListPopup removeAllItems];
	[videoFormatPopup removeAllItems];

	// Disable the interface
	[startButton setEnabled:NO];
	[self enableInterface:NO];

	//
	// Create and initialise DeckLink device discovery and preview objects
	deckLinkDiscovery = new DeckLinkDeviceDiscovery(self);
	if (deckLinkDiscovery != NULL)
	{
		deckLinkDiscovery->enable();
	}
	else
	{
		[self showErrorMessage:@"This application requires the Desktop Video drivers installed." title:@"Please install the Blackmagic Desktop Video drivers to use the features of this application."];
	}

}

- (void)enableInterface:(BOOL)enable
{
	// Set the enable state of user interface elements
	[deviceListPopup setEnabled:enable];
	[outputSignalPopup setEnabled:enable];
	[audioChannelPopup setEnabled:enable];
	[audioSampleDepthPopup setEnabled:enable];
	[videoFormatPopup setEnabled:enable];
	[pixelFormatPopup setEnabled:enable];
}

- (IBAction)toggleStart:(id)sender
{
	if (running == NO)
		[self startRunning];
	else
		[self stopRunning];
}

- (IBAction)pixelFormatChange:(id)sender
{
	[self refreshDisplayModeMenu];
}

- (void)startRunning
{
	IDeckLinkOutput*		deckLinkOutput;
	IDeckLinkDisplayMode*	videoDisplayMode = NULL;
	BMDVideoOutputFlags		videoOutputFlags;
	NSMenuItem*				videoDisplayItem;

	videoOutputFlags = bmdVideoOutputFlagDefault;

	// If the mode title contains "3D" then enable the video in 3D mode
	videoDisplayItem = [videoFormatPopup selectedItem];
	if ([[videoDisplayItem title] hasSuffix:(NSString*)CFSTR("3D")])
		videoOutputFlags |= bmdVideoOutputDualStream3D;

	// Determine the audio and video properties for the output stream
	outputSignal = (OutputSignal)[outputSignalPopup indexOfSelectedItem];
	audioChannelCount = [[audioChannelPopup selectedItem] tag];
	audioSampleDepth = [[audioSampleDepthPopup selectedItem] tag];
	audioSampleRate = bmdAudioSampleRate48kHz;

	// - Extract the IDeckLinkDisplayMode from the display mode popup menu (stashed in the item's tag)
	videoDisplayMode = (IDeckLinkDisplayMode*)[[videoFormatPopup selectedItem] tag];
	frameWidth = videoDisplayMode->GetWidth();
	frameHeight = videoDisplayMode->GetHeight();

	videoDisplayMode->GetFrameRate(&frameDuration, &frameTimescale);
	// Calculate the number of frames per second, rounded up to the nearest integer.  For example, for NTSC (29.97 FPS), framesPerSecond == 30.
	framesPerSecond = (frameTimescale + (frameDuration-1))  /  frameDuration;

	// Set the video output mode
	deckLinkOutput = selectedDevice->getDeviceOutput();
	if (deckLinkOutput->EnableVideoOutput(videoDisplayMode->GetDisplayMode(), videoOutputFlags) != S_OK)
		goto bail;
	
	// Set the audio output mode
	if (deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, audioSampleDepth, audioChannelCount, bmdAudioOutputStreamTimestamped) != S_OK)
		goto bail;

	// Generate one second of audio tone
	audioSamplesPerFrame = ((audioSampleRate * frameDuration) / frameTimescale);
	audioBufferSampleLength = (framesPerSecond * audioSampleRate * frameDuration) / frameTimescale;
	audioBuffer = malloc(audioBufferSampleLength * audioChannelCount * (audioSampleDepth / 8));
	if (audioBuffer == NULL)
		goto bail;
	FillSine(audioBuffer, audioBufferSampleLength, audioChannelCount, audioSampleDepth);

	videoFrameBlack = [self CreateBlackFrame];
	if (! videoFrameBlack)
		goto bail;

	videoFrameBars = [self CreateBarsFrame];
	if (! videoFrameBars)
		goto bail;

	// Begin video preroll by scheduling a second of frames in hardware
	totalFramesScheduled = 0;
	for (int i = 0; i < framesPerSecond; i++)
		[self scheduleNextFrame:YES];
	
	// Begin audio preroll.  This will begin calling our audio callback, which will start the DeckLink output stream.
	totalAudioSecondsScheduled = 0;
	if (deckLinkOutput->BeginAudioPreroll() != S_OK)
		goto bail;

	// Success; update the UI
	running = YES;
	[startButton setTitle:@"Stop"];
	// Disable the user interface while running (prevent the user from making changes to the output signal)
	[self enableInterface:NO];
	
	return;
	
bail:
	// *** Error-handling code.  Cleanup any resources that were allocated. *** //
	[self stopRunning];
}

- (void)stopRunning
{
	IDeckLinkOutput* deckLinkOutput = selectedDevice->getDeviceOutput();

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

	if (videoFrameBlack != NULL)
		videoFrameBlack->Release();
	videoFrameBlack = NULL;

	if (videoFrameBars != NULL)
		videoFrameBars->Release();
	videoFrameBars = NULL;

	if (audioBuffer != NULL)
		free(audioBuffer);
	audioBuffer = NULL;

	// Success; update the UI
	running = NO;
	[startButton setTitle:@"Start"];
	// Re-enable the user interface when stopped
	[self enableInterface:YES];
}

- (void)scheduleNextFrame:(BOOL)prerolling
{
	IDeckLinkOutput* deckLinkOutput = selectedDevice->getDeviceOutput();

	if (prerolling == NO)
	{
		// If not prerolling, make sure that playback is still active
		if (running == NO)
			return;
	}
	
	if (outputSignal == kOutputSignalPip)
	{
		if ((totalFramesScheduled % framesPerSecond) == 0)
		{
			// On each second, schedule a frame of bars
			if (deckLinkOutput->ScheduleVideoFrame(videoFrameBars, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of black
			if (deckLinkOutput->ScheduleVideoFrame(videoFrameBlack, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
				return;
		}
	}
	else
	{
		if ((totalFramesScheduled % framesPerSecond) == 0)
		{
			// On each second, schedule a frame of black
			if (deckLinkOutput->ScheduleVideoFrame(videoFrameBlack, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of color bars
			if (deckLinkOutput->ScheduleVideoFrame(videoFrameBars, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
				return;
		}
	}
		
	totalFramesScheduled += 1;
}

- (void)writeNextAudioSamples
{
	IDeckLinkOutput* deckLinkOutput = selectedDevice->getDeviceOutput();

	// Write one second of audio to the DeckLink API.
	if (outputSignal == kOutputSignalPip)
	{
		// Schedule one-frame of audio tone
		if (deckLinkOutput->ScheduleAudioSamples(audioBuffer, audioSamplesPerFrame, (totalAudioSecondsScheduled * audioBufferSampleLength), audioSampleRate, NULL) != S_OK)
			return;
	}
	else
	{
		// Schedule one-second (minus one frame) of audio tone
		if (deckLinkOutput->ScheduleAudioSamples(audioBuffer, (audioBufferSampleLength - audioSamplesPerFrame), (totalAudioSecondsScheduled * audioBufferSampleLength) + audioSamplesPerFrame, audioSampleRate, NULL) != S_OK)
			return;
	}
	
	totalAudioSecondsScheduled += 1;
}

- (void)applicationWillTerminate:(NSNotification *)notification
{
	// Stop the output signal
	[self stopRunning];

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

	// Release all DeckLinkDevice instances
	while([deviceListPopup numberOfItems] > 0)
	{
		PlaybackDelegate* device = (PlaybackDelegate*)[[deviceListPopup itemAtIndex:0] tag];
		device->Release();
		[deviceListPopup removeItemAtIndex:0];
	}

	// Release all DisplayMode instances
	while([videoFormatPopup numberOfItems] > 0)
	{
		IDeckLinkDisplayMode* displayMode = (IDeckLinkDisplayMode*)[[videoFormatPopup itemAtIndex:0] tag];
		displayMode->Release();
		[videoFormatPopup removeItemAtIndex:0];
	}
	
	// Release DeckLink discovery instance
	if (deckLinkDiscovery != NULL)
	{
		deckLinkDiscovery->Release();
		deckLinkDiscovery = NULL;
	}
}

@end


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

PlaybackDelegate::PlaybackDelegate (SyncController* owner, IDeckLink* deckLink)
	: m_controller(owner), m_deckLink(deckLink)
{
}

PlaybackDelegate::~PlaybackDelegate()
{
	if (m_deckLinkOutput)
	{
		m_deckLinkOutput->Release();
		m_deckLinkOutput = NULL;
	}
	
	if (m_deckLink)
	{
		m_deckLink->Release();
		m_deckLink = NULL;
	}
	
	if (m_deviceName)
		CFRelease(m_deviceName);
}

bool PlaybackDelegate::init()
{
	// Get output interface
	if (m_deckLink->QueryInterface(IID_IDeckLinkOutput, (void**) &m_deckLinkOutput) != S_OK)
		return false;

	// Get device name
	if (m_deckLink->GetDisplayName(&m_deviceName) != S_OK)
		m_deviceName = CFStringCreateCopy(NULL, CFSTR("DeckLink"));

	// Provide the delegate to the audio and video output interfaces
	m_deckLinkOutput->SetScheduledFrameCompletionCallback(this);
	m_deckLinkOutput->SetAudioCallback(this);

	return true;
}

HRESULT	PlaybackDelegate::QueryInterface(REFIID iid, LPVOID *ppv)
{
	CFUUIDBytes		iunknown;
	HRESULT			result = E_NOINTERFACE;

	// Initialise the return result
	*ppv = NULL;

	// Obtain the IUnknown interface and compare it the provided REFIID
	iunknown = CFUUIDGetUUIDBytes(IUnknownUUID);
	if (memcmp(&iid, &iunknown, sizeof(REFIID)) == 0)
	{
		*ppv = this;
		AddRef();
		result = S_OK;
	}
	else if (memcmp(&iid, &IID_IDeckLinkNotificationCallback, sizeof(REFIID)) == 0)
	{
		*ppv = (IDeckLinkNotificationCallback*)this;
		AddRef();
		result = S_OK;
	}

	return result;
}

ULONG	PlaybackDelegate::AddRef(void)
{
	return OSAtomicIncrement32(&m_refCount);
}

ULONG	PlaybackDelegate::Release(void)
{
	ULONG newRefValue = OSAtomicDecrement32(&m_refCount);
	if (newRefValue == 0)
		delete this;
	return newRefValue;
}

HRESULT		PlaybackDelegate::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
	// When a video frame has been 
	[m_controller scheduleNextFrame:NO];
	return S_OK;
}

HRESULT		PlaybackDelegate::ScheduledPlaybackHasStopped ()
{
	return S_OK;
}

HRESULT		PlaybackDelegate::RenderAudioSamples (bool preroll)
{
	// Provide further audio samples to the DeckLink API until our preferred buffer waterlevel is reached
	[m_controller writeNextAudioSamples];
	
	if (preroll)
	{
		// Start audio and video output
		m_deckLinkOutput->StartScheduledPlayback(0, 100, 1.0);
	}
	
	return S_OK;
}

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

DeckLinkDeviceDiscovery::DeckLinkDeviceDiscovery(SyncController* delegate)
: m_uiDelegate(delegate), m_deckLinkDiscovery(NULL), m_refCount(1)
{
	m_deckLinkDiscovery = CreateDeckLinkDiscoveryInstance();
}


DeckLinkDeviceDiscovery::~DeckLinkDeviceDiscovery()
{
	if (m_deckLinkDiscovery != NULL)
	{
		// Uninstall device arrival notifications and release discovery object
		m_deckLinkDiscovery->UninstallDeviceNotifications();
		m_deckLinkDiscovery->Release();
		m_deckLinkDiscovery = NULL;
	}
}

bool		DeckLinkDeviceDiscovery::enable()
{
	HRESULT		result = E_FAIL;

	// Install device arrival notifications
	if (m_deckLinkDiscovery != NULL)
		result = m_deckLinkDiscovery->InstallDeviceNotifications(this);

	return result == S_OK;
}

void		DeckLinkDeviceDiscovery::disable()
{
	// Uninstall device arrival notifications
	if (m_deckLinkDiscovery != NULL)
		m_deckLinkDiscovery->UninstallDeviceNotifications();
}

HRESULT		DeckLinkDeviceDiscovery::DeckLinkDeviceArrived (/* in */ IDeckLink* deckLink)
{
	// Update UI (add new device to menu) from main thread
	// AddRef the IDeckLink instance before handing it off to the main thread
	deckLink->AddRef();
	dispatch_async(dispatch_get_main_queue(), ^{
		[m_uiDelegate addDevice:deckLink];
	});

	return S_OK;
}

HRESULT		DeckLinkDeviceDiscovery::DeckLinkDeviceRemoved (/* in */ IDeckLink* deckLink)
{
	dispatch_async(dispatch_get_main_queue(), ^{
		[m_uiDelegate removeDevice:deckLink];
	});
	return S_OK;
}

HRESULT		DeckLinkDeviceDiscovery::QueryInterface (REFIID iid, LPVOID *ppv)
{
	CFUUIDBytes		iunknown;
	HRESULT			result = E_NOINTERFACE;

	// Initialise the return result
	*ppv = NULL;

	// Obtain the IUnknown interface and compare it the provided REFIID
	iunknown = CFUUIDGetUUIDBytes(IUnknownUUID);
	if (memcmp(&iid, &iunknown, sizeof(REFIID)) == 0)
	{
		*ppv = this;
		AddRef();
		result = S_OK;
	}
	else if (memcmp(&iid, &IID_IDeckLinkDeviceNotificationCallback, sizeof(REFIID)) == 0)
	{
		*ppv = (IDeckLinkDeviceNotificationCallback*)this;
		AddRef();
		result = S_OK;
	}

	return result;
}

ULONG		DeckLinkDeviceDiscovery::AddRef (void)
{
	return OSAtomicIncrement32(&m_refCount);
}

ULONG		DeckLinkDeviceDiscovery::Release (void)
{
	int32_t		newRefValue;

	newRefValue = OSAtomicDecrement32(&m_refCount);
	if (newRefValue == 0)
	{
		delete this;
		return 0;
	}

	return newRefValue;
}

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


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

void	FillColourBars (IDeckLinkVideoFrame* theFrame, bool reversed)
{
	uint32_t*		nextWord;
	uint32_t		width;
	uint32_t		height;
	uint32_t*		bars;
	uint8_t			numBars;

	theFrame->GetBytes((void**)&nextWord);
	width = theFrame->GetWidth();
	height = theFrame->GetHeight();
	
	if (width > 720)
	{
		bars = gHD75pcColourBars;
		numBars = ARRAY_SIZE(gHD75pcColourBars);
	}
	else
	{
		bars = gSD75pcColourBars;
		numBars = ARRAY_SIZE(gSD75pcColourBars);
	}

	for (uint32_t y = 0; y < height; y++)
	{
		for (uint32_t x = 0; x < width; x+=2)
		{
			int pos = x * numBars / width;
			
			if (reversed)
				pos = numBars - pos - 1;

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

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

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;
}
