From 38ffcaa262d131c412f2077eecf1fd804bbec064 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 20:33:22 -0500 Subject: [PATCH] Here, at last, is _some_ audio output, at least. --- Machines/Electron/Electron.cpp | 6 +- Machines/Electron/Electron.hpp | 2 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 + .../ClockSignal-Bridging-Header.h | 1 + .../Documents/ElectronDocument.swift | 1 + .../Documents/MachineDocument.swift | 3 + .../Mac/Clock Signal/Wrappers/AudioQueue.h | 15 ++ .../Mac/Clock Signal/Wrappers/AudioQueue.m | 131 ++++++++++++++++++ .../Wrappers/CSMachine+Subclassing.h | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.h | 2 + .../Mac/Clock Signal/Wrappers/CSMachine.mm | 5 +- Outputs/Speaker.hpp | 6 +- 12 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0d45a9e3c..fc4dddc4a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -493,7 +493,7 @@ void Machine::set_key_state(Key key, bool isPressed) } } -void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target) +void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target) { if(!_is_enabled) { @@ -501,7 +501,7 @@ void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_sampl } else { - *target = ((start_time / _divider)&1) ? 255 : 0; + *target = ((start_time / (_divider+1))&1) ? 255 : 0; } } @@ -513,5 +513,5 @@ void Machine::Speaker::set_divider(uint8_t divider) void Machine::Speaker::set_is_enabled(bool is_enabled) { - _is_enabled = false; + _is_enabled = is_enabled; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index fb2a944a5..4efc1fa04 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -103,7 +103,7 @@ class Machine: public CPU6502::Processor { void set_is_enabled(bool is_enabled); inline bool get_is_enabled() { return _is_enabled; } - void get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target); + void get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target); private: uint8_t _divider; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cb526d640..dfb6aa1f9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; @@ -321,6 +322,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; + 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; 4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = ""; }; 4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = ""; }; @@ -738,6 +741,8 @@ 4B55CE521C3B7ABF0093A61B /* CSElectron.h */, 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */, 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */, + 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */, + 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */, ); path = Wrappers; sourceTree = ""; @@ -1547,6 +1552,7 @@ 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, + 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index d9434a171..1ad92dd77 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -6,3 +6,4 @@ #import "CSAtari2600.h" #import "CSElectron.h" #import "CSCathodeRayView.h" +#import "AudioQueue.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index cd1adc6cd..9c54ab8b1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -27,6 +27,7 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) electron.view = openGLView + electron.audioQueue = self.audioQueue openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 54bfd891f..428cc05a0 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -7,6 +7,7 @@ // import Cocoa +import AudioToolbox class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewResponderDelegate { @@ -17,6 +18,8 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes } } + lazy var audioQueue = AudioQueue() + override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h new file mode 100644 index 000000000..d72eed389 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h @@ -0,0 +1,15 @@ +// +// AudioQueue.h +// Clock Signal +// +// Created by Thomas Harte on 14/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +@interface AudioQueue : NSObject + +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m new file mode 100644 index 000000000..764e4fecf --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -0,0 +1,131 @@ +// +// AudioQueue.m +// Clock Signal +// +// Created by Thomas Harte on 14/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "AudioQueue.h" +@import AudioToolbox; + +#define AudioQueueNumAudioBuffers 3 +#define AudioQueueStreamLength 2048 +#define AudioQueueBufferLength 512 + +@implementation AudioQueue +{ + AudioQueueRef _audioQueue; + AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers]; + unsigned int _audioStreamReadPosition, _audioStreamWritePosition, _queuedAudioStreamSegments; + int16_t _audioStream[AudioQueueStreamLength]; + BOOL _isOutputtingAudio; +} + + +#pragma mark - +#pragma mark AudioQueue callbacks and setup; for pushing audio out + +- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer +{ + @synchronized(self) + { + if(_queuedAudioStreamSegments > AudioQueueNumAudioBuffers-1) _isOutputtingAudio = YES; + + if(_isOutputtingAudio && _queuedAudioStreamSegments) + { + _queuedAudioStreamSegments--; + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition], buffer->mAudioDataByteSize); + _audioStreamReadPosition = (_audioStreamReadPosition + AudioQueueBufferLength)%AudioQueueStreamLength; + } + else + { + memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + _isOutputtingAudio = NO; + } + AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); + } +} + +static void audioOutputCallback( + void *inUserData, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer) +{ + [(__bridge AudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; +} + +- (instancetype)init +{ + self = [super init]; + + if(self) + { + /* + + Describe a mono, 16bit, 44.1Khz audio format + + */ + AudioStreamBasicDescription outputDescription; + + outputDescription.mSampleRate = 44100; + + outputDescription.mFormatID = kAudioFormatLinearPCM; + outputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + outputDescription.mBytesPerPacket = 2; + outputDescription.mFramesPerPacket = 1; + outputDescription.mBytesPerFrame = 2; + outputDescription.mChannelsPerFrame = 1; + outputDescription.mBitsPerChannel = 16; + + outputDescription.mReserved = 0; + + // create an audio output queue along those lines + if(!AudioQueueNewOutput( + &outputDescription, + audioOutputCallback, + (__bridge void *)(self), + NULL, + kCFRunLoopCommonModes, + 0, + &_audioQueue)) + { + _audioStreamWritePosition = AudioQueueBufferLength; + UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t); + + int c = AudioQueueNumAudioBuffers; + while(c--) + { + AudioQueueAllocateBuffer(_audioQueue, bufferBytes, &_audioBuffers[c]); + memset(_audioBuffers[c]->mAudioData, 0, bufferBytes); + _audioBuffers[c]->mAudioDataByteSize = bufferBytes; + AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[c], 0, NULL); + } + + AudioQueueStart(_audioQueue, NULL); + } + } + + return self; +} + +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples +{ + @synchronized(self) + { + memcpy(&_audioStream[_audioStreamWritePosition], buffer, lengthInSamples * sizeof(int16_t)); + _audioStreamWritePosition = (_audioStreamWritePosition + lengthInSamples)%AudioQueueStreamLength; + + if(_queuedAudioStreamSegments == (AudioQueueStreamLength/AudioQueueBufferLength)) + { + _audioStreamReadPosition = (_audioStreamReadPosition + lengthInSamples)%AudioQueueStreamLength; + } + else + { + _queuedAudioStreamSegments++; + } + } +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 64c00b6fa..255af2ae1 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -19,6 +19,6 @@ - (void)perform:(dispatch_block_t)action; - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length; +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index dbce321a3..9ce936fe2 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -8,11 +8,13 @@ #import #import "CSCathodeRayView.h" +#import "AudioQueue.h" @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; @property (nonatomic, weak) CSCathodeRayView *view; +@property (nonatomic, weak) AudioQueue *audioQueue; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index f6ce615dc..0711abeec 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -18,7 +18,7 @@ struct CRTDelegate: public Outputs::CRT::Delegate { struct SpeakerDelegate: public Outputs::Speaker::Delegate { __weak CSMachine *machine; - void speaker_did_complete_samples(Outputs::Speaker *speaker, const uint16_t *buffer, int buffer_size) { + void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) { [machine speaker:speaker didCompleteSamples:buffer length:buffer_size]; } }; @@ -44,7 +44,8 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { if([self.view pushFrame:frame]) crt->return_frame(); } -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length { +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { + [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } - (void)runForNumberOfCycles:(int)cycles { diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index a0085c755..4e22a8c1e 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -19,7 +19,7 @@ class Speaker { public: class Delegate { public: - virtual void speaker_did_complete_samples(Speaker *speaker, const uint16_t *buffer, int buffer_size) = 0; + virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; }; void set_output_rate(int cycles_per_second, int buffer_size) @@ -28,7 +28,7 @@ class Speaker { if(_buffer_size != buffer_size) { delete[] _buffer_in_progress; - _buffer_in_progress = new uint16_t[buffer_size]; + _buffer_in_progress = new int16_t[buffer_size]; _buffer_size = buffer_size; } set_needs_updated_filter_coefficients(); @@ -52,7 +52,7 @@ class Speaker { } protected: - uint16_t *_buffer_in_progress; + int16_t *_buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; int _number_of_taps;