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();