diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0113939d2..d3d0530d3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -20,7 +20,9 @@ static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; Machine::Machine() : _interruptControl(0), _frameCycles(0), - _outputPosition(0), + _displayOutputPosition(0), + _audioOutputPosition(0), + _audioOutputPositionError(0), _currentOutputLine(0) { memset(_keyStates, 0, sizeof(_keyStates)); @@ -29,6 +31,8 @@ Machine::Machine() : _interruptStatus = 0x02; for(int c = 0; c < 16; c++) memset(_roms[c], 0xff, 16384); + + _speaker.set_input_rate(62500); setup6502(); } @@ -132,21 +136,41 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } break; case 0x6: - printf("Counter\n"); + if(!isReadOperation(operation)) + { + if(_speaker.is_enabled) + update_audio(); + _speaker.divider = *value; + } break; case 0x7: if(!isReadOperation(operation)) { - _screenMode = ((*value) >> 3)&7; - if(_screenMode == 7) _screenMode = 4; - switch(_screenMode) + // update screen mode + uint8_t new_screen_mode = ((*value) >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != _screenMode) { - case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; - case 3: _screenModeBaseAddress = 0x4000; break; - case 4: case 5: _screenModeBaseAddress = 0x5800; break; - case 6: _screenModeBaseAddress = 0x6000; break; + update_display(); + _screenMode = new_screen_mode; + switch(_screenMode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } } - printf("Misc. control\n"); + + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != _speaker.is_enabled) + { + update_audio(); + _speaker.is_enabled = new_speaker_is_enabled; + } + + // TODO: tape mode, tape motor, caps lock LED } break; default: @@ -222,15 +246,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // } _frameCycles += cycles; - if(_frameCycles == cycles_per_frame) + switch(_frameCycles) { - update_display(); - _frameCycles = 0; - _outputPosition = 0; - _currentOutputLine = 0; + case 64*128: + update_audio(); + break; + + case 128*128: + update_audio(); + signal_interrupt(InterruptRealTimeClock); + break; + + case 196*128: + update_audio(); + break; + + case 284*128: + update_audio(); + signal_interrupt(InterruptDisplayEnd); + break; + + case cycles_per_frame: + update_display(); + update_audio(); + _frameCycles = 0; + _displayOutputPosition = 0; + _audioOutputPosition = 0; + _currentOutputLine = 0; + break; + } - if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock); - if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd); return cycles; } @@ -266,6 +311,14 @@ inline void Machine::evaluate_interrupts() set_irq_line(_interruptStatus & 1); } +inline void Machine::update_audio() +{ + int difference = _frameCycles - _audioOutputPosition; + _audioOutputPosition = _frameCycles; + _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5); + _audioOutputPositionError = (_audioOutputPositionError + difference)&31; +} + inline void Machine::update_display() { const int lines_of_hsync = 3; @@ -275,26 +328,26 @@ inline void Machine::update_display() if(_frameCycles >= end_of_hsync) { // assert sync for the first three lines of the display, with a break at the end for horizontal alignment - if(_outputPosition < end_of_hsync) + if(_displayOutputPosition < end_of_hsync) { for(int c = 0; c < lines_of_hsync; c++) { _crt->output_sync(119 * crt_cycles_multiplier); _crt->output_blank(9 * crt_cycles_multiplier); } - _outputPosition = end_of_hsync; + _displayOutputPosition = end_of_hsync; } - while(_outputPosition >= end_of_hsync && _outputPosition < _frameCycles) + while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _frameCycles) { - const int current_line = _outputPosition >> 7; - const int line_position = _outputPosition & 127; + const int current_line = _displayOutputPosition >> 7; + const int line_position = _displayOutputPosition & 127; // all lines then start with 9 cycles of sync if(!line_position) { _crt->output_sync(9 * crt_cycles_multiplier); - _outputPosition += 9; + _displayOutputPosition += 9; } else { @@ -308,7 +361,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(119 * crt_cycles_multiplier); - _outputPosition += 119; + _displayOutputPosition += 119; } } else @@ -317,7 +370,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(15 * crt_cycles_multiplier); - _outputPosition += 15; + _displayOutputPosition += 15; _crt->allocate_write_area(80 * crt_cycles_multiplier); _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); @@ -392,7 +445,7 @@ inline void Machine::update_display() break; } } - _outputPosition++; + _displayOutputPosition++; } if(line_position == 104) @@ -408,7 +461,7 @@ inline void Machine::update_display() _currentLine = nullptr; _crt->output_data(80 * crt_cycles_multiplier); _crt->output_blank(24 * crt_cycles_multiplier); - _outputPosition += 24; + _displayOutputPosition += 24; } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 7c3db47ce..cbbd83c3e 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -70,6 +70,7 @@ class Machine: public CPU6502::Processor { void set_key_state(Key key, bool isPressed); Outputs::CRT *get_crt() { return _crt; } + Outputs::Speaker *get_speaker() { return &_speaker; } const char *get_signal_decoder(); private: @@ -84,17 +85,22 @@ class Machine: public CPU6502::Processor { Outputs::CRT *_crt; - int _frameCycles, _outputPosition; + int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; + uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; int _currentOutputLine; uint8_t *_currentLine; inline void update_display(); + inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); - class Speaker: public ::Speaker::Filter { - }; + class Speaker: public ::Outputs::Filter { + public: + uint8_t divider; + bool is_enabled; + } _speaker; }; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index f8b260442..3f0efb63c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -35,6 +35,11 @@ _electron.get_crt()->set_delegate(delegate); } +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate { + _electron.get_speaker()->set_output_rate(44100, 512); + _electron.get_speaker()->set_output_quality(15); +} + - (void)setView:(CSCathodeRayView *)view { [super setView:view]; [view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB]; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 6ca467be8..61c3460f6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -7,14 +7,17 @@ // #import "CSMachine.h" -#include "../../../../Outputs/CRT.hpp" +#include "CRT.hpp" +#include "Speaker.hpp" @interface CSMachine (Subclassing) +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate; - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate; -- (void)doRunForNumberOfCycles:(int)numberOfCycles; -- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; +- (void)doRunForNumberOfCycles:(int)numberOfCycles; - (void)perform:(dispatch_block_t)action; +- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index e5b98a87d..e3d656f9c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -53,6 +53,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { if (self) { _crtDelegate.machine = self; [self setCRTDelegate:&_crtDelegate]; + [self setSpeakerDelegate:nil]; _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; } @@ -61,6 +62,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { } - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {} +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate {} - (void)doRunForNumberOfCycles:(int)numberOfCycles {} @end diff --git a/Outputs/Speaker.cpp b/Outputs/Speaker.cpp index e393dbc76..bba4e6396 100644 --- a/Outputs/Speaker.cpp +++ b/Outputs/Speaker.cpp @@ -8,5 +8,5 @@ #include "Speaker.hpp" -using namespace Speaker; +using namespace Outputs; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 59eec9862..6a1b24928 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -10,17 +10,18 @@ #define Speaker_hpp #include +#include #include "../SignalProcessing/Stepper.hpp" -namespace Speaker { +namespace Outputs { -class Delegate { +class Speaker { public: - virtual void speaker_did_complete_samples(uint8_t *buffer); -}; + class Delegate { + public: + virtual void speaker_did_complete_samples(uint8_t *buffer); + }; -template class Filter { - public: void set_output_rate(int cycles_per_second, int buffer_size) { _output_cycles_per_second = cycles_per_second; @@ -50,18 +51,7 @@ template class Filter { set_needs_updated_filter_coefficients(); } - void run_for_cycles(int input_cycles) - { - if(_coefficients_are_dirty) update_filter_coefficients(); - - // point sample for now, as a temporary measure - while(input_cycles--) - { - static_cast(this)->perform_bus_operation(); - } - } - - private: + protected: uint16_t *_buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; @@ -76,14 +66,31 @@ template class Filter { { _coefficients_are_dirty = true; } +}; + +template class Filter: public Speaker { + public: + void run_for_cycles(int input_cycles) + { + if(_coefficients_are_dirty) update_filter_coefficients(); + + // point sample for now, as a temporary measure + while(input_cycles--) + { +// static_cast(this)->perform_bus_operation(); + } + } + + private: + SignalProcessing::Stepper *_stepper; void update_filter_coefficients() { _coefficients_are_dirty = false; _buffer_in_progress_pointer = 0; - delete[] _stepper; - _stepper = Stepper(_input_cycles_per_second, _output_cycles_per_second); + delete _stepper; + _stepper = new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second); } };