From 5c4f35e13ff5f67acafa42b4432e12bf287a77a6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 May 2016 21:23:44 -0400 Subject: [PATCH] Finally started on generalising the C++ stuff so as to be able to be able to get a working audio binding on the OS-specific side without further repetition by factoring an appropriate protocol out from the Electron and sketching out the correct speaker class for the Atari. Added a method to ask it what a good output frequency would be. --- Machines/Atari2600/Atari2600.cpp | 19 +++++++-- Machines/Atari2600/Atari2600.hpp | 40 +++++++++++++++---- Machines/CRTMachine.hpp | 30 ++++++++++++++ Machines/Electron/Electron.cpp | 2 +- Machines/Electron/Electron.hpp | 33 ++++++++------- .../Clock Signal.xcodeproj/project.pbxproj | 2 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 1 - Outputs/Speaker.hpp | 12 ++++++ Processors/6502/CPU6502.hpp | 29 ++++++++------ 9 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 Machines/CRTMachine.hpp diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 176951f5c..7cf96d27e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -71,6 +71,8 @@ void Machine::setup_output(float aspect_ratio) "return (float(y) / 14.0) * (1.0 - amplitude) + step(1, iPhase) * amplitude * cos(phase + phaseOffset);" "}"); _crt->set_output_device(Outputs::CRT::Television); + + _speaker.set_input_rate(2 * 263 * 60); } void Machine::switch_region() @@ -89,6 +91,8 @@ void Machine::switch_region() "return (float(y) / 14.0) * (1.0 - amplitude) + step(4, (iPhase + 2u) & 15u) * amplitude * cos(phase + phaseOffset);" "}"); _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); + + _speaker.set_input_rate(2 * 312 * 50); } void Machine::close_output() @@ -445,6 +449,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin returnValue &= _ram[address&0x7f]; } else { _ram[address&0x7f] = *value; +// if((address&0x7f) == (0x9a&0x7f)) +// { +// printf("[9a] <- %02x\n", *value); +// } } } @@ -585,7 +593,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _playerGraphics[1][index^1] = _playerGraphics[0][index^1]; } break; case 0x1d: - case 0x1e: _missileGraphicsEnable[decodedAddress - 0x1d] = ((*value) >> 1)&1; break; + case 0x1e: + _missileGraphicsEnable[decodedAddress - 0x1d] = ((*value) >> 1)&1; +// printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-'); + break; case 0x1f: _ballGraphicsEnable[0] = ((*value) >> 1)&1; break; @@ -623,15 +634,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _objectCounter[_objectCounterPointer][index + 2].count = (_objectCounter[_objectCounterPointer][index + 2].count + extra_offset)%160; } _missileGraphicsReset[index] = !!((*value) & 0x02); +// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-'); } break; - case 0x2a: + case 0x2a: { // justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete"; // which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be // in five cycles from now +// int start_pause = ((_horizontalTimer + 3)&3) + 4; _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; - break; + } break; case 0x2b: _objectMotion[0] = _objectMotion[1] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index a64f32556..1596692c4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -10,31 +10,52 @@ #define Atari2600_cpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT/CRT.hpp" +#include "../CRTMachine.hpp" #include #include "Atari2600Inputs.h" namespace Atari2600 { -const unsigned int number_of_upcoming_events = 6; +const unsigned int number_of_upcoming_events = 16; const unsigned int number_of_recorded_counters = 7; -class Machine: public CPU6502::Processor { +class Speaker: public ::Outputs::Filter { + public: + void set_volume(int channel, uint8_t volume); + void set_divider(int channel, uint8_t divider); + void set_control(int channel, uint8_t control); + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + private: + uint8_t _volume[2]; + uint8_t _divider[2]; + uint8_t _control[2]; + int _shift_counters[2]; +}; + +class Machine: public CPU6502::Processor, CRTMachine::Machine { public: Machine(); ~Machine(); - unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void set_rom(size_t length, const uint8_t *data); void switch_region(); void set_digital_input(Atari2600DigitalInput input, bool state); - Outputs::CRT::CRT *get_crt() { return _crt; } - void setup_output(float aspect_ratio); - void close_output(); + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise() {} + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual Outputs::CRT::CRT *get_crt() { return _crt; } + virtual Outputs::Speaker *get_speaker() { return &_speaker; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } private: uint8_t *_rom, *_romPages[4], _ram[128]; @@ -137,7 +158,10 @@ class Machine: public CPU6502::Processor { void output_pixels(unsigned int count); uint8_t get_output_pixel(); void update_timers(int mask); + + // Outputs Outputs::CRT::CRT *_crt; + Speaker _speaker; // latched output state unsigned int _lastOutputStateDuration; diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp new file mode 100644 index 000000000..235e1e10b --- /dev/null +++ b/Machines/CRTMachine.hpp @@ -0,0 +1,30 @@ +// +// CRTMachine.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTMachine_hpp +#define CRTMachine_hpp + +#include "../Outputs/CRT/CRT.hpp" +#include "../Outputs/Speaker.hpp" + +namespace CRTMachine { + +class Machine { + public: + virtual void setup_output(float aspect_ratio) = 0; + virtual void close_output() = 0; + + virtual Outputs::CRT::CRT *get_crt() = 0; + virtual Outputs::Speaker *get_speaker() = 0; + + virtual void run_for_cycles(int number_of_cycles) = 0; +}; + +} + +#endif /* CRTMachine_hpp */ diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ff1b5ab93..af60bb8de 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -450,7 +450,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin return cycles; } -void Machine::update_output() +void Machine::synchronise() { update_display(); update_audio(); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 151cdbe54..373d555a6 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,9 +10,8 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT/CRT.hpp" -#include "../../Outputs/Speaker.hpp" #include "../../Storage/Tape/Tape.hpp" +#include "../CRTMachine.hpp" #include namespace Electron { @@ -142,30 +141,34 @@ class Speaker: public ::Outputs::Filter { @discussion An instance of Electron::Machine represents the current state of an Acorn Electron. */ -class Machine: public CPU6502::Processor, Tape::Delegate { +class Machine: public CPU6502::Processor, Tape::Delegate, CRTMachine::Machine { public: Machine(); - unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void set_rom(ROMSlot slot, size_t length, const uint8_t *data); void set_tape(std::shared_ptr tape); void set_key_state(Key key, bool isPressed); void clear_all_keys(); - void setup_output(float aspect_ratio); - void close_output(); - Outputs::CRT::CRT *get_crt() { return _crt.get(); } - Outputs::Speaker *get_speaker() { return &_speaker; } - - virtual void tape_did_change_interrupt_status(Tape *tape); - - void update_output(); inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise(); + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual Outputs::CRT::CRT *get_crt() { return _crt.get(); } + virtual Outputs::Speaker *get_speaker() { return &_speaker; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + + // to satisfy Tape::Delegate + virtual void tape_did_change_interrupt_status(Tape *tape); + private: inline void update_display(); @@ -215,12 +218,12 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint8_t *_current_output_target, *_initial_output_target; unsigned int _current_output_divider; - // Tape. + // Tape Tape _tape; bool _use_fast_tape_hack; bool _fast_load_is_in_data; - // Outputs. + // Outputs std::unique_ptr _crt; Speaker _speaker; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5b2fa6348..f9dac9acd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -334,6 +334,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = ""; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; @@ -1192,6 +1193,7 @@ children = ( 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, ); name = Machines; path = ../../Machines; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index bb8ec8c12..08a039cec 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -19,7 +19,6 @@ - (void)runForNumberOfCycles:(int)numberOfCycles { @synchronized(self) { _electron.run_for_cycles(numberOfCycles); - _electron.update_output(); } } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 759d04472..efd1fe772 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -24,6 +24,18 @@ class Speaker { virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; }; + int get_ideal_clock_rate_in_range(int minimum, int maximum) + { + // return exactly the input rate if possible + if(_input_cycles_per_second >= minimum && _input_cycles_per_second <= maximum) return _input_cycles_per_second; + + // if the input rate is lower, return the minimum + if(_input_cycles_per_second < minimum) return minimum; + + // otherwise, return the maximum + return maximum; + } + void set_output_rate(int cycles_per_second, int buffer_size) { _output_cycles_per_second = cycles_per_second; diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index fc3fc5633..e4e884a06 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -67,9 +67,12 @@ extern const uint8_t JamOpcode; @abstact An abstract base class for emulation of a 6502 processor via the curiously recurring template pattern/f-bounded polymorphism. @discussion Subclasses should implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) in - order to provde the bus on which the 6502 operates. Additional functionality can be provided by the host machine by providing - a jam handler and inserting jam opcodes where appropriate; that will cause call outs when the program counter reaches those - addresses. @c return_from_subroutine can be used to exit from a jammed state. + order to provide the bus on which the 6502 operates and @c synchronise(), which is called upon completion of a continuous run + of cycles to allow a subclass to bring any on-demand activities up to date. + + Additional functionality can be provided by the host machine by providing a jam handler and inserting jam opcodes where appropriate; + that will cause call outs when the program counter reaches those addresses. @c return_from_subroutine can be used to exit from a + jammed state. */ template class Processor { public: @@ -597,7 +600,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; -// printf("%04x\n", _pc.full); +// printf("%04x x:%02x\n", _pc.full, _x); _pc.full++; read_op(_operation, _lastOperationPC.full); @@ -1041,15 +1044,17 @@ template class Processor { _ready_is_active = true; } } - - _cycles_left_to_run = number_of_cycles; - _scheduleProgramsReadPointer = scheduleProgramsReadPointer; - _scheduleProgramProgramCounter = scheduleProgramProgramCounter; - _nextAddress = nextAddress; - _nextBusOperation = nextBusOperation; - _busAddress = busAddress; - _busValue = busValue; } + + _cycles_left_to_run = number_of_cycles; + _scheduleProgramsReadPointer = scheduleProgramsReadPointer; + _scheduleProgramProgramCounter = scheduleProgramProgramCounter; + _nextAddress = nextAddress; + _nextBusOperation = nextBusOperation; + _busAddress = busAddress; + _busValue = busValue; + + static_cast(this)->synchronise(); } /*!