From 8623dc28337b00e13a92a8b79e460b14a8b6af93 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 1 Jun 2016 19:04:07 -0400
Subject: [PATCH] Consolidated a little more within the common code, adding
 sampling rate selection based on querying the machine.

---
 .../Documents/Atari2600Document.swift         |  5 --
 .../Documents/ElectronDocument.swift          |  9 ++--
 .../Documents/MachineDocument.swift           | 20 ++++++--
 .../Mac/Clock Signal/Wrappers/AudioQueue.h    |  5 ++
 .../Mac/Clock Signal/Wrappers/AudioQueue.m    | 48 +++++++++++++++----
 .../Mac/Clock Signal/Wrappers/CSMachine.h     |  3 ++
 .../Mac/Clock Signal/Wrappers/CSMachine.mm    | 20 +++++++-
 7 files changed, 85 insertions(+), 25 deletions(-)

diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift
index 172a9a1f2..e13fff8fa 100644
--- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift	
+++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift	
@@ -21,11 +21,6 @@ class Atari2600Document: MachineDocument {
 		self.intendedCyclesPerSecond = 1194720
 	}
 
-	override func windowControllerDidLoadNib(aController: NSWindowController) {
-		super.windowControllerDidLoadNib(aController)
-		atari2600.setView(openGLView, aspectRatio: 4.0 / 3.0)
-	}
-
 	override class func autosavesInPlace() -> Bool {
 		return true
 	}
diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift
index 1af7427ca..9f73af395 100644
--- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift	
+++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift	
@@ -16,9 +16,12 @@ class ElectronDocument: MachineDocument {
 		return electron
 	}
 
+	override func aspectRatio() -> NSSize {
+		return NSSize(width: 11.0, height: 10.0)
+	}
+
 	override func windowControllerDidLoadNib(aController: NSWindowController) {
 		super.windowControllerDidLoadNib(aController)
-		aController.window?.contentAspectRatio = NSSize(width: 11.0, height: 10.0)
 
 		self.intendedCyclesPerSecond = 2000000
 
@@ -29,10 +32,6 @@ class ElectronDocument: MachineDocument {
 			self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!)
 		}
 
-		openGLView.performWithGLContext({
-			self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0)
-		})
-
 		establishStoredOptions()
 	}
 
diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift
index 40da4bdd1..491941257 100644
--- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
+++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
@@ -17,6 +17,10 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
 		return nil
 	}
 
+	func aspectRatio() -> NSSize {
+		return NSSize(width: 4.0, height: 3.0)
+	}
+
 	@IBOutlet weak var openGLView: CSOpenGLView! {
 		didSet {
 			openGLView.delegate = self
@@ -29,16 +33,24 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
 		optionsPanel?.setIsVisible(true)
 	}
 
-	lazy var audioQueue = AudioQueue()
+	var audioQueue : AudioQueue! = nil
 
 	override func windowControllerDidLoadNib(aController: NSWindowController) {
 		super.windowControllerDidLoadNib(aController)
 
-		// bind the content aspect ratio to remain 4:3 from now on as a default
-		aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0)
+		// establish the output aspect ratio and audio
+		let displayAspectRatio = self.aspectRatio()
+		aController.window?.contentAspectRatio = displayAspectRatio
+		openGLView.performWithGLContext({
+			self.machine().setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
+		})
 
-		// provide the audio queue
+		// establish and provide the audio queue, taking advice as to an appropriate sampling rate
+		let maximumSamplingRate = AudioQueue.preferredSamplingRate()
+		let selectedSamplingRate = self.machine().idealSamplingRateFromRange(NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
+		audioQueue = AudioQueue(samplingRate: Float64(selectedSamplingRate))
 		self.machine().audioQueue = self.audioQueue
+		self.machine().setAudioSamplingRate(selectedSamplingRate)
 	}
 
 	override func close() {
diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h
index cf96e1166..de14ea68b 100644
--- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h	
+++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h	
@@ -10,6 +10,11 @@
 
 @interface AudioQueue : NSObject
 
+- (instancetype)initWithSamplingRate:(Float64)samplingRate;
 - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples;
 
+@property (nonatomic, readonly) Float64 samplingRate;
+
++ (Float64)preferredSamplingRate;
+
 @end
diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m
index 80f14d7e0..9763cd1c6 100644
--- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m	
+++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m	
@@ -85,20 +85,21 @@ static void audioOutputCallback(
 	[(__bridge AudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
 }
 
-- (instancetype)init
+- (instancetype)initWithSamplingRate:(Float64)samplingRate
 {
 	self = [super init];
 
 	if(self)
 	{
 		_writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed];
+		_samplingRate = samplingRate;
 
 		/*
 			Describe a mono, 16bit, 44.1Khz audio format
 		*/
 		AudioStreamBasicDescription outputDescription;
 
-		outputDescription.mSampleRate = 44100;
+		outputDescription.mSampleRate = samplingRate;
 
 		outputDescription.mFormatID = kAudioFormatLinearPCM;
 		outputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
@@ -113,13 +114,13 @@ static void audioOutputCallback(
 
 		// create an audio output queue along those lines
 		if(!AudioQueueNewOutput(
-			&outputDescription,
-			audioOutputCallback,
-			(__bridge void *)(self),
-			NULL,
-			kCFRunLoopCommonModes,
-			0,
-			&_audioQueue))
+				&outputDescription,
+				audioOutputCallback,
+				(__bridge void *)(self),
+				NULL,
+				kCFRunLoopCommonModes,
+				0,
+				&_audioQueue))
 		{
 			UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t);
 
@@ -139,6 +140,11 @@ static void audioOutputCallback(
 	return self;
 }
 
+- (instancetype)init
+{
+	return [self initWithSamplingRate:[[self class] preferredSamplingRate]];
+}
+
 - (void)dealloc
 {
 	[_writeLock lock];
@@ -190,4 +196,28 @@ static void audioOutputCallback(
 	return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanProceed : AudioQueueWait;
 }
 
++ (AudioDeviceID)defaultOutputDevice
+{
+	AudioObjectPropertyAddress address;
+	address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+	address.mScope = kAudioObjectPropertyScopeGlobal;
+	address.mElement = kAudioObjectPropertyElementMaster;
+
+	AudioDeviceID deviceID;
+	UInt32 size = sizeof(AudioDeviceID);
+	return AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &deviceID) ? 0 : deviceID;
+}
+
++ (Float64)preferredSamplingRate
+{
+	AudioObjectPropertyAddress address;
+	address.mSelector = kAudioDevicePropertyNominalSampleRate;
+	address.mScope = kAudioObjectPropertyScopeGlobal;
+	address.mElement = kAudioObjectPropertyElementMaster;
+
+	Float64 samplingRate;
+	UInt32 size = sizeof(Float64);
+	return AudioHardwareServiceGetPropertyData([self defaultOutputDevice], &address, 0, NULL, &size, &samplingRate) ? 0.0 : samplingRate;
+}
+
 @end
diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h
index 1791b3780..c91487b7f 100644
--- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h	
+++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h	
@@ -14,6 +14,9 @@
 
 - (void)runForNumberOfCycles:(int)numberOfCycles;
 
+- (int)idealSamplingRateFromRange:(NSRange)range;
+- (void)setAudioSamplingRate:(int)samplingRate;
+
 - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
 - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
 
diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm
index 10b5b67d0..f17c7e31a 100644
--- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm	
+++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm	
@@ -34,8 +34,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate {
 
 	if(self) {
 		_serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL);
-		_speakerDelegate.machine = self;
-		[self setSpeakerDelegate:&_speakerDelegate sampleRate:44100];
 	}
 
 	return self;
@@ -49,6 +47,24 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate {
 	}];
 }
 
+- (int)idealSamplingRateFromRange:(NSRange)range {
+	@synchronized(self) {
+		Outputs::Speaker *speaker = self.machine->get_speaker();
+		if(speaker)
+		{
+			return speaker->get_ideal_clock_rate_in_range((int)range.location, (int)(range.location + range.length));
+		}
+		return (int)range.location;
+	}
+}
+
+- (void)setAudioSamplingRate:(int)samplingRate {
+	@synchronized(self) {
+		_speakerDelegate.machine = self;
+		[self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate];
+	}
+}
+
 - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate {
 	@synchronized(self) {
 		Outputs::Speaker *speaker = self.machine->get_speaker();