1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 18:30:21 +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);"
"}");
_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] =

View File

@ -10,31 +10,52 @@
#define Atari2600_cpp
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../CRTMachine.hpp"
#include <stdint.h>
#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<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:
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<Machine>::run_for_cycles(number_of_cycles); }
private:
uint8_t *_rom, *_romPages[4], _ram[128];
@ -137,7 +158,10 @@ class Machine: public CPU6502::Processor<Machine> {
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;

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;
}
void Machine::update_output()
void Machine::synchronise()
{
update_display();
update_audio();

View File

@ -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 <stdint.h>
namespace Electron {
@ -142,30 +141,34 @@ class Speaker: public ::Outputs::Filter<Speaker> {
@discussion An instance of Electron::Machine represents the current state of an
Acorn Electron.
*/
class Machine: public CPU6502::Processor<Machine>, Tape::Delegate {
class Machine: public CPU6502::Processor<Machine>, 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<Storage::Tape> 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<Machine>::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<Machine>, 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<Outputs::CRT::CRT> _crt;
Speaker _speaker;
};

View File

@ -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 = "<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>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
@ -1192,6 +1193,7 @@
children = (
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B2E2D9E1C3A070900138695 /* Electron */,
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
);
name = Machines;
path = ../../Machines;

View File

@ -19,7 +19,6 @@
- (void)runForNumberOfCycles:(int)numberOfCycles {
@synchronized(self) {
_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;
};
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;

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.
@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 T> class Processor {
public:
@ -597,7 +600,7 @@ template <class T> 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 T> 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<T *>(this)->synchronise();
}
/*!