1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

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.

This commit is contained in:
Thomas Harte 2016-05-31 21:23:44 -04:00
parent 3fc80ea01f
commit 5c4f35e13f
9 changed files with 128 additions and 40 deletions

View File

@ -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);" "return (float(y) / 14.0) * (1.0 - amplitude) + step(1, iPhase) * amplitude * cos(phase + phaseOffset);"
"}"); "}");
_crt->set_output_device(Outputs::CRT::Television); _crt->set_output_device(Outputs::CRT::Television);
_speaker.set_input_rate(2 * 263 * 60);
} }
void Machine::switch_region() 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);" "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); _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1);
_speaker.set_input_rate(2 * 312 * 50);
} }
void Machine::close_output() void Machine::close_output()
@ -445,6 +449,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
returnValue &= _ram[address&0x7f]; returnValue &= _ram[address&0x7f];
} else { } else {
_ram[address&0x7f] = *value; _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]; _playerGraphics[1][index^1] = _playerGraphics[0][index^1];
} break; } break;
case 0x1d: 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: case 0x1f:
_ballGraphicsEnable[0] = ((*value) >> 1)&1; _ballGraphicsEnable[0] = ((*value) >> 1)&1;
break; 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; _objectCounter[_objectCounterPointer][index + 2].count = (_objectCounter[_objectCounterPointer][index + 2].count + extra_offset)%160;
} }
_missileGraphicsReset[index] = !!((*value) & 0x02); _missileGraphicsReset[index] = !!((*value) & 0x02);
// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-');
} }
break; break;
case 0x2a: case 0x2a: {
// justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete"; // 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 // which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be
// in five cycles from now // in five cycles from now
// int start_pause = ((_horizontalTimer + 3)&3) + 4;
_upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup;
break; } break;
case 0x2b: case 0x2b:
_objectMotion[0] = _objectMotion[0] =
_objectMotion[1] = _objectMotion[1] =

View File

@ -10,31 +10,52 @@
#define Atari2600_cpp #define Atari2600_cpp
#include "../../Processors/6502/CPU6502.hpp" #include "../../Processors/6502/CPU6502.hpp"
#include "../../Outputs/CRT/CRT.hpp" #include "../CRTMachine.hpp"
#include <stdint.h> #include <stdint.h>
#include "Atari2600Inputs.h" #include "Atari2600Inputs.h"
namespace Atari2600 { 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; const unsigned int number_of_recorded_counters = 7;
class Machine: public CPU6502::Processor<Machine> { class Speaker: public ::Outputs::Filter<Speaker> {
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<Machine>, CRTMachine::Machine {
public: public:
Machine(); Machine();
~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 set_rom(size_t length, const uint8_t *data);
void switch_region(); void switch_region();
void set_digital_input(Atari2600DigitalInput input, bool state); void set_digital_input(Atari2600DigitalInput input, bool state);
Outputs::CRT::CRT *get_crt() { return _crt; } // to satisfy CPU6502::Processor
void setup_output(float aspect_ratio); unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void close_output(); 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<Machine>::run_for_cycles(number_of_cycles); }
private: private:
uint8_t *_rom, *_romPages[4], _ram[128]; uint8_t *_rom, *_romPages[4], _ram[128];
@ -137,7 +158,10 @@ class Machine: public CPU6502::Processor<Machine> {
void output_pixels(unsigned int count); void output_pixels(unsigned int count);
uint8_t get_output_pixel(); uint8_t get_output_pixel();
void update_timers(int mask); void update_timers(int mask);
// Outputs
Outputs::CRT::CRT *_crt; Outputs::CRT::CRT *_crt;
Speaker _speaker;
// latched output state // latched output state
unsigned int _lastOutputStateDuration; unsigned int _lastOutputStateDuration;

30
Machines/CRTMachine.hpp Normal file
View File

@ -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 */

View File

@ -450,7 +450,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
return cycles; return cycles;
} }
void Machine::update_output() void Machine::synchronise()
{ {
update_display(); update_display();
update_audio(); update_audio();

View File

@ -10,9 +10,8 @@
#define Electron_hpp #define Electron_hpp
#include "../../Processors/6502/CPU6502.hpp" #include "../../Processors/6502/CPU6502.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../CRTMachine.hpp"
#include <stdint.h> #include <stdint.h>
namespace Electron { namespace Electron {
@ -142,30 +141,34 @@ class Speaker: public ::Outputs::Filter<Speaker> {
@discussion An instance of Electron::Machine represents the current state of an @discussion An instance of Electron::Machine represents the current state of an
Acorn Electron. Acorn Electron.
*/ */
class Machine: public CPU6502::Processor<Machine>, Tape::Delegate { class Machine: public CPU6502::Processor<Machine>, Tape::Delegate, CRTMachine::Machine {
public: public:
Machine(); 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_rom(ROMSlot slot, size_t length, const uint8_t *data);
void set_tape(std::shared_ptr<Storage::Tape> tape); void set_tape(std::shared_ptr<Storage::Tape> tape);
void set_key_state(Key key, bool isPressed); void set_key_state(Key key, bool isPressed);
void clear_all_keys(); 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; } 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<Machine>::run_for_cycles(number_of_cycles); }
// to satisfy Tape::Delegate
virtual void tape_did_change_interrupt_status(Tape *tape);
private: private:
inline void update_display(); inline void update_display();
@ -215,12 +218,12 @@ class Machine: public CPU6502::Processor<Machine>, Tape::Delegate {
uint8_t *_current_output_target, *_initial_output_target; uint8_t *_current_output_target, *_initial_output_target;
unsigned int _current_output_divider; unsigned int _current_output_divider;
// Tape. // Tape
Tape _tape; Tape _tape;
bool _use_fast_tape_hack; bool _use_fast_tape_hack;
bool _fast_load_is_in_data; bool _fast_load_is_in_data;
// Outputs. // Outputs
std::unique_ptr<Outputs::CRT::CRT> _crt; std::unique_ptr<Outputs::CRT::CRT> _crt;
Speaker _speaker; Speaker _speaker;
}; };

View File

@ -334,6 +334,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
@ -1192,6 +1193,7 @@
children = ( children = (
4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B2E2D9E1C3A070900138695 /* Electron */, 4B2E2D9E1C3A070900138695 /* Electron */,
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
); );
name = Machines; name = Machines;
path = ../../Machines; path = ../../Machines;

View File

@ -19,7 +19,6 @@
- (void)runForNumberOfCycles:(int)numberOfCycles { - (void)runForNumberOfCycles:(int)numberOfCycles {
@synchronized(self) { @synchronized(self) {
_electron.run_for_cycles(numberOfCycles); _electron.run_for_cycles(numberOfCycles);
_electron.update_output();
} }
} }

View File

@ -24,6 +24,18 @@ class Speaker {
virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; 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) void set_output_rate(int cycles_per_second, int buffer_size)
{ {
_output_cycles_per_second = cycles_per_second; _output_cycles_per_second = cycles_per_second;

View File

@ -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. @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 @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 order to provide the bus on which the 6502 operates and @c synchronise(), which is called upon completion of a continuous run
a jam handler and inserting jam opcodes where appropriate; that will cause call outs when the program counter reaches those of cycles to allow a subclass to bring any on-demand activities up to date.
addresses. @c return_from_subroutine can be used to exit from a jammed state.
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 T> class Processor { template <class T> class Processor {
public: public:
@ -597,7 +600,7 @@ template <class T> class Processor {
case CycleFetchOperation: { case CycleFetchOperation: {
_lastOperationPC = _pc; _lastOperationPC = _pc;
// printf("%04x\n", _pc.full); // printf("%04x x:%02x\n", _pc.full, _x);
_pc.full++; _pc.full++;
read_op(_operation, _lastOperationPC.full); read_op(_operation, _lastOperationPC.full);
@ -1041,15 +1044,17 @@ template <class T> class Processor {
_ready_is_active = true; _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<T *>(this)->synchronise();
} }
/*! /*!