mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +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:
parent
3fc80ea01f
commit
5c4f35e13f
@ -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] =
|
||||
|
@ -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
30
Machines/CRTMachine.hpp
Normal 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 */
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -19,7 +19,6 @@
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles {
|
||||
@synchronized(self) {
|
||||
_electron.run_for_cycles(numberOfCycles);
|
||||
_electron.update_output();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
Loading…
Reference in New Issue
Block a user