mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-14 20:31:53 +00:00
Adopt new indentation, improve const
ness.
This commit is contained in:
parent
36edfe9715
commit
5545906063
Components
@ -74,7 +74,7 @@ uint8_t MFP68901::read(int address) {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void MFP68901::write(int address, uint8_t value) {
|
||||
void MFP68901::write(int address, const uint8_t value) {
|
||||
address &= 0x1f;
|
||||
|
||||
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
|
||||
@ -181,7 +181,7 @@ void MFP68901::write(int address, uint8_t value) {
|
||||
}
|
||||
|
||||
template <int timer>
|
||||
void MFP68901::run_timer_for(int cycles) {
|
||||
void MFP68901::run_timer_for(const int cycles) {
|
||||
if(timers_[timer].mode >= TimerMode::Delay) {
|
||||
// This code applies the timer prescaling only. prescale_count is used to count
|
||||
// upwards rather than downwards for simplicity, but on the real hardware it's
|
||||
@ -202,7 +202,7 @@ void MFP68901::run_timer_for(int cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void MFP68901::run_for(HalfCycles time) {
|
||||
void MFP68901::run_for(const HalfCycles time) {
|
||||
cycles_left_ += time;
|
||||
|
||||
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
|
||||
@ -220,7 +220,7 @@ HalfCycles MFP68901::next_sequence_point() {
|
||||
|
||||
// MARK: - Timers
|
||||
|
||||
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
|
||||
void MFP68901::set_timer_mode(const int timer, const TimerMode mode, const int prescale, const bool reset_timer) {
|
||||
logger.error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
|
||||
timers_[timer].mode = mode;
|
||||
if(reset_timer) {
|
||||
@ -236,19 +236,19 @@ void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool rese
|
||||
timers_[timer].prescale = prescale;
|
||||
}
|
||||
|
||||
void MFP68901::set_timer_data(int timer, uint8_t value) {
|
||||
void MFP68901::set_timer_data(const int timer, const uint8_t value) {
|
||||
if(timers_[timer].mode == TimerMode::Stopped) {
|
||||
timers_[timer].value = value;
|
||||
}
|
||||
timers_[timer].reload_value = value;
|
||||
}
|
||||
|
||||
uint8_t MFP68901::get_timer_data(int timer) {
|
||||
uint8_t MFP68901::get_timer_data(const int timer) {
|
||||
return timers_[timer].value;
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void MFP68901::set_timer_event_input(bool value) {
|
||||
void MFP68901::set_timer_event_input(const bool value) {
|
||||
if(timers_[channel].event_input == value) return;
|
||||
|
||||
timers_[channel].event_input = value;
|
||||
@ -316,7 +316,7 @@ void MFP68901::decrement_timer(int amount) {
|
||||
}
|
||||
|
||||
// MARK: - GPIP
|
||||
void MFP68901::set_port_input(uint8_t input) {
|
||||
void MFP68901::set_port_input(const uint8_t input) {
|
||||
gpip_input_ = input;
|
||||
reevaluate_gpip_interrupts();
|
||||
}
|
||||
@ -342,12 +342,12 @@ void MFP68901::reevaluate_gpip_interrupts() {
|
||||
|
||||
// MARK: - Interrupts
|
||||
|
||||
void MFP68901::begin_interrupts(int interrupt) {
|
||||
void MFP68901::begin_interrupts(const int interrupt) {
|
||||
interrupt_pending_ |= interrupt & interrupt_enable_;
|
||||
update_interrupts();
|
||||
}
|
||||
|
||||
void MFP68901::end_interrupts(int interrupt) {
|
||||
void MFP68901::end_interrupts(const int interrupt) {
|
||||
interrupt_pending_ &= ~interrupt;
|
||||
update_interrupts();
|
||||
}
|
||||
@ -403,6 +403,6 @@ int MFP68901::acknowledge_interrupt() {
|
||||
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
|
||||
}
|
||||
|
||||
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
|
||||
void MFP68901::set_interrupt_delegate(InterruptDelegate *const delegate) {
|
||||
interrupt_delegate_ = delegate;
|
||||
}
|
||||
|
@ -24,162 +24,162 @@ class PortHandler {
|
||||
Models the Motorola 68901 Multi-Function Peripheral ('MFP').
|
||||
*/
|
||||
class MFP68901: public ClockingHint::Source {
|
||||
public:
|
||||
/// @returns the result of a read from @c address.
|
||||
uint8_t read(int address);
|
||||
public:
|
||||
/// @returns the result of a read from @c address.
|
||||
uint8_t read(int address);
|
||||
|
||||
/// Performs a write of @c value to @c address.
|
||||
void write(int address, uint8_t value);
|
||||
/// Performs a write of @c value to @c address.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/// Advances the MFP by the supplied number of HalfCycles.
|
||||
void run_for(HalfCycles);
|
||||
/// Advances the MFP by the supplied number of HalfCycles.
|
||||
void run_for(HalfCycles);
|
||||
|
||||
/// @returns the number of cycles until the next possible sequence point — the next time
|
||||
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
|
||||
/// so that mechanism can also be used to reduce the quantity of calls into this class.
|
||||
///
|
||||
/// @discussion TODO, alas.
|
||||
HalfCycles next_sequence_point();
|
||||
/// @returns the number of cycles until the next possible sequence point — the next time
|
||||
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
|
||||
/// so that mechanism can also be used to reduce the quantity of calls into this class.
|
||||
///
|
||||
/// @discussion TODO, alas.
|
||||
HalfCycles next_sequence_point();
|
||||
|
||||
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
|
||||
template <int channel>
|
||||
void set_timer_event_input(bool value);
|
||||
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
|
||||
template <int channel>
|
||||
void set_timer_event_input(bool value);
|
||||
|
||||
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
|
||||
///
|
||||
/// @discussion TODO.
|
||||
void set_port_handler(PortHandler *);
|
||||
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
|
||||
///
|
||||
/// @discussion TODO.
|
||||
void set_port_handler(PortHandler *);
|
||||
|
||||
/// Sets the current input GPIP values.
|
||||
void set_port_input(uint8_t);
|
||||
/// Sets the current input GPIP values.
|
||||
void set_port_input(uint8_t);
|
||||
|
||||
/// @returns the current GPIP output values.
|
||||
///
|
||||
/// @discussion TODO.
|
||||
uint8_t get_port_output();
|
||||
/// @returns the current GPIP output values.
|
||||
///
|
||||
/// @discussion TODO.
|
||||
uint8_t get_port_output();
|
||||
|
||||
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
|
||||
bool get_interrupt_line();
|
||||
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
|
||||
bool get_interrupt_line();
|
||||
|
||||
static constexpr int NoAcknowledgement = 0x100;
|
||||
static constexpr int NoAcknowledgement = 0x100;
|
||||
|
||||
/// Communicates an interrupt acknowledge cycle.
|
||||
///
|
||||
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
|
||||
int acknowledge_interrupt();
|
||||
/// Communicates an interrupt acknowledge cycle.
|
||||
///
|
||||
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
|
||||
int acknowledge_interrupt();
|
||||
|
||||
struct InterruptDelegate {
|
||||
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
|
||||
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
|
||||
};
|
||||
/// Sets a delegate that will be notified upon any change in the interrupt line.
|
||||
void set_interrupt_delegate(InterruptDelegate *delegate);
|
||||
struct InterruptDelegate {
|
||||
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
|
||||
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
|
||||
};
|
||||
/// Sets a delegate that will be notified upon any change in the interrupt line.
|
||||
void set_interrupt_delegate(InterruptDelegate *);
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
private:
|
||||
// MARK: - Timers
|
||||
enum class TimerMode {
|
||||
Stopped, EventCount, Delay, PulseWidth
|
||||
};
|
||||
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
|
||||
void set_timer_data(int timer, uint8_t);
|
||||
uint8_t get_timer_data(int timer);
|
||||
template <int timer> void decrement_timer(int amount);
|
||||
template <int timer> void run_timer_for(int cycles);
|
||||
private:
|
||||
// MARK: - Timers
|
||||
enum class TimerMode {
|
||||
Stopped, EventCount, Delay, PulseWidth
|
||||
};
|
||||
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
|
||||
void set_timer_data(int timer, uint8_t);
|
||||
uint8_t get_timer_data(int timer);
|
||||
template <int timer> void decrement_timer(int amount);
|
||||
template <int timer> void run_timer_for(int cycles);
|
||||
|
||||
struct Timer {
|
||||
TimerMode mode = TimerMode::Stopped;
|
||||
uint8_t value = 0;
|
||||
uint8_t reload_value = 0;
|
||||
int prescale = 1;
|
||||
int prescale_count = 1;
|
||||
bool event_input = false;
|
||||
} timers_[4];
|
||||
uint8_t timer_ab_control_[2] = { 0, 0 };
|
||||
uint8_t timer_cd_control_ = 0;
|
||||
struct Timer {
|
||||
TimerMode mode = TimerMode::Stopped;
|
||||
uint8_t value = 0;
|
||||
uint8_t reload_value = 0;
|
||||
int prescale = 1;
|
||||
int prescale_count = 1;
|
||||
bool event_input = false;
|
||||
} timers_[4];
|
||||
uint8_t timer_ab_control_[2] = { 0, 0 };
|
||||
uint8_t timer_cd_control_ = 0;
|
||||
|
||||
HalfCycles cycles_left_;
|
||||
HalfCycles cycles_left_;
|
||||
|
||||
// MARK: - GPIP
|
||||
uint8_t gpip_input_ = 0;
|
||||
uint8_t gpip_output_ = 0;
|
||||
uint8_t gpip_active_edge_ = 0;
|
||||
uint8_t gpip_direction_ = 0;
|
||||
uint8_t gpip_interrupt_state_ = 0;
|
||||
// MARK: - GPIP
|
||||
uint8_t gpip_input_ = 0;
|
||||
uint8_t gpip_output_ = 0;
|
||||
uint8_t gpip_active_edge_ = 0;
|
||||
uint8_t gpip_direction_ = 0;
|
||||
uint8_t gpip_interrupt_state_ = 0;
|
||||
|
||||
void reevaluate_gpip_interrupts();
|
||||
void reevaluate_gpip_interrupts();
|
||||
|
||||
// MARK: - Interrupts
|
||||
// MARK: - Interrupts
|
||||
|
||||
InterruptDelegate *interrupt_delegate_ = nullptr;
|
||||
InterruptDelegate *interrupt_delegate_ = nullptr;
|
||||
|
||||
// Ad hoc documentation:
|
||||
//
|
||||
// An interrupt becomes pending if it is enabled at the time it occurs.
|
||||
//
|
||||
// If a pending interrupt is enabled in the interrupt mask, a processor
|
||||
// interrupt is generated. Otherwise no processor interrupt is generated.
|
||||
//
|
||||
// (Disabling a bit in the enabled mask also instantaneously clears anything
|
||||
// in the pending mask.)
|
||||
//
|
||||
// The user can write to the pending interrupt register; a write
|
||||
// masks whatever is there — so you can disable bits but you cannot set them.
|
||||
//
|
||||
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
|
||||
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
|
||||
// its in-service bit. That bit will remain set until the user writes a zero to its position.
|
||||
// If any bits are set in the in-service register, then they will prevent lower-priority
|
||||
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
|
||||
// priority may occur.
|
||||
//
|
||||
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
|
||||
// Acknowledgement of an interrupt will automatically clear the corresponding
|
||||
// pending bit.
|
||||
//
|
||||
int interrupt_enable_ = 0;
|
||||
int interrupt_pending_ = 0;
|
||||
int interrupt_mask_ = 0;
|
||||
int interrupt_in_service_ = 0;
|
||||
bool interrupt_line_ = false;
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
// Ad hoc documentation:
|
||||
//
|
||||
// An interrupt becomes pending if it is enabled at the time it occurs.
|
||||
//
|
||||
// If a pending interrupt is enabled in the interrupt mask, a processor
|
||||
// interrupt is generated. Otherwise no processor interrupt is generated.
|
||||
//
|
||||
// (Disabling a bit in the enabled mask also instantaneously clears anything
|
||||
// in the pending mask.)
|
||||
//
|
||||
// The user can write to the pending interrupt register; a write
|
||||
// masks whatever is there — so you can disable bits but you cannot set them.
|
||||
//
|
||||
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
|
||||
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
|
||||
// its in-service bit. That bit will remain set until the user writes a zero to its position.
|
||||
// If any bits are set in the in-service register, then they will prevent lower-priority
|
||||
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
|
||||
// priority may occur.
|
||||
//
|
||||
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
|
||||
// Acknowledgement of an interrupt will automatically clear the corresponding
|
||||
// pending bit.
|
||||
//
|
||||
int interrupt_enable_ = 0;
|
||||
int interrupt_pending_ = 0;
|
||||
int interrupt_mask_ = 0;
|
||||
int interrupt_in_service_ = 0;
|
||||
bool interrupt_line_ = false;
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
|
||||
enum Interrupt {
|
||||
GPIP0 = (1 << 0),
|
||||
GPIP1 = (1 << 1),
|
||||
GPIP2 = (1 << 2),
|
||||
GPIP3 = (1 << 3),
|
||||
TimerD = (1 << 4),
|
||||
TimerC = (1 << 5),
|
||||
GPIP4 = (1 << 6),
|
||||
GPIP5 = (1 << 7),
|
||||
enum Interrupt {
|
||||
GPIP0 = (1 << 0),
|
||||
GPIP1 = (1 << 1),
|
||||
GPIP2 = (1 << 2),
|
||||
GPIP3 = (1 << 3),
|
||||
TimerD = (1 << 4),
|
||||
TimerC = (1 << 5),
|
||||
GPIP4 = (1 << 6),
|
||||
GPIP5 = (1 << 7),
|
||||
|
||||
TimerB = (1 << 8),
|
||||
TransmitError = (1 << 9),
|
||||
TransmitBufferEmpty = (1 << 10),
|
||||
ReceiveError = (1 << 11),
|
||||
ReceiveBufferFull = (1 << 12),
|
||||
TimerA = (1 << 13),
|
||||
GPIP6 = (1 << 14),
|
||||
GPIP7 = (1 << 15),
|
||||
};
|
||||
void begin_interrupts(int interrupt);
|
||||
void end_interrupts(int interrupt);
|
||||
void update_interrupts();
|
||||
TimerB = (1 << 8),
|
||||
TransmitError = (1 << 9),
|
||||
TransmitBufferEmpty = (1 << 10),
|
||||
ReceiveError = (1 << 11),
|
||||
ReceiveBufferFull = (1 << 12),
|
||||
TimerA = (1 << 13),
|
||||
GPIP6 = (1 << 14),
|
||||
GPIP7 = (1 << 15),
|
||||
};
|
||||
void begin_interrupts(int interrupt);
|
||||
void end_interrupts(int interrupt);
|
||||
void update_interrupts();
|
||||
|
||||
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
|
||||
inline static int msb16(int v) {
|
||||
// Saturate all bits below the MSB.
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
|
||||
static constexpr int msb16(int v) {
|
||||
// Saturate all bits below the MSB.
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
|
||||
// Throw away lesser bits.
|
||||
return (v+1) >> 1;
|
||||
}
|
||||
// Throw away lesser bits.
|
||||
return (v+1) >> 1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -54,75 +54,75 @@ namespace TI::TMS {
|
||||
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||
*/
|
||||
template <Personality personality> class TMS9918: private Base<personality> {
|
||||
public:
|
||||
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
|
||||
TMS9918();
|
||||
public:
|
||||
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
|
||||
TMS9918();
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard);
|
||||
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
|
||||
/*! Gets the current scan status. */
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
/*! Gets the current scan status. */
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*! Sets the type of CRT display. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
/*! Sets the type of CRT display. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/*! Gets the type of CRT display. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
/*! Gets the type of CRT display. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/*!
|
||||
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
|
||||
/*!
|
||||
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
|
||||
|
||||
For everything except the Mega Drive VDP:
|
||||
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
|
||||
For everything except the Mega Drive VDP:
|
||||
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
|
||||
|
||||
For the Mega Drive:
|
||||
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
|
||||
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
For the Mega Drive:
|
||||
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
|
||||
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
|
||||
*/
|
||||
void run_for(const HalfCycles);
|
||||
|
||||
/*! Sets a register value. */
|
||||
void write(int address, uint8_t value);
|
||||
/*! Sets a register value. */
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/*! Gets a register value. */
|
||||
uint8_t read(int address);
|
||||
/*! Gets a register value. */
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! Gets the current scan line; provided by the Sega VDPs only. */
|
||||
uint8_t get_current_line() const;
|
||||
/*! Gets the current scan line; provided by the Sega VDPs only. */
|
||||
uint8_t get_current_line() const;
|
||||
|
||||
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
|
||||
uint8_t get_latched_horizontal_counter() const;
|
||||
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
|
||||
uint8_t get_latched_horizontal_counter() const;
|
||||
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
|
||||
/*!
|
||||
Returns the amount of time until @c get_interrupt_line would next change if
|
||||
there are no interceding calls to @c write or to @c read.
|
||||
/*!
|
||||
Returns the amount of time until @c get_interrupt_line would next change if
|
||||
there are no interceding calls to @c write or to @c read.
|
||||
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles next_sequence_point() const;
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
reached on line @c line. If no line interrupt position is defined for
|
||||
this VDP, returns the time until the 'beginning' of that line, whatever
|
||||
that may mean.
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
reached on line @c line. If no line interrupt position is defined for
|
||||
this VDP, returns the time until the 'beginning' of that line, whatever
|
||||
that may mean.
|
||||
|
||||
@line is relative to the first pixel line of the display and may be negative.
|
||||
*/
|
||||
HalfCycles get_time_until_line(int line);
|
||||
@line is relative to the first pixel line of the display and may be negative.
|
||||
*/
|
||||
HalfCycles get_time_until_line(int line);
|
||||
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line() const;
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ TMS9918<personality>::TMS9918() {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void TMS9918<personality>::set_tv_standard(TVStandard standard) {
|
||||
void TMS9918<personality>::set_tv_standard(const TVStandard standard) {
|
||||
// TODO: the Yamaha is programmable on this at runtime.
|
||||
this->tv_standard_ = standard;
|
||||
switch(standard) {
|
||||
@ -104,7 +104,7 @@ void TMS9918<personality>::set_tv_standard(TVStandard standard) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void TMS9918<personality>::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
void TMS9918<personality>::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
this->crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ Outputs::Display::ScanStatus TMS9918<personality>::get_scaled_scan_status() cons
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void TMS9918<personality>::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
void TMS9918<personality>::set_display_type(const Outputs::Display::DisplayType display_type) {
|
||||
this->crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ void SpriteBuffer::reset_sprite_collection() {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::posit_sprite(int sprite_number, int sprite_position, uint8_t screen_row) {
|
||||
void Base<personality>::posit_sprite(const int sprite_number, const int sprite_position, const uint8_t screen_row) {
|
||||
// Evaluation of visibility of sprite 0 is always the first step in
|
||||
// populating a sprite buffer; so use it to uncork a new one.
|
||||
if(!sprite_number) {
|
||||
@ -661,7 +661,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram_dot) {
|
||||
void Base<personality>::output_border(int cycles, [[maybe_unused]] const uint32_t cram_dot) {
|
||||
cycles = from_internal<personality, Clock::CRT>(cycles);
|
||||
|
||||
uint32_t border_colour;
|
||||
@ -694,7 +694,7 @@ void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram
|
||||
// MARK: - External interface.
|
||||
|
||||
template <Personality personality>
|
||||
int Base<personality>::masked_address(int address) const {
|
||||
int Base<personality>::masked_address(const int address) const {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
return address & 3;
|
||||
} else {
|
||||
@ -703,7 +703,7 @@ int Base<personality>::masked_address(int address) const {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::write_vram(uint8_t value) {
|
||||
void Base<personality>::write_vram(const uint8_t value) {
|
||||
write_phase_ = false;
|
||||
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
@ -713,7 +713,7 @@ void Base<personality>::write_vram(uint8_t value) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::commit_register(int reg, uint8_t value) {
|
||||
void Base<personality>::commit_register(int reg, const uint8_t value) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
reg &= 0x3f;
|
||||
} else if constexpr (is_sega_vdp(personality)) {
|
||||
@ -1017,7 +1017,7 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::write_register(uint8_t value) {
|
||||
void Base<personality>::write_register(const uint8_t value) {
|
||||
// Writes to address 1 are performed in pairs; if this is the
|
||||
// low byte of a value, store it and wait for the high byte.
|
||||
if(!write_phase_) {
|
||||
@ -1068,7 +1068,7 @@ void Base<personality>::write_register(uint8_t value) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::write_palette(uint8_t value) {
|
||||
void Base<personality>::write_palette(const uint8_t value) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
if(!Storage<personality>::palette_write_phase_) {
|
||||
Storage<personality>::new_colour_ = value;
|
||||
@ -1091,7 +1091,7 @@ void Base<personality>::write_palette(uint8_t value) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::write_register_indirect([[maybe_unused]] uint8_t value) {
|
||||
void Base<personality>::write_register_indirect([[maybe_unused]] const uint8_t value) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
// Register 17 cannot be written to indirectly.
|
||||
if(Storage<personality>::indirect_register_ != 17) {
|
||||
@ -1188,7 +1188,7 @@ uint8_t Base<personality>::read_register() {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
uint8_t TMS9918<personality>::read(int address) {
|
||||
uint8_t TMS9918<personality>::read(const int address) {
|
||||
const int target = this->masked_address(address);
|
||||
|
||||
if(target < 2) {
|
||||
|
@ -41,7 +41,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles
|
||||
// after corresponding data read.
|
||||
|
||||
static constexpr uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
|
||||
static constexpr uint32_t palette_pack(const uint8_t r, const uint8_t g, const uint8_t b) {
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
return uint32_t((r << 24) | (g << 16) | (b << 8));
|
||||
#else
|
||||
@ -85,7 +85,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
/// Mutates @c target such that @c source replaces the @c length bits that currently start
|
||||
/// at bit @c shift . Subsequently ensures @c target is constrained by the
|
||||
/// applicable @c memory_mask.
|
||||
template <int shift, int length = 8> void install_field(AddressT &target, uint8_t source) {
|
||||
template <int shift, int length = 8> void install_field(AddressT &target, const uint8_t source) {
|
||||
static_assert(length > 0 && length <= 8);
|
||||
constexpr auto source_mask = (1 << length) - 1;
|
||||
constexpr auto mask = AddressT(~(source_mask << shift));
|
||||
@ -302,11 +302,11 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
return ScreenMode::Blank;
|
||||
}
|
||||
|
||||
static AddressT rotate(AddressT address) {
|
||||
static AddressT rotate(const AddressT address) {
|
||||
return AddressT((address >> 1) | (address << 16)) & memory_mask(personality);
|
||||
}
|
||||
|
||||
AddressT command_address(Vector location, bool expansion) const {
|
||||
AddressT command_address(const Vector location, const bool expansion) const {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
switch(this->underlying_mode_) {
|
||||
default:
|
||||
@ -345,7 +345,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t extract_colour(uint8_t byte, Vector location) const {
|
||||
uint8_t extract_colour(const uint8_t byte, const Vector location) const {
|
||||
switch(this->screen_mode_) {
|
||||
default:
|
||||
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
|
||||
@ -360,7 +360,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint8_t, uint8_t> command_colour_mask(Vector location) const {
|
||||
std::pair<uint8_t, uint8_t> command_colour_mask(const Vector location) const {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
auto &context = Storage<personality>::command_context_;
|
||||
auto colour = context.latched_colour.has_value() ? context.latched_colour : context.colour;
|
||||
@ -394,7 +394,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
}
|
||||
}
|
||||
|
||||
void do_external_slot(int access_column) {
|
||||
void do_external_slot(const int access_column) {
|
||||
// Don't do anything if the required time for the access to become executable
|
||||
// has yet to pass.
|
||||
if(queued_access_ == MemoryAccess::None || access_column < minimum_access_column_) {
|
||||
@ -588,7 +588,7 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
///
|
||||
/// i.e. it provides standard glue to enter a fetch sequence at any point, while the fetches themselves are templated on the cycle
|
||||
/// at which they appear for neater expression.
|
||||
template<bool use_end, typename Fetcher> void dispatch(Fetcher &fetcher, int start, int end);
|
||||
template<bool use_end, typename Fetcher> void dispatch(Fetcher &, int start, int end);
|
||||
|
||||
// Various fetchers.
|
||||
template<bool use_end> void fetch_tms_refresh(uint8_t y, int start, int end);
|
||||
@ -616,7 +616,14 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
template<ScreenMode mode> void draw_yamaha(uint8_t y, int start, int end);
|
||||
void draw_yamaha(uint8_t y, int start, int end);
|
||||
|
||||
template <SpriteMode mode, bool double_width> void draw_sprites(uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer = nullptr);
|
||||
template <SpriteMode mode, bool double_width>
|
||||
void draw_sprites(
|
||||
uint8_t y,
|
||||
int start,
|
||||
int end,
|
||||
const std::array<uint32_t, 16> &palette,
|
||||
int *colour_buffer = nullptr
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ enum class ScreenMode {
|
||||
YamahaGraphics2 = Graphics,
|
||||
};
|
||||
|
||||
constexpr int pixels_per_byte(ScreenMode mode) {
|
||||
constexpr int pixels_per_byte(const ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 1;
|
||||
@ -54,7 +54,7 @@ constexpr int pixels_per_byte(ScreenMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int width(ScreenMode mode) {
|
||||
constexpr int width(const ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 0;
|
||||
@ -72,11 +72,11 @@ constexpr int width(ScreenMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool interleaves_banks(ScreenMode mode) {
|
||||
constexpr bool interleaves_banks(const ScreenMode mode) {
|
||||
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
|
||||
}
|
||||
|
||||
constexpr bool is_text(ScreenMode mode) {
|
||||
constexpr bool is_text(const ScreenMode mode) {
|
||||
return mode == ScreenMode::Text || mode == ScreenMode::YamahaText80;
|
||||
}
|
||||
|
||||
|
@ -89,77 +89,77 @@ template <Personality personality, Clock head, Clock... tail> constexpr int from
|
||||
an error term be tracked.
|
||||
*/
|
||||
template <Personality personality> class ClockConverter {
|
||||
public:
|
||||
/*!
|
||||
Given that another @c source external **half-cycles** has occurred,
|
||||
indicates how many complete internal **cycles** have additionally elapsed
|
||||
since the last call to @c to_internal.
|
||||
public:
|
||||
/*!
|
||||
Given that another @c source external **half-cycles** has occurred,
|
||||
indicates how many complete internal **cycles** have additionally elapsed
|
||||
since the last call to @c to_internal.
|
||||
|
||||
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
|
||||
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
|
||||
*/
|
||||
int to_internal(int source) {
|
||||
switch(personality) {
|
||||
// Default behaviour is to apply a multiplication by 3/4;
|
||||
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
|
||||
default: {
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
|
||||
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
|
||||
*/
|
||||
int to_internal(const int source) {
|
||||
switch(personality) {
|
||||
// Default behaviour is to apply a multiplication by 3/4;
|
||||
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
|
||||
default: {
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
|
||||
// The two Yamaha chips have an internal clock that is four times
|
||||
// as fast as the TMS, therefore a stateless translation is possible.
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source * 3;
|
||||
// The two Yamaha chips have an internal clock that is four times
|
||||
// as fast as the TMS, therefore a stateless translation is possible.
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source * 3;
|
||||
|
||||
// The Mega Drive runs at 3420 master clocks per line, which is then
|
||||
// divided by 4 or 5 depending on other state. That's 7 times the
|
||||
// rate provided to the CPU; given that the input is in half-cycles
|
||||
// the proper multiplier is therefore 3.5.
|
||||
case Personality::MDVDP: {
|
||||
const int result = source * 7 + cycles_error_;
|
||||
cycles_error_ = result & 1;
|
||||
return result >> 1;
|
||||
}
|
||||
// The Mega Drive runs at 3420 master clocks per line, which is then
|
||||
// divided by 4 or 5 depending on other state. That's 7 times the
|
||||
// rate provided to the CPU; given that the input is in half-cycles
|
||||
// the proper multiplier is therefore 3.5.
|
||||
case Personality::MDVDP: {
|
||||
const int result = source * 7 + cycles_error_;
|
||||
cycles_error_ = result & 1;
|
||||
return result >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the number of external cycles that need to begin from now in order to
|
||||
get at least @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
// Logic here correlates with multipliers as per @c to_internal.
|
||||
switch(personality) {
|
||||
default:
|
||||
// Relative to the external clock multiplied by 3, it will definitely take this
|
||||
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
||||
internal_cycles = (internal_cycles - 1) << 2;
|
||||
/*!
|
||||
Provides the number of external cycles that need to begin from now in order to
|
||||
get at least @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
// Logic here correlates with multipliers as per @c to_internal.
|
||||
switch(personality) {
|
||||
default:
|
||||
// Relative to the external clock multiplied by 3, it will definitely take this
|
||||
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
||||
internal_cycles = (internal_cycles - 1) << 2;
|
||||
|
||||
// It will also be necessary to complete the current one.
|
||||
internal_cycles += 4 - cycles_error_;
|
||||
// It will also be necessary to complete the current one.
|
||||
internal_cycles += 4 - cycles_error_;
|
||||
|
||||
// Round up to get the first external cycle after
|
||||
// the number of internal_cycles has elapsed.
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
// Round up to get the first external cycle after
|
||||
// the number of internal_cycles has elapsed.
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::MDVDP:
|
||||
internal_cycles = (internal_cycles - 1) << 1;
|
||||
internal_cycles += 2 - cycles_error_;
|
||||
return HalfCycles((internal_cycles + 6) / 7);
|
||||
}
|
||||
case Personality::MDVDP:
|
||||
internal_cycles = (internal_cycles - 1) << 1;
|
||||
internal_cycles += 2 - cycles_error_;
|
||||
return HalfCycles((internal_cycles + 6) / 7);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
int cycles_error_ = 0;
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
int cycles_error_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -14,7 +14,13 @@ namespace TI::TMS {
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode mode, bool double_width>
|
||||
void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer) {
|
||||
void Base<personality>::draw_sprites(
|
||||
[[maybe_unused]] const uint8_t y,
|
||||
const int start,
|
||||
const int end,
|
||||
const std::array<uint32_t, 16> &palette,
|
||||
int *const colour_buffer
|
||||
) {
|
||||
if(!draw_line_buffer_->sprites) {
|
||||
return;
|
||||
}
|
||||
@ -255,7 +261,7 @@ void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode sprite_mode>
|
||||
void Base<personality>::draw_tms_character(int start, int end) {
|
||||
void Base<personality>::draw_tms_character(const int start, const int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Paint the background tiles.
|
||||
@ -304,7 +310,7 @@ void Base<personality>::draw_tms_character(int start, int end) {
|
||||
|
||||
template <Personality personality>
|
||||
template <bool apply_blink>
|
||||
void Base<personality>::draw_tms_text(int start, int end) {
|
||||
void Base<personality>::draw_tms_text(const int start, const int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
uint32_t colours[2][2] = {
|
||||
{palette()[background_colour_], palette()[text_colour_]},
|
||||
@ -345,7 +351,11 @@ void Base<personality>::draw_tms_text(int start, int end) {
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] uint32_t cram_dot) {
|
||||
void Base<personality>::draw_sms(
|
||||
[[maybe_unused]] const int start,
|
||||
[[maybe_unused]] const int end,
|
||||
[[maybe_unused]] const uint32_t cram_dot
|
||||
) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
int colour_buffer[256];
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
@ -449,7 +459,7 @@ void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] in
|
||||
|
||||
template <Personality personality>
|
||||
template <ScreenMode mode>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
void Base<personality>::draw_yamaha(const uint8_t y, int start, int end) {
|
||||
[[maybe_unused]] const auto active_palette = palette();
|
||||
const int sprite_start = start >> 2;
|
||||
const int sprite_end = end >> 2;
|
||||
@ -536,7 +546,7 @@ void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
void Base<personality>::draw_yamaha(const uint8_t y, const int start, const int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
switch(draw_line_buffer_->screen_mode) {
|
||||
// Modes that are the same (or close enough) to those on the TMS.
|
||||
|
@ -57,23 +57,40 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
|
||||
|
||||
switch(start) {
|
||||
default: assert(false);
|
||||
index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9);
|
||||
index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19);
|
||||
index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29);
|
||||
index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39);
|
||||
index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49);
|
||||
index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59);
|
||||
index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69);
|
||||
index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79);
|
||||
index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89);
|
||||
index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99);
|
||||
index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109);
|
||||
index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119);
|
||||
index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129);
|
||||
index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139);
|
||||
index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149);
|
||||
index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159);
|
||||
index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169);
|
||||
index(0); index(1); index(2); index(3); index(4);
|
||||
index(5); index(6); index(7); index(8); index(9);
|
||||
index(10); index(11); index(12); index(13); index(14);
|
||||
index(15); index(16); index(17); index(18); index(19);
|
||||
index(20); index(21); index(22); index(23); index(24);
|
||||
index(25); index(26); index(27); index(28); index(29);
|
||||
index(30); index(31); index(32); index(33); index(34);
|
||||
index(35); index(36); index(37); index(38); index(39);
|
||||
index(40); index(41); index(42); index(43); index(44);
|
||||
index(45); index(46); index(47); index(48); index(49);
|
||||
index(50); index(51); index(52); index(53); index(54);
|
||||
index(55); index(56); index(57); index(58); index(59);
|
||||
index(60); index(61); index(62); index(63); index(64);
|
||||
index(65); index(66); index(67); index(68); index(69);
|
||||
index(70); index(71); index(72); index(73); index(74);
|
||||
index(75); index(76); index(77); index(78); index(79);
|
||||
index(80); index(81); index(82); index(83); index(84);
|
||||
index(85); index(86); index(87); index(88); index(89);
|
||||
index(90); index(91); index(92); index(93); index(94);
|
||||
index(95); index(96); index(97); index(98); index(99);
|
||||
index(100); index(101); index(102); index(103); index(104);
|
||||
index(105); index(106); index(107); index(108); index(109);
|
||||
index(110); index(111); index(112); index(113); index(114);
|
||||
index(115); index(116); index(117); index(118); index(119);
|
||||
index(120); index(121); index(122); index(123); index(124);
|
||||
index(125); index(126); index(127); index(128); index(129);
|
||||
index(130); index(131); index(132); index(133); index(134);
|
||||
index(135); index(136); index(137); index(138); index(139);
|
||||
index(140); index(141); index(142); index(143); index(144);
|
||||
index(145); index(146); index(147); index(148); index(149);
|
||||
index(150); index(151); index(152); index(153); index(154);
|
||||
index(155); index(156); index(157); index(158); index(159);
|
||||
index(160); index(161); index(162); index(163); index(164);
|
||||
index(165); index(166); index(167); index(168); index(169);
|
||||
index(170);
|
||||
}
|
||||
|
||||
@ -108,7 +125,7 @@ template <Personality personality>
|
||||
struct CharacterFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
CharacterFetcher(Base<personality> *base, uint8_t y) :
|
||||
CharacterFetcher(Base<personality> *const base, const uint8_t y) :
|
||||
base(base),
|
||||
y(y),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31)))
|
||||
@ -137,15 +154,15 @@ struct CharacterFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_name(int column) {
|
||||
void fetch_name(const int column) {
|
||||
base->tile_offset_ = base->ram_[row_base + AddressT(column)];
|
||||
}
|
||||
|
||||
void fetch_pattern(int column) {
|
||||
void fetch_pattern(const int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)];
|
||||
}
|
||||
|
||||
void fetch_colour(int column) {
|
||||
void fetch_colour(const int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)];
|
||||
}
|
||||
|
||||
|
@ -11,16 +11,16 @@
|
||||
namespace TI::TMS {
|
||||
|
||||
// Genus determinants for the various personalityes.
|
||||
constexpr bool is_sega_vdp(Personality p) {
|
||||
constexpr bool is_sega_vdp(const Personality p) {
|
||||
return p >= Personality::SMSVDP;
|
||||
}
|
||||
|
||||
constexpr bool is_yamaha_vdp(Personality p) {
|
||||
constexpr bool is_yamaha_vdp(const Personality p) {
|
||||
return p == Personality::V9938 || p == Personality::V9958;
|
||||
}
|
||||
|
||||
// i.e. one with the original internal timings.
|
||||
constexpr bool is_classic_vdp(Personality p) {
|
||||
constexpr bool is_classic_vdp(const Personality p) {
|
||||
return
|
||||
p == Personality::TMS9918A ||
|
||||
p == Personality::SMSVDP ||
|
||||
@ -28,7 +28,7 @@ constexpr bool is_classic_vdp(Personality p) {
|
||||
p == Personality::GGVDP;
|
||||
}
|
||||
|
||||
constexpr size_t memory_size(Personality p) {
|
||||
constexpr size_t memory_size(const Personality p) {
|
||||
switch(p) {
|
||||
case TI::TMS::TMS9918A:
|
||||
case TI::TMS::SMSVDP:
|
||||
@ -41,7 +41,7 @@ constexpr size_t memory_size(Personality p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr size_t memory_mask(Personality p) {
|
||||
constexpr size_t memory_mask(const Personality p) {
|
||||
return memory_size(p) - 1;
|
||||
}
|
||||
|
||||
|
@ -28,269 +28,269 @@ template <> struct Storage<Personality::TMS9918A> {
|
||||
};
|
||||
|
||||
struct YamahaFetcher {
|
||||
public:
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
|
||||
/// external data must be ready by in order to take part in those slots.
|
||||
uint16_t offset = 1368;
|
||||
enum class Type: uint8_t {
|
||||
/// A slot for reading or writing data on behalf of the CPU or the command engine.
|
||||
External,
|
||||
public:
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
|
||||
/// external data must be ready by in order to take part in those slots.
|
||||
uint16_t offset = 1368;
|
||||
enum class Type: uint8_t {
|
||||
/// A slot for reading or writing data on behalf of the CPU or the command engine.
|
||||
External,
|
||||
|
||||
//
|
||||
// Sprites.
|
||||
//
|
||||
SpriteY,
|
||||
SpriteLocation,
|
||||
SpritePattern,
|
||||
//
|
||||
// Sprites.
|
||||
//
|
||||
SpriteY,
|
||||
SpriteLocation,
|
||||
SpritePattern,
|
||||
|
||||
//
|
||||
// Backgrounds.
|
||||
//
|
||||
Name,
|
||||
Colour,
|
||||
Pattern,
|
||||
} type = Type::External;
|
||||
uint8_t id = 0;
|
||||
//
|
||||
// Backgrounds.
|
||||
//
|
||||
Name,
|
||||
Colour,
|
||||
Pattern,
|
||||
} type = Type::External;
|
||||
uint8_t id = 0;
|
||||
|
||||
constexpr Event(Type type, uint8_t id = 0) noexcept :
|
||||
type(type),
|
||||
id(id) {}
|
||||
constexpr Event(Type type, uint8_t id = 0) noexcept :
|
||||
type(type),
|
||||
id(id) {}
|
||||
|
||||
constexpr Event() noexcept = default;
|
||||
};
|
||||
constexpr Event() noexcept = default;
|
||||
};
|
||||
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
|
||||
protected:
|
||||
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event_type = GeneratorT::event(c);
|
||||
size += event_type.has_value();
|
||||
}
|
||||
return size + 1;
|
||||
protected:
|
||||
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event_type = GeneratorT::event(c);
|
||||
size += event_type.has_value();
|
||||
}
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
/// @return An array of all events generated by @c GeneratorT in line order.
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
size_t index = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
|
||||
if(!event) {
|
||||
continue;
|
||||
}
|
||||
result[index] = *event;
|
||||
result[index].offset = uint16_t(c);
|
||||
++index;
|
||||
/// @return An array of all events generated by @c GeneratorT in line order.
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
size_t index = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
|
||||
if(!event) {
|
||||
continue;
|
||||
}
|
||||
result[index] = Event();
|
||||
return result;
|
||||
result[index] = *event;
|
||||
result[index].offset = uint16_t(c);
|
||||
++index;
|
||||
}
|
||||
result[index] = Event();
|
||||
return result;
|
||||
}
|
||||
|
||||
struct StandardGenerators {
|
||||
static constexpr std::optional<Event> external_every_eight(int index) {
|
||||
if(index & 7) return std::nullopt;
|
||||
struct StandardGenerators {
|
||||
static constexpr std::optional<Event> external_every_eight(int index) {
|
||||
if(index & 7) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct RefreshGenerator {
|
||||
static constexpr std::optional<Event> event(const int grauw_index) {
|
||||
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index < 126) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 0);
|
||||
}
|
||||
|
||||
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
|
||||
// CPU/CMD and the final being refresh.
|
||||
if(grauw_index >= 164 && grauw_index < 1234) {
|
||||
const int offset = grauw_index - 164;
|
||||
if(offset & 7) return std::nullopt;
|
||||
if(((offset >> 3) & 15) == 15) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct RefreshGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index < 126) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 0);
|
||||
}
|
||||
|
||||
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
|
||||
// CPU/CMD and the final being refresh.
|
||||
if(grauw_index >= 164 && grauw_index < 1234) {
|
||||
const int offset = grauw_index - 164;
|
||||
if(offset & 7) return std::nullopt;
|
||||
if(((offset >> 3) & 15) == 15) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index >= 1268 && grauw_index < 1330) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1268);
|
||||
}
|
||||
|
||||
// A CPU/CMD at 1334.
|
||||
if(grauw_index == 1334) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
|
||||
if(grauw_index >= 1344 && grauw_index < 1366) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1344);
|
||||
}
|
||||
|
||||
// Otherwise: nothing.
|
||||
return std::nullopt;
|
||||
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index >= 1268 && grauw_index < 1330) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1268);
|
||||
}
|
||||
};
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
if(!include_sprites) {
|
||||
// Various standard zones of one-every-eight external slots.
|
||||
if(grauw_index < 124) {
|
||||
return StandardGenerators::external_every_eight(grauw_index + 2);
|
||||
}
|
||||
if(grauw_index > 1266) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1266);
|
||||
}
|
||||
} else {
|
||||
// This records collection points for all data for selected sprites.
|
||||
// There's only four of them (each site covering two sprites),
|
||||
// so it's clearer just to be explicit.
|
||||
//
|
||||
// There's also a corresponding number of extra external slots to spell out.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1238: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1302: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 2: return Event(Event::Type::SpriteLocation, 4);
|
||||
case 66: return Event(Event::Type::SpriteLocation, 6);
|
||||
case 1270: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1338: return Event(Event::Type::SpritePattern, 2);
|
||||
case 34: return Event(Event::Type::SpritePattern, 4);
|
||||
case 98: return Event(Event::Type::SpritePattern, 6);
|
||||
case 1264: case 1330: case 28: case 92:
|
||||
return Event::Type::External;
|
||||
}
|
||||
}
|
||||
|
||||
if(grauw_index >= 162 && grauw_index < 176) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 162);
|
||||
}
|
||||
|
||||
// Everywhere else the pattern is:
|
||||
//
|
||||
// external or sprite y, external, data block
|
||||
//
|
||||
// Subject to caveats:
|
||||
//
|
||||
// 1) the first data block is just a dummy fetch with no side effects,
|
||||
// so this emulator declines to record it; and
|
||||
// 2) every fourth block, the second external is actually a refresh.
|
||||
//
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
|
||||
switch(sub_block) {
|
||||
default: return std::nullopt;
|
||||
case 0:
|
||||
if(include_sprites) {
|
||||
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
|
||||
if(block < 32) {
|
||||
return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
}
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if(block) {
|
||||
return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
// A CPU/CMD at 1334.
|
||||
if(grauw_index == 1334) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Capture various one-in-eight zones.
|
||||
if(grauw_index < 72) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 2);
|
||||
}
|
||||
if(grauw_index >= 166 && grauw_index < 228) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
if(grauw_index >= 1206 && grauw_index < 1332) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1206);
|
||||
}
|
||||
if(grauw_index == 1336) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
if(grauw_index >= 1346) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1346);
|
||||
}
|
||||
|
||||
// Elsewhere...
|
||||
if(grauw_index >= 246) {
|
||||
const int offset = grauw_index - 246;
|
||||
const int block = offset / 48;
|
||||
const int sub_block = offset % 48;
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
case 0: return Event(Event::Type::Name, uint8_t(block));
|
||||
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
|
||||
case 24: return Event(Event::Type::Pattern, uint8_t(block));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
|
||||
if(grauw_index >= 1344 && grauw_index < 1366) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1344);
|
||||
}
|
||||
};
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Grab sprite events.
|
||||
// Otherwise: nothing.
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(const int grauw_index) {
|
||||
if(!include_sprites) {
|
||||
// Various standard zones of one-every-eight external slots.
|
||||
if(grauw_index < 124) {
|
||||
return StandardGenerators::external_every_eight(grauw_index + 2);
|
||||
}
|
||||
if(grauw_index > 1266) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1266);
|
||||
}
|
||||
} else {
|
||||
// This records collection points for all data for selected sprites.
|
||||
// There's only four of them (each site covering two sprites),
|
||||
// so it's clearer just to be explicit.
|
||||
//
|
||||
// There's also a corresponding number of extra external slots to spell out.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1242: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1306: return Event(Event::Type::SpriteLocation, 1);
|
||||
case 6: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 70: return Event(Event::Type::SpriteLocation, 3);
|
||||
case 1274: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1342: return Event(Event::Type::SpritePattern, 1);
|
||||
case 38: return Event(Event::Type::SpritePattern, 2);
|
||||
case 102: return Event(Event::Type::SpritePattern, 3);
|
||||
case 1268: case 1334: case 32: case 96: return Event::Type::External;
|
||||
case 1238: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1302: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 2: return Event(Event::Type::SpriteLocation, 4);
|
||||
case 66: return Event(Event::Type::SpriteLocation, 6);
|
||||
case 1270: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1338: return Event(Event::Type::SpritePattern, 2);
|
||||
case 34: return Event(Event::Type::SpritePattern, 4);
|
||||
case 98: return Event(Event::Type::SpritePattern, 6);
|
||||
case 1264: case 1330: case 28: case 92:
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
if(grauw_index >= 166 && grauw_index < 180) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
if(grauw_index >= 162 && grauw_index < 176) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 162);
|
||||
}
|
||||
|
||||
// Everywhere else the pattern is:
|
||||
//
|
||||
// external or sprite y, external, data block
|
||||
//
|
||||
// Subject to caveats:
|
||||
//
|
||||
// 1) the first data block is just a dummy fetch with no side effects,
|
||||
// so this emulator declines to record it; and
|
||||
// 2) every fourth block, the second external is actually a refresh.
|
||||
//
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
|
||||
switch(sub_block) {
|
||||
default: return std::nullopt;
|
||||
case 0:
|
||||
if(include_sprites) {
|
||||
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
|
||||
if(block < 32) {
|
||||
return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
}
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if(block) {
|
||||
return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(const int grauw_index) {
|
||||
// Capture various one-in-eight zones.
|
||||
if(grauw_index < 72) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 2);
|
||||
}
|
||||
if(grauw_index >= 166 && grauw_index < 228) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
if(grauw_index >= 1206 && grauw_index < 1332) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1206);
|
||||
}
|
||||
if(grauw_index == 1336) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
if(grauw_index >= 1346) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1346);
|
||||
}
|
||||
|
||||
// Elsewhere...
|
||||
if(grauw_index >= 246) {
|
||||
const int offset = grauw_index - 246;
|
||||
const int block = offset / 48;
|
||||
const int sub_block = offset % 48;
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
case 0: return Event(Event::Type::Name, uint8_t(block));
|
||||
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
|
||||
case 24: return Event(Event::Type::Pattern, uint8_t(block));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(const int grauw_index) {
|
||||
// Grab sprite events.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1242: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1306: return Event(Event::Type::SpriteLocation, 1);
|
||||
case 6: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 70: return Event(Event::Type::SpriteLocation, 3);
|
||||
case 1274: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1342: return Event(Event::Type::SpritePattern, 1);
|
||||
case 38: return Event(Event::Type::SpritePattern, 2);
|
||||
case 102: return Event(Event::Type::SpritePattern, 3);
|
||||
case 1268: case 1334: case 32: case 96: return Event::Type::External;
|
||||
}
|
||||
|
||||
if(grauw_index >= 166 && grauw_index < 180) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct YamahaCommandState {
|
||||
@ -314,7 +314,7 @@ struct YamahaCommandState {
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
void update_command_step(const int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
@ -354,7 +354,10 @@ struct YamahaCommandState {
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public YamahaFetcher, public YamahaCommandState {
|
||||
template <Personality personality>
|
||||
struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>:
|
||||
public YamahaFetcher, public YamahaCommandState
|
||||
{
|
||||
using AddressT = uint32_t;
|
||||
|
||||
// The Yamaha's (optional in real hardware) additional 64kb of expansion RAM.
|
||||
@ -410,7 +413,7 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
|
||||
}
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
void begin_line(const ScreenMode mode, const bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
|
@ -17,13 +17,13 @@ namespace TI::TMS {
|
||||
struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(uint8_t value) {
|
||||
template <int offset, bool high> void set(const uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
||||
template <int offset> void add(int amount) {
|
||||
template <int offset> void add(const int amount) {
|
||||
v[offset] += amount;
|
||||
|
||||
if constexpr (offset == 1) {
|
||||
@ -41,7 +41,7 @@ struct Vector {
|
||||
};
|
||||
|
||||
struct Colour {
|
||||
void set(uint8_t value) {
|
||||
void set(const uint8_t value) {
|
||||
colour = value;
|
||||
colour4bpp = uint8_t((value & 0xf) | (value << 4));
|
||||
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
|
||||
@ -131,7 +131,8 @@ struct Command {
|
||||
/// Current command parameters.
|
||||
CommandContext &context;
|
||||
ModeDescription &mode_description;
|
||||
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
|
||||
Command(CommandContext &context, ModeDescription &mode_description) :
|
||||
context(context), mode_description(mode_description) {}
|
||||
virtual ~Command() = default;
|
||||
|
||||
/// @returns @c true if all output from this command is done; @c false otherwise.
|
||||
@ -142,7 +143,7 @@ struct Command {
|
||||
virtual void advance() = 0;
|
||||
|
||||
protected:
|
||||
template <int axis, bool include_source> void advance_axis(int offset = 1) {
|
||||
template <int axis, bool include_source> void advance_axis(const int offset = 1) {
|
||||
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
if constexpr (include_source) {
|
||||
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
@ -161,58 +162,58 @@ namespace Commands {
|
||||
/// * 88 cycles between every pixel plot;
|
||||
/// * plus an additional 32 cycles if a step along the minor axis is taken.
|
||||
struct Line: public Command {
|
||||
public:
|
||||
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
// context.destination = start position;
|
||||
// context.size.v[0] = long side dots;
|
||||
// context.size.v[1] = short side dots;
|
||||
// context.arguments => direction
|
||||
public:
|
||||
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
// context.destination = start position;
|
||||
// context.size.v[0] = long side dots;
|
||||
// context.size.v[1] = short side dots;
|
||||
// context.arguments => direction
|
||||
|
||||
position_ = context.size.v[1];
|
||||
numerator_ = position_ << 1;
|
||||
denominator_ = context.size.v[0] << 1;
|
||||
position_ = context.size.v[1];
|
||||
numerator_ = position_ << 1;
|
||||
denominator_ = context.size.v[0] << 1;
|
||||
|
||||
cycles = 32;
|
||||
access = AccessType::PlotPoint;
|
||||
cycles = 32;
|
||||
access = AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[0];
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
--context.size.v[0];
|
||||
cycles = 88;
|
||||
|
||||
// b0: 1 => long direction is y;
|
||||
// 0 => long direction is x.
|
||||
//
|
||||
// b2: 1 => x direction is left;
|
||||
// 0 => x direction is right.
|
||||
//
|
||||
// b3: 1 => y direction is up;
|
||||
// 0 => y direction is down.
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<1, false>();
|
||||
} else {
|
||||
advance_axis<0, false>();
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[0];
|
||||
}
|
||||
position_ -= numerator_;
|
||||
if(position_ < 0) {
|
||||
position_ += denominator_;
|
||||
cycles += 32;
|
||||
|
||||
void advance() final {
|
||||
--context.size.v[0];
|
||||
cycles = 88;
|
||||
|
||||
// b0: 1 => long direction is y;
|
||||
// 0 => long direction is x.
|
||||
//
|
||||
// b2: 1 => x direction is left;
|
||||
// 0 => x direction is right.
|
||||
//
|
||||
// b3: 1 => y direction is up;
|
||||
// 0 => y direction is down.
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<1, false>();
|
||||
} else {
|
||||
advance_axis<0, false>();
|
||||
}
|
||||
|
||||
position_ -= numerator_;
|
||||
if(position_ < 0) {
|
||||
position_ += denominator_;
|
||||
cycles += 32;
|
||||
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<0, false>();
|
||||
} else {
|
||||
advance_axis<1, false>();
|
||||
}
|
||||
} else {
|
||||
advance_axis<1, false>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int position_, numerator_, denominator_;
|
||||
private:
|
||||
int position_, numerator_, denominator_;
|
||||
};
|
||||
|
||||
// MARK: - Single pixel manipulation.
|
||||
@ -221,88 +222,90 @@ struct Line: public Command {
|
||||
///
|
||||
/// No timings are documented, so this'll output or input as quickly as possible.
|
||||
template <bool is_read> struct Point: public Command {
|
||||
public:
|
||||
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
cycles = 0; // TODO.
|
||||
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
|
||||
}
|
||||
public:
|
||||
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
cycles = 0; // TODO.
|
||||
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return done_;
|
||||
}
|
||||
bool done() final {
|
||||
return done_;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
done_ = true;
|
||||
}
|
||||
void advance() final {
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool done_ = false;
|
||||
private:
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular base.
|
||||
|
||||
/// Useful base class for anything that does logical work in a rectangle.
|
||||
template <bool logical, bool include_source> struct Rectangle: public Command {
|
||||
public:
|
||||
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
if constexpr (include_source) {
|
||||
start_x_[0] = context.source.v[0];
|
||||
}
|
||||
start_x_[1] = context.destination.v[0];
|
||||
width_ = context.size.v[0];
|
||||
public:
|
||||
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
if constexpr (include_source) {
|
||||
start_x_[0] = context.source.v[0];
|
||||
}
|
||||
start_x_[1] = context.destination.v[0];
|
||||
width_ = context.size.v[0];
|
||||
|
||||
if(!width_) {
|
||||
// Width = 0 => maximal width for this mode.
|
||||
// (aside: it's still unclear to me whether commands are
|
||||
// automatically clipped to the display; I think so but
|
||||
// don't want to spend any time on it until I'm certain)
|
||||
if(!width_) {
|
||||
// Width = 0 => maximal width for this mode.
|
||||
// (aside: it's still unclear to me whether commands are
|
||||
// automatically clipped to the display; I think so but
|
||||
// don't want to spend any time on it until I'm certain)
|
||||
// context.size.v[0] = width_ = mode_description.width;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the current destination and, if @c include_source is @c true also the source;
|
||||
/// @returns @c true if a new row was started; @c false otherwise.
|
||||
bool advance_pixel() {
|
||||
if constexpr (logical) {
|
||||
advance_axis<0, include_source>();
|
||||
--context.size.v[0];
|
||||
|
||||
if(context.size.v[0]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
advance_axis<0, include_source>(mode_description.pixels_per_byte);
|
||||
context.size.v[0] -= mode_description.pixels_per_byte;
|
||||
|
||||
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the current destination and, if @c include_source is @c true also the source;
|
||||
/// @returns @c true if a new row was started; @c false otherwise.
|
||||
bool advance_pixel() {
|
||||
if constexpr (logical) {
|
||||
advance_axis<0, include_source>();
|
||||
--context.size.v[0];
|
||||
|
||||
if(context.size.v[0]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
advance_axis<0, include_source>(mode_description.pixels_per_byte);
|
||||
context.size.v[0] -= mode_description.pixels_per_byte;
|
||||
|
||||
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.size.v[0] = width_;
|
||||
if constexpr (include_source) {
|
||||
context.source.v[0] = start_x_[0];
|
||||
}
|
||||
context.destination.v[0] = start_x_[1];
|
||||
|
||||
advance_axis<1, include_source>();
|
||||
--context.size.v[1];
|
||||
|
||||
return true;
|
||||
context.size.v[0] = width_;
|
||||
if constexpr (include_source) {
|
||||
context.source.v[0] = start_x_[0];
|
||||
}
|
||||
context.destination.v[0] = start_x_[1];
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[1] || !width_;
|
||||
}
|
||||
advance_axis<1, include_source>();
|
||||
--context.size.v[1];
|
||||
|
||||
private:
|
||||
int start_x_[2]{}, width_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[1] || !width_;
|
||||
}
|
||||
|
||||
private:
|
||||
int start_x_[2]{}, width_ = 0;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves to/from CPU.
|
||||
|
||||
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
|
||||
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
|
||||
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) :
|
||||
Rectangle<logical, false>(context, mode_description)
|
||||
{
|
||||
Command::is_cpu_transfer = true;
|
||||
|
||||
// This command is started with the first colour ready to transfer.
|
||||
|
@ -22,7 +22,11 @@ using namespace GI::AY38910;
|
||||
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
|
||||
|
||||
template <bool is_stereo>
|
||||
AY38910SampleSource<is_stereo>::AY38910SampleSource(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
|
||||
AY38910SampleSource<is_stereo>::AY38910SampleSource(
|
||||
Personality personality,
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue)
|
||||
: task_queue_(task_queue)
|
||||
{
|
||||
// Don't use the low bit of the envelope position if this is an AY.
|
||||
envelope_position_mask_ |= personality == Personality::AY38910;
|
||||
|
||||
@ -82,7 +86,7 @@ AY38910SampleSource<is_stereo>::AY38910SampleSource(Personality personality, Con
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_sample_volume_range(std::int16_t range) {
|
||||
void AY38910SampleSource<is_stereo>::set_sample_volume_range(const std::int16_t range) {
|
||||
// Set up volume lookup table; the function below is based on a combination of the graph
|
||||
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
|
||||
// values reported elsewhere.
|
||||
@ -101,7 +105,14 @@ void AY38910SampleSource<is_stereo>::set_sample_volume_range(std::int16_t range)
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
|
||||
void AY38910SampleSource<is_stereo>::set_output_mixing(
|
||||
const float a_left,
|
||||
const float b_left,
|
||||
const float c_left,
|
||||
const float a_right,
|
||||
const float b_right,
|
||||
const float c_right
|
||||
) {
|
||||
a_left_ = uint8_t(a_left * 255.0f);
|
||||
b_left_ = uint8_t(b_left * 255.0f);
|
||||
c_left_ = uint8_t(c_left * 255.0f);
|
||||
@ -223,12 +234,12 @@ bool AY38910SampleSource<is_stereo>::is_zero_level() const {
|
||||
// MARK: - Register manipulation
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::select_register(uint8_t r) {
|
||||
void AY38910SampleSource<is_stereo>::select_register(const uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_register_value(uint8_t value) {
|
||||
void AY38910SampleSource<is_stereo>::set_register_value(const uint8_t value) {
|
||||
// There are only 16 registers.
|
||||
if(selected_register_ > 15) return;
|
||||
|
||||
@ -298,7 +309,7 @@ void AY38910SampleSource<is_stereo>::set_register_value(uint8_t value) {
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_register_value() {
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_register_value() const {
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 0s
|
||||
// when read, conforming to CPC-sourced unit tests.
|
||||
const uint8_t register_masks[16] = {
|
||||
@ -313,27 +324,27 @@ uint8_t AY38910SampleSource<is_stereo>::get_register_value() {
|
||||
// MARK: - Port querying
|
||||
|
||||
template <bool is_stereo>
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_port_output(bool port_b) {
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_port_output(const bool port_b) const {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
// MARK: - Bus handling
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_port_handler(PortHandler *handler) {
|
||||
void AY38910SampleSource<is_stereo>::set_port_handler(PortHandler *const handler) {
|
||||
port_handler_ = handler;
|
||||
set_port_output(true);
|
||||
set_port_output(false);
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_data_input(uint8_t r) {
|
||||
void AY38910SampleSource<is_stereo>::set_data_input(const uint8_t r) {
|
||||
data_input_ = r;
|
||||
update_bus();
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_port_output(bool port_b) {
|
||||
void AY38910SampleSource<is_stereo>::set_port_output(const bool port_b) {
|
||||
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
|
||||
// so that when in the "input" mode, all pins will read normally high". Therefore,
|
||||
// report programmer selection of input mode as creating an output of 0xff.
|
||||
@ -344,7 +355,7 @@ void AY38910SampleSource<is_stereo>::set_port_output(bool port_b) {
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_data_output() {
|
||||
uint8_t AY38910SampleSource<is_stereo>::get_data_output() const {
|
||||
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
|
||||
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
|
||||
// value returned to the CPU when reading it is the and of the output value and any input.
|
||||
@ -361,7 +372,7 @@ uint8_t AY38910SampleSource<is_stereo>::get_data_output() {
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_control_lines(ControlLines control_lines) {
|
||||
void AY38910SampleSource<is_stereo>::set_control_lines(const ControlLines control_lines) {
|
||||
switch(int(control_lines)) {
|
||||
default: control_state_ = Inactive; break;
|
||||
|
||||
@ -377,7 +388,7 @@ void AY38910SampleSource<is_stereo>::set_control_lines(ControlLines control_line
|
||||
}
|
||||
|
||||
template <bool is_stereo>
|
||||
void AY38910SampleSource<is_stereo>::set_reset(bool active) {
|
||||
void AY38910SampleSource<is_stereo>::set_reset(const bool active) {
|
||||
if(active == reset_) return;
|
||||
reset_ = active;
|
||||
|
||||
|
@ -24,23 +24,23 @@ namespace GI::AY38910 {
|
||||
instead use AY38910.get_port_output.
|
||||
*/
|
||||
class PortHandler {
|
||||
public:
|
||||
/*!
|
||||
Requests the current input on an AY port.
|
||||
public:
|
||||
/*!
|
||||
Requests the current input on an AY port.
|
||||
|
||||
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
|
||||
*/
|
||||
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
|
||||
return 0xff;
|
||||
}
|
||||
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
|
||||
*/
|
||||
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current output on an AY port.
|
||||
/*!
|
||||
Sets the current output on an AY port.
|
||||
|
||||
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
|
||||
@param value the value now being output.
|
||||
*/
|
||||
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
|
||||
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
|
||||
@param value the value now being output.
|
||||
*/
|
||||
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -67,110 +67,110 @@ enum class Personality {
|
||||
This AY has an attached mono or stereo mixer.
|
||||
*/
|
||||
template <bool stereo> class AY38910SampleSource {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue<false> &);
|
||||
AY38910SampleSource(const AY38910SampleSource &) = delete;
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue<false> &);
|
||||
AY38910SampleSource(const AY38910SampleSource &) = delete;
|
||||
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
|
||||
/// Gets the value that would appear on the data lines if only the AY is outputting.
|
||||
uint8_t get_data_output();
|
||||
/// Gets the value that would appear on the data lines if only the AY is outputting.
|
||||
uint8_t get_data_output() const;
|
||||
|
||||
/// Sets the current control line state, as a bit field.
|
||||
void set_control_lines(ControlLines control_lines);
|
||||
/// Sets the current control line state, as a bit field.
|
||||
void set_control_lines(ControlLines control_lines);
|
||||
|
||||
/// Strobes the reset line.
|
||||
void reset();
|
||||
/// Strobes the reset line.
|
||||
void reset();
|
||||
|
||||
/// Sets the current value of the reset line.
|
||||
void set_reset(bool reset);
|
||||
/// Sets the current value of the reset line.
|
||||
void set_reset(bool reset);
|
||||
|
||||
/*!
|
||||
Gets the value that would appear on the requested interface port if it were in output mode.
|
||||
@parameter port_b @c true to get the value for Port B, @c false to get the value for Port A.
|
||||
*/
|
||||
uint8_t get_port_output(bool port_b);
|
||||
/*!
|
||||
Gets the value that would appear on the requested interface port if it were in output mode.
|
||||
@parameter port_b @c true to get the value for Port B, @c false to get the value for Port A.
|
||||
*/
|
||||
uint8_t get_port_output(bool port_b) const;
|
||||
|
||||
/*!
|
||||
Sets the port handler, which will receive a call every time the AY either wants to sample
|
||||
input or else declare new output. As a convenience, current port output can be obtained
|
||||
without installing a port handler via get_port_output.
|
||||
*/
|
||||
void set_port_handler(PortHandler *);
|
||||
/*!
|
||||
Sets the port handler, which will receive a call every time the AY either wants to sample
|
||||
input or else declare new output. As a convenience, current port output can be obtained
|
||||
without installing a port handler via get_port_output.
|
||||
*/
|
||||
void set_port_handler(PortHandler *);
|
||||
|
||||
/*!
|
||||
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
|
||||
channels in each of the output channels.
|
||||
/*!
|
||||
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
|
||||
channels in each of the output channels.
|
||||
|
||||
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
|
||||
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
|
||||
|
||||
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
|
||||
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
|
||||
|
||||
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
|
||||
*/
|
||||
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
|
||||
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
|
||||
*/
|
||||
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
|
||||
|
||||
// Sample generation.
|
||||
typename Outputs::Speaker::SampleT<stereo>::type level() const;
|
||||
void advance();
|
||||
bool is_zero_level() const;
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
// Sample generation.
|
||||
typename Outputs::Speaker::SampleT<stereo>::type level() const;
|
||||
void advance();
|
||||
bool is_zero_level() const;
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
||||
bool reset_ = false;
|
||||
bool reset_ = false;
|
||||
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16]{};
|
||||
uint8_t output_registers_[16]{};
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16]{};
|
||||
uint8_t output_registers_[16]{};
|
||||
|
||||
int tone_periods_[3]{};
|
||||
int tone_counters_[3]{};
|
||||
int tone_outputs_[3]{};
|
||||
int tone_periods_[3]{};
|
||||
int tone_counters_[3]{};
|
||||
int tone_outputs_[3]{};
|
||||
|
||||
int noise_period_ = 0;
|
||||
int noise_counter_ = 0;
|
||||
int noise_shift_register_ = 0xffff;
|
||||
int noise_output_ = 0;
|
||||
int noise_period_ = 0;
|
||||
int noise_counter_ = 0;
|
||||
int noise_shift_register_ = 0xffff;
|
||||
int noise_output_ = 0;
|
||||
|
||||
int envelope_period_ = 0;
|
||||
int envelope_divider_ = 0;
|
||||
int envelope_position_ = 0, envelope_position_mask_ = 0;
|
||||
int envelope_shapes_[16][64];
|
||||
int envelope_overflow_masks_[16];
|
||||
int envelope_period_ = 0;
|
||||
int envelope_divider_ = 0;
|
||||
int envelope_position_ = 0, envelope_position_mask_ = 0;
|
||||
int envelope_shapes_[16][64];
|
||||
int envelope_overflow_masks_[16];
|
||||
|
||||
int volumes_[32];
|
||||
int volumes_[32];
|
||||
|
||||
enum ControlState {
|
||||
Inactive,
|
||||
LatchAddress,
|
||||
Read,
|
||||
Write
|
||||
} control_state_;
|
||||
enum ControlState {
|
||||
Inactive,
|
||||
LatchAddress,
|
||||
Read,
|
||||
Write
|
||||
} control_state_;
|
||||
|
||||
void select_register(uint8_t r);
|
||||
void set_register_value(uint8_t value);
|
||||
uint8_t get_register_value();
|
||||
void select_register(uint8_t r);
|
||||
void set_register_value(uint8_t value);
|
||||
uint8_t get_register_value() const;
|
||||
|
||||
uint8_t data_input_, data_output_;
|
||||
uint8_t data_input_, data_output_;
|
||||
|
||||
typename Outputs::Speaker::SampleT<stereo>::type output_volume_;
|
||||
typename Outputs::Speaker::SampleT<stereo>::type output_volume_;
|
||||
|
||||
void update_bus();
|
||||
PortHandler *port_handler_ = nullptr;
|
||||
void set_port_output(bool port_b);
|
||||
void update_bus();
|
||||
PortHandler *port_handler_ = nullptr;
|
||||
void set_port_output(bool port_b);
|
||||
|
||||
void evaluate_output_volume();
|
||||
void evaluate_output_volume();
|
||||
|
||||
// Output mixing control.
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
// Output mixing control.
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
|
||||
friend struct State;
|
||||
friend struct State;
|
||||
};
|
||||
|
||||
/// Defines a default AY to be the sample source with a master divider of 4;
|
||||
@ -193,7 +193,9 @@ template <bool stereo> struct AY38910:
|
||||
*/
|
||||
struct Utility {
|
||||
template <typename AY> static void write(AY &ay, bool is_data_write, uint8_t data) {
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1)));
|
||||
ay.set_control_lines(
|
||||
GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1))
|
||||
);
|
||||
ay.set_data_input(data);
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
}
|
||||
|
@ -20,273 +20,272 @@ namespace Apple::Clock {
|
||||
will also signal interrupts — this is just the storage and time counting.
|
||||
*/
|
||||
class ClockStorage {
|
||||
public:
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
public:
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
|
||||
The caller should also signal an interrupt if applicable.
|
||||
The caller should also signal an interrupt if applicable.
|
||||
*/
|
||||
void update() {
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current [P/B]RAM contents.
|
||||
*/
|
||||
template <typename CollectionT> void set_data(const CollectionT &collection) {
|
||||
set_data(collection.begin(), collection.end());
|
||||
}
|
||||
|
||||
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
|
||||
size_t c = 0;
|
||||
while(begin != end && c < 256) {
|
||||
data_[c] = *begin;
|
||||
++begin;
|
||||
++c;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr uint16_t NoResult = 0x100;
|
||||
static constexpr uint16_t DidComplete = 0x101;
|
||||
uint16_t perform(const uint8_t command) {
|
||||
/*
|
||||
Documented commands:
|
||||
|
||||
z0000001 Seconds register 0 (lowest order byte)
|
||||
z0000101 Seconds register 1
|
||||
z0001001 Seconds register 2
|
||||
z0001101 Seconds register 3
|
||||
00110001 Test register (write only)
|
||||
00110101 Write-protect register (write only)
|
||||
z010aa01 RAM addresses 0x10 - 0x13
|
||||
z1aaaa01 RAM addresses 0x00 – 0x0f
|
||||
|
||||
z0111abc, followed by 0defgh00
|
||||
RAM address abcdefgh
|
||||
|
||||
z = 1 => a read; z = 0 => a write.
|
||||
|
||||
The top bit of the write-protect register enables (0) or disables (1)
|
||||
writes to other locations.
|
||||
|
||||
All the documentation says about the test register is to set the top
|
||||
two bits to 0 for normal operation. Abnormal operation is undefined.
|
||||
*/
|
||||
void update() {
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
switch(phase_) {
|
||||
case Phase::Command:
|
||||
// Decode an address.
|
||||
switch(command & 0x70) {
|
||||
default:
|
||||
if(command & 0x40) {
|
||||
// RAM addresses 0x00 – 0x0f.
|
||||
address_ = (command >> 2) & 0xf;
|
||||
} else return DidComplete; // Unrecognised.
|
||||
break;
|
||||
|
||||
/*!
|
||||
Sets the current [P/B]RAM contents.
|
||||
*/
|
||||
template <typename CollectionT> void set_data(const CollectionT &collection) {
|
||||
set_data(collection.begin(), collection.end());
|
||||
}
|
||||
|
||||
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
|
||||
size_t c = 0;
|
||||
while(begin != end && c < 256) {
|
||||
data_[c] = *begin;
|
||||
++begin;
|
||||
++c;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr uint16_t NoResult = 0x100;
|
||||
static constexpr uint16_t DidComplete = 0x101;
|
||||
uint16_t perform(uint8_t command) {
|
||||
/*
|
||||
Documented commands:
|
||||
|
||||
z0000001 Seconds register 0 (lowest order byte)
|
||||
z0000101 Seconds register 1
|
||||
z0001001 Seconds register 2
|
||||
z0001101 Seconds register 3
|
||||
00110001 Test register (write only)
|
||||
00110101 Write-protect register (write only)
|
||||
z010aa01 RAM addresses 0x10 - 0x13
|
||||
z1aaaa01 RAM addresses 0x00 – 0x0f
|
||||
|
||||
z0111abc, followed by 0defgh00
|
||||
RAM address abcdefgh
|
||||
|
||||
z = 1 => a read; z = 0 => a write.
|
||||
|
||||
The top bit of the write-protect register enables (0) or disables (1)
|
||||
writes to other locations.
|
||||
|
||||
All the documentation says about the test register is to set the top
|
||||
two bits to 0 for normal operation. Abnormal operation is undefined.
|
||||
*/
|
||||
switch(phase_) {
|
||||
case Phase::Command:
|
||||
// Decode an address.
|
||||
switch(command & 0x70) {
|
||||
default:
|
||||
if(command & 0x40) {
|
||||
// RAM addresses 0x00 – 0x0f.
|
||||
address_ = (command >> 2) & 0xf;
|
||||
} else return DidComplete; // Unrecognised.
|
||||
break;
|
||||
|
||||
case 0x00:
|
||||
// A time access.
|
||||
address_ = SecondsBuffer + ((command >> 2)&3);
|
||||
break;
|
||||
case 0x30:
|
||||
// Either a register access or an extended instruction.
|
||||
if(command & 0x08) {
|
||||
address_ = unsigned((command & 0x7) << 5);
|
||||
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
|
||||
return NoResult;
|
||||
} else {
|
||||
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
|
||||
}
|
||||
break;
|
||||
case 0x20:
|
||||
// RAM addresses 0x10 – 0x13.
|
||||
address_ = 0x10 + ((command >> 2) & 0x3);
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is a read, return a result; otherwise prepare to write.
|
||||
if(command & 0x80) {
|
||||
// The two registers are write-only.
|
||||
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
|
||||
return DidComplete;
|
||||
}
|
||||
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
|
||||
}
|
||||
phase_ = Phase::WriteData;
|
||||
return NoResult;
|
||||
|
||||
case Phase::SecondAddressByteRead:
|
||||
case Phase::SecondAddressByteWrite:
|
||||
if(command & 0x83) {
|
||||
return DidComplete;
|
||||
}
|
||||
address_ |= command >> 2;
|
||||
|
||||
if(phase_ == Phase::SecondAddressByteRead) {
|
||||
phase_ = Phase::Command;
|
||||
return data_[address_]; // Only RAM accesses can get this far.
|
||||
} else {
|
||||
phase_ = Phase::WriteData;
|
||||
}
|
||||
return NoResult;
|
||||
|
||||
case Phase::WriteData:
|
||||
// First test: is this to the write-protect register?
|
||||
if(address_ == RegisterWriteProtect) {
|
||||
write_protect_ = command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
if(address_ == RegisterTest) {
|
||||
// No documentation here.
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
// No other writing is permitted if the write protect
|
||||
// register won't allow it.
|
||||
if(!(write_protect_ & 0x80)) {
|
||||
if(address_ >= SecondsBuffer) {
|
||||
seconds_[address_ & 0xff] = command;
|
||||
case 0x00:
|
||||
// A time access.
|
||||
address_ = SecondsBuffer + ((command >> 2)&3);
|
||||
break;
|
||||
case 0x30:
|
||||
// Either a register access or an extended instruction.
|
||||
if(command & 0x08) {
|
||||
address_ = unsigned((command & 0x7) << 5);
|
||||
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
|
||||
return NoResult;
|
||||
} else {
|
||||
data_[address_] = command;
|
||||
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
|
||||
}
|
||||
break;
|
||||
case 0x20:
|
||||
// RAM addresses 0x10 – 0x13.
|
||||
address_ = 0x10 + ((command >> 2) & 0x3);
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is a read, return a result; otherwise prepare to write.
|
||||
if(command & 0x80) {
|
||||
// The two registers are write-only.
|
||||
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
phase_ = Phase::Command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
|
||||
}
|
||||
phase_ = Phase::WriteData;
|
||||
return NoResult;
|
||||
|
||||
case Phase::SecondAddressByteRead:
|
||||
case Phase::SecondAddressByteWrite:
|
||||
if(command & 0x83) {
|
||||
return DidComplete;
|
||||
}
|
||||
address_ |= command >> 2;
|
||||
|
||||
if(phase_ == Phase::SecondAddressByteRead) {
|
||||
phase_ = Phase::Command;
|
||||
return data_[address_]; // Only RAM accesses can get this far.
|
||||
} else {
|
||||
phase_ = Phase::WriteData;
|
||||
}
|
||||
return NoResult;
|
||||
|
||||
case Phase::WriteData:
|
||||
// First test: is this to the write-protect register?
|
||||
if(address_ == RegisterWriteProtect) {
|
||||
write_protect_ = command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
if(address_ == RegisterTest) {
|
||||
// No documentation here.
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
// No other writing is permitted if the write protect
|
||||
// register won't allow it.
|
||||
if(!(write_protect_ & 0x80)) {
|
||||
if(address_ >= SecondsBuffer) {
|
||||
seconds_[address_ & 0xff] = command;
|
||||
} else {
|
||||
data_[address_] = command;
|
||||
}
|
||||
}
|
||||
|
||||
phase_ = Phase::Command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
return NoResult;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 256> data_{0xff};
|
||||
std::array<uint8_t, 4> seconds_{};
|
||||
uint8_t write_protect_ = 0;
|
||||
unsigned int address_ = 0;
|
||||
|
||||
static constexpr int SecondsBuffer = 0x100;
|
||||
static constexpr int RegisterTest = 0x200;
|
||||
static constexpr int RegisterWriteProtect = 0x201;
|
||||
private:
|
||||
std::array<uint8_t, 256> data_{0xff};
|
||||
std::array<uint8_t, 4> seconds_{};
|
||||
uint8_t write_protect_ = 0;
|
||||
unsigned int address_ = 0;
|
||||
|
||||
enum class Phase {
|
||||
Command,
|
||||
SecondAddressByteRead,
|
||||
SecondAddressByteWrite,
|
||||
WriteData
|
||||
};
|
||||
Phase phase_ = Phase::Command;
|
||||
static constexpr int SecondsBuffer = 0x100;
|
||||
static constexpr int RegisterTest = 0x200;
|
||||
static constexpr int RegisterWriteProtect = 0x201;
|
||||
|
||||
enum class Phase {
|
||||
Command,
|
||||
SecondAddressByteRead,
|
||||
SecondAddressByteWrite,
|
||||
WriteData
|
||||
};
|
||||
Phase phase_ = Phase::Command;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the serial interface implemented by the Macintosh.
|
||||
*/
|
||||
class SerialClock: public ClockStorage {
|
||||
public:
|
||||
/*!
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(bool clock, bool data) {
|
||||
// The data line is valid when the clock transitions to level 0.
|
||||
if(clock && !previous_clock_) {
|
||||
// Shift into the command_ register, no matter what.
|
||||
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
||||
result_ <<= 1;
|
||||
public:
|
||||
/*!
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(const bool clock, const bool data) {
|
||||
// The data line is valid when the clock transitions to level 0.
|
||||
if(clock && !previous_clock_) {
|
||||
// Shift into the command_ register, no matter what.
|
||||
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
||||
result_ <<= 1;
|
||||
|
||||
// Increment phase.
|
||||
++phase_;
|
||||
// Increment phase.
|
||||
++phase_;
|
||||
|
||||
// If a whole byte has been collected, push it onwards.
|
||||
if(!(phase_&7)) {
|
||||
// Begin pessimistically.
|
||||
const auto effect = perform(uint8_t(command_));
|
||||
// If a whole byte has been collected, push it onwards.
|
||||
if(!(phase_&7)) {
|
||||
// Begin pessimistically.
|
||||
const auto effect = perform(uint8_t(command_));
|
||||
|
||||
switch(effect) {
|
||||
case ClockStorage::NoResult:
|
||||
break;
|
||||
default:
|
||||
result_ = uint8_t(effect);
|
||||
break;
|
||||
case ClockStorage::DidComplete:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
switch(effect) {
|
||||
case ClockStorage::NoResult:
|
||||
break;
|
||||
default:
|
||||
result_ = uint8_t(effect);
|
||||
break;
|
||||
case ClockStorage::DidComplete:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
previous_clock_ = clock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Reads the current data output level from the clock.
|
||||
*/
|
||||
bool get_data() {
|
||||
return !!(result_ & 0x80);
|
||||
}
|
||||
previous_clock_ = clock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces that a serial command has been aborted.
|
||||
*/
|
||||
void abort() {
|
||||
result_ = 0;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
/*!
|
||||
Reads the current data output level from the clock.
|
||||
*/
|
||||
bool get_data() const {
|
||||
return !!(result_ & 0x80);
|
||||
}
|
||||
|
||||
private:
|
||||
int phase_ = 0;
|
||||
uint16_t command_;
|
||||
uint8_t result_ = 0;
|
||||
/*!
|
||||
Announces that a serial command has been aborted.
|
||||
*/
|
||||
void abort() {
|
||||
result_ = 0;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
|
||||
bool previous_clock_ = false;
|
||||
private:
|
||||
int phase_ = 0;
|
||||
uint16_t command_;
|
||||
uint8_t result_ = 0;
|
||||
|
||||
bool previous_clock_ = false;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the parallel interface implemented by the IIgs.
|
||||
*/
|
||||
class ParallelClock: public ClockStorage {
|
||||
public:
|
||||
void set_control(uint8_t control) {
|
||||
if(!(control&0x80)) return;
|
||||
public:
|
||||
void set_control(const uint8_t control) {
|
||||
if(!(control&0x80)) return;
|
||||
|
||||
if(control & 0x40) {
|
||||
// Read from the RTC.
|
||||
// A no-op for now.
|
||||
} else {
|
||||
// Write to the RTC. Which in this implementation also sets up a future read.
|
||||
const auto result = perform(data_);
|
||||
if(result < 0x100) {
|
||||
data_ = uint8_t(result);
|
||||
}
|
||||
if(control & 0x40) {
|
||||
// Read from the RTC.
|
||||
// A no-op for now.
|
||||
} else {
|
||||
// Write to the RTC. Which in this implementation also sets up a future read.
|
||||
const auto result = perform(data_);
|
||||
if(result < 0x100) {
|
||||
data_ = uint8_t(result);
|
||||
}
|
||||
|
||||
// MAGIC! The transaction took 0 seconds.
|
||||
// TODO: no magic.
|
||||
control_ = control & 0x7f;
|
||||
|
||||
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
|
||||
}
|
||||
|
||||
uint8_t get_control() {
|
||||
return control_;
|
||||
}
|
||||
// MAGIC! The transaction took 0 seconds.
|
||||
// TODO: no magic.
|
||||
control_ = control & 0x7f;
|
||||
|
||||
void set_data(uint8_t data) {
|
||||
data_ = data;
|
||||
}
|
||||
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
|
||||
}
|
||||
|
||||
uint8_t get_data() {
|
||||
return data_;
|
||||
}
|
||||
uint8_t get_control() const {
|
||||
return control_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data_;
|
||||
uint8_t control_;
|
||||
void set_data(const uint8_t data) {
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
uint8_t get_data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data_;
|
||||
uint8_t control_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,12 +15,12 @@ using namespace Audio;
|
||||
Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Toggle::set_sample_volume_range(std::int16_t range) {
|
||||
void Toggle::set_sample_volume_range(const std::int16_t range) {
|
||||
volume_ = range;
|
||||
level_ = level_active_ ? volume_ : 0;
|
||||
}
|
||||
|
||||
void Toggle::set_output(bool enabled) {
|
||||
void Toggle::set_output(const bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.enqueue([this, enabled] {
|
||||
|
@ -17,29 +17,29 @@ namespace Audio {
|
||||
Provides a sample source that can programmatically be set to one of two values.
|
||||
*/
|
||||
class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
|
||||
public:
|
||||
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
public:
|
||||
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
|
||||
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
|
||||
}
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
bool is_zero_level() const {
|
||||
return !level_;
|
||||
}
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
|
||||
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
|
||||
}
|
||||
void set_sample_volume_range(const std::int16_t range);
|
||||
bool is_zero_level() const {
|
||||
return !level_;
|
||||
}
|
||||
|
||||
void set_output(bool enabled);
|
||||
bool get_output() const;
|
||||
void set_output(bool enabled);
|
||||
bool get_output() const;
|
||||
|
||||
private:
|
||||
// Accessed on the calling thread.
|
||||
bool is_enabled_ = false;
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
private:
|
||||
// Accessed on the calling thread.
|
||||
bool is_enabled_ = false;
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
// Accessed on the audio thread.
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
bool level_active_ = false;
|
||||
// Accessed on the audio thread.
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
bool level_active_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace {
|
||||
const uint8_t input_flux = 0x1;
|
||||
}
|
||||
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
DiskII::DiskII(const int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{
|
||||
@ -38,7 +38,7 @@ DiskII::DiskII(int clock_rate) :
|
||||
drives_[active_drive_].set_event_delegate(this);
|
||||
}
|
||||
|
||||
void DiskII::set_control(Control control, bool on) {
|
||||
void DiskII::set_control(const Control control, const bool on) {
|
||||
int previous_stepper_mask = stepper_mask_;
|
||||
switch(control) {
|
||||
case Control::P0: stepper_mask_ = (stepper_mask_ & 0xe) | (on ? 0x1 : 0x0); break;
|
||||
@ -76,7 +76,7 @@ void DiskII::set_control(Control control, bool on) {
|
||||
}
|
||||
}
|
||||
|
||||
void DiskII::select_drive(int drive) {
|
||||
void DiskII::select_drive(const int drive) {
|
||||
if((drive&1) == active_drive_) return;
|
||||
|
||||
drives_[active_drive_].set_event_delegate(this);
|
||||
@ -152,7 +152,7 @@ void DiskII::run_for(const Cycles cycles) {
|
||||
}
|
||||
|
||||
void DiskII::decide_clocking_preference() {
|
||||
ClockingHint::Preference prior_preference = clocking_preference_;
|
||||
const ClockingHint::Preference prior_preference = clocking_preference_;
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
@ -183,7 +183,7 @@ void DiskII::decide_clocking_preference() {
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool DiskII::is_write_protected() {
|
||||
bool DiskII::is_write_protected() const {
|
||||
return (stepper_mask_ & 2) || drives_[active_drive_].get_is_read_only();
|
||||
}
|
||||
|
||||
|
@ -29,99 +29,99 @@ class DiskII :
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
DiskII(int clock_rate);
|
||||
public:
|
||||
DiskII(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void set_data_input(uint8_t input);
|
||||
/// Sets the current external value of the data bus.
|
||||
void set_data_input(uint8_t input);
|
||||
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
|
||||
@returns The 8-bit value loaded to the data bus by the DiskII if any;
|
||||
@c DidNotLoad otherwise.
|
||||
*/
|
||||
int read_address(int address);
|
||||
@returns The 8-bit value loaded to the data bus by the DiskII if any;
|
||||
@c DidNotLoad otherwise.
|
||||
*/
|
||||
int read_address(int address);
|
||||
|
||||
/*!
|
||||
The value returned by @c read_address if accessing that address
|
||||
didn't cause the disk II to place anything onto the bus.
|
||||
*/
|
||||
static constexpr int DidNotLoad = -1;
|
||||
/*!
|
||||
The value returned by @c read_address if accessing that address
|
||||
didn't cause the disk II to place anything onto the bus.
|
||||
*/
|
||||
static constexpr int DidNotLoad = -1;
|
||||
|
||||
/// Advances the controller by @c cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
/// Advances the controller by @c cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/*!
|
||||
Supplies the image of the state machine (i.e. P6) ROM,
|
||||
which dictates how the Disk II will respond to input.
|
||||
/*!
|
||||
Supplies the image of the state machine (i.e. P6) ROM,
|
||||
which dictates how the Disk II will respond to input.
|
||||
|
||||
To reduce processing costs, some assumptions are made by
|
||||
the implementation as to the content of this ROM.
|
||||
Including:
|
||||
To reduce processing costs, some assumptions are made by
|
||||
the implementation as to the content of this ROM.
|
||||
Including:
|
||||
|
||||
* If Q6 is set and Q7 is reset, the controller is testing
|
||||
for write protect. If and when the shift register has
|
||||
become full with the state of the write protect value,
|
||||
no further processing is required.
|
||||
* If Q6 is set and Q7 is reset, the controller is testing
|
||||
for write protect. If and when the shift register has
|
||||
become full with the state of the write protect value,
|
||||
no further processing is required.
|
||||
|
||||
* If both Q6 and Q7 are reset, the drive motor is disabled,
|
||||
and the shift register is all zeroes, no further processing
|
||||
is required.
|
||||
*/
|
||||
void set_state_machine(const std::vector<uint8_t> &);
|
||||
* If both Q6 and Q7 are reset, the drive motor is disabled,
|
||||
and the shift register is all zeroes, no further processing
|
||||
is required.
|
||||
*/
|
||||
void set_state_machine(const std::vector<uint8_t> &);
|
||||
|
||||
/// Inserts @c disk into the drive @c drive.
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
/// Inserts @c disk into the drive @c drive.
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
// As per Sleeper.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
// As per Sleeper.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// Returns the Storage::Disk::Drive in use for drive @c index.
|
||||
// *NOT FOR HARDWARE EMULATION USAGE*.
|
||||
Storage::Disk::Drive &get_drive(int index);
|
||||
// Returns the Storage::Disk::Drive in use for drive @c index.
|
||||
// *NOT FOR HARDWARE EMULATION USAGE*.
|
||||
Storage::Disk::Drive &get_drive(int index);
|
||||
|
||||
private:
|
||||
enum class Control {
|
||||
P0, P1, P2, P3,
|
||||
Motor,
|
||||
};
|
||||
enum class Mode {
|
||||
Read, Write
|
||||
};
|
||||
void set_control(Control control, bool on);
|
||||
void set_mode(Mode mode);
|
||||
void select_drive(int drive);
|
||||
private:
|
||||
enum class Control {
|
||||
P0, P1, P2, P3,
|
||||
Motor,
|
||||
};
|
||||
enum class Mode {
|
||||
Read, Write
|
||||
};
|
||||
void set_control(Control control, bool on);
|
||||
void set_mode(Mode mode);
|
||||
void select_drive(int drive);
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Drive::Event &event) final;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Drive::Event &event) final;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
|
||||
|
||||
const Cycles::IntType clock_rate_ = 0;
|
||||
const Cycles::IntType clock_rate_ = 0;
|
||||
|
||||
uint8_t state_ = 0;
|
||||
uint8_t inputs_ = 0;
|
||||
uint8_t shift_register_ = 0;
|
||||
uint8_t state_ = 0;
|
||||
uint8_t inputs_ = 0;
|
||||
uint8_t shift_register_ = 0;
|
||||
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
Cycles::IntType motor_off_time_ = -1;
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
Cycles::IntType motor_off_time_ = -1;
|
||||
|
||||
bool is_write_protected();
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
Storage::Disk::Drive drives_[2];
|
||||
bool drive_is_sleeping_[2];
|
||||
int active_drive_ = 0;
|
||||
bool motor_is_enabled_ = false;
|
||||
bool is_write_protected() const;
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
Storage::Disk::Drive drives_[2];
|
||||
bool drive_is_sleeping_[2];
|
||||
int active_drive_ = 0;
|
||||
bool motor_is_enabled_ = false;
|
||||
|
||||
void decide_clocking_preference();
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
void decide_clocking_preference();
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
uint8_t data_input_ = 0;
|
||||
int flux_duration_ = 0;
|
||||
uint8_t data_input_ = 0;
|
||||
int flux_duration_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -10,16 +10,16 @@
|
||||
|
||||
using namespace Apple::Disk;
|
||||
|
||||
DiskIIDrive::DiskIIDrive(int input_clock_rate) :
|
||||
DiskIIDrive::DiskIIDrive(const int input_clock_rate) :
|
||||
IWMDrive(input_clock_rate, 1) {
|
||||
Drive::set_rotation_speed(300.0f);
|
||||
}
|
||||
|
||||
void DiskIIDrive::set_enabled(bool enabled) {
|
||||
void DiskIIDrive::set_enabled(const bool enabled) {
|
||||
set_motor_on(enabled);
|
||||
}
|
||||
|
||||
void DiskIIDrive::set_control_lines(int lines) {
|
||||
void DiskIIDrive::set_control_lines(const int lines) {
|
||||
// If the stepper magnet selections have changed, and any is on, see how
|
||||
// that moves the head.
|
||||
if(lines ^ stepper_mask_ && lines) {
|
||||
|
@ -13,16 +13,16 @@
|
||||
namespace Apple::Disk {
|
||||
|
||||
class DiskIIDrive: public IWMDrive {
|
||||
public:
|
||||
DiskIIDrive(int input_clock_rate);
|
||||
public:
|
||||
DiskIIDrive(int input_clock_rate);
|
||||
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ namespace {
|
||||
constexpr int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
|
||||
constexpr int Q6 = 1 << 6;
|
||||
constexpr int Q7 = 1 << 7;
|
||||
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
|
||||
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a
|
||||
confusingly-similar name to SELECT but a distinct purpose. */
|
||||
|
||||
Log::Logger<Log::Source::IWM> logger;
|
||||
}
|
||||
@ -31,7 +32,7 @@ IWM::IWM(int clock_rate) :
|
||||
|
||||
// MARK: - Bus accessors
|
||||
|
||||
uint8_t IWM::read(int address) {
|
||||
uint8_t IWM::read(const int address) {
|
||||
access(address);
|
||||
|
||||
// Per Inside Macintosh:
|
||||
@ -103,7 +104,7 @@ uint8_t IWM::read(int address) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void IWM::write(int address, uint8_t input) {
|
||||
void IWM::write(const int address, const uint8_t input) {
|
||||
access(address);
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
@ -205,7 +206,7 @@ void IWM::access(int address) {
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_select(bool enabled) {
|
||||
void IWM::set_select(const bool enabled) {
|
||||
// Store SEL as an extra state bit.
|
||||
if(enabled) state_ |= SEL;
|
||||
else state_ &= ~SEL;
|
||||
@ -367,7 +368,7 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::propose_shift(uint8_t bit) {
|
||||
void IWM::propose_shift(const uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// logger.info().append("Shifting at %d", cycles_since_shift_.as<int>());
|
||||
@ -397,7 +398,7 @@ void IWM::propose_shift(uint8_t bit) {
|
||||
cycles_since_shift_ -= bit_length_;
|
||||
}
|
||||
|
||||
void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
void IWM::set_drive(const int slot, IWMDrive *const drive) {
|
||||
drives_[slot] = drive;
|
||||
if(drive) {
|
||||
drive->set_event_delegate(this);
|
||||
@ -407,7 +408,10 @@ void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
|
||||
void IWM::set_component_prefers_clocking(
|
||||
ClockingHint::Source *const component,
|
||||
const ClockingHint::Preference clocking
|
||||
) {
|
||||
const bool is_rotating = clocking != ClockingHint::Preference::None;
|
||||
|
||||
if(drives_[0] && component == static_cast<ClockingHint::Source *>(drives_[0])) {
|
||||
@ -417,7 +421,7 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_activity_observer(Activity::Observer *observer) {
|
||||
void IWM::set_activity_observer(Activity::Observer *const observer) {
|
||||
if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
|
||||
if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
|
||||
}
|
||||
|
@ -45,77 +45,77 @@ struct IWMDrive: public Storage::Disk::Drive {
|
||||
class IWM:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
IWM(int clock_rate);
|
||||
public:
|
||||
IWM(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void write(int address, uint8_t value);
|
||||
/// Sets the current external value of the data bus.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
|
||||
@returns The 8-bit value loaded to the data bus by the IWM.
|
||||
*/
|
||||
uint8_t read(int address);
|
||||
@returns The 8-bit value loaded to the data bus by the IWM.
|
||||
*/
|
||||
uint8_t read(int address);
|
||||
|
||||
/*!
|
||||
Sets the current input of the IWM's SEL line.
|
||||
*/
|
||||
void set_select(bool enabled);
|
||||
/*!
|
||||
Sets the current input of the IWM's SEL line.
|
||||
*/
|
||||
void set_select(bool);
|
||||
|
||||
/// Advances the controller by @c cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
/// Advances the controller.
|
||||
void run_for(const Cycles);
|
||||
|
||||
/// Connects a drive to the IWM.
|
||||
void set_drive(int slot, IWMDrive *drive);
|
||||
/// Connects a drive to the IWM.
|
||||
void set_drive(int slot, IWMDrive *);
|
||||
|
||||
/// Registers the currently-connected drives as @c Activity::Sources ;
|
||||
/// the first will be declared 'Internal', the second 'External'.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
/// Registers the currently-connected drives as @c Activity::Sources ;
|
||||
/// the first will be declared 'Internal', the second 'External'.
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &event) final;
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &) final;
|
||||
|
||||
const int clock_rate_;
|
||||
const int clock_rate_;
|
||||
|
||||
uint8_t data_register_ = 0;
|
||||
uint8_t mode_ = 0;
|
||||
// These related to functionality not-yet implemented.
|
||||
// bool read_write_ready_ = true;
|
||||
// bool write_overran_ = false;
|
||||
uint8_t data_register_ = 0;
|
||||
uint8_t mode_ = 0;
|
||||
// These related to functionality not-yet implemented.
|
||||
// bool read_write_ready_ = true;
|
||||
// bool write_overran_ = false;
|
||||
|
||||
int state_ = 0;
|
||||
int state_ = 0;
|
||||
|
||||
int active_drive_ = 0;
|
||||
IWMDrive *drives_[2] = {nullptr, nullptr};
|
||||
bool drive_is_rotating_[2] = {false, false};
|
||||
int active_drive_ = 0;
|
||||
IWMDrive *drives_[2] = {nullptr, nullptr};
|
||||
bool drive_is_rotating_[2] = {false, false};
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
|
||||
|
||||
Cycles cycles_until_disable_;
|
||||
uint8_t write_handshake_ = 0x80;
|
||||
Cycles cycles_until_disable_;
|
||||
uint8_t write_handshake_ = 0x80;
|
||||
|
||||
void access(int address);
|
||||
void access(int address);
|
||||
|
||||
uint8_t shift_register_ = 0;
|
||||
uint8_t next_output_ = 0;
|
||||
int output_bits_remaining_ = 0;
|
||||
uint8_t shift_register_ = 0;
|
||||
uint8_t next_output_ = 0;
|
||||
int output_bits_remaining_ = 0;
|
||||
|
||||
void propose_shift(uint8_t bit);
|
||||
Cycles cycles_since_shift_;
|
||||
Cycles bit_length_ = Cycles(16);
|
||||
void propose_shift(uint8_t bit);
|
||||
Cycles cycles_since_shift_;
|
||||
Cycles bit_length_ = Cycles(16);
|
||||
|
||||
void push_drive_state();
|
||||
void push_drive_state();
|
||||
|
||||
enum class ShiftMode {
|
||||
Reading,
|
||||
Writing,
|
||||
CheckingWriteProtect
|
||||
} shift_mode_;
|
||||
enum class ShiftMode {
|
||||
Reading,
|
||||
Writing,
|
||||
CheckingWriteProtect
|
||||
} shift_mode_;
|
||||
|
||||
uint8_t sense();
|
||||
void select_shift_mode();
|
||||
uint8_t sense();
|
||||
void select_shift_mode();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
|
||||
DoubleDensityDrive::DoubleDensityDrive(const int input_clock_rate, const bool is_800k) :
|
||||
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
|
||||
is_800k_(is_800k) {
|
||||
// Start with a valid rotation speed.
|
||||
@ -29,7 +29,7 @@ DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
|
||||
|
||||
// MARK: - Speed Selection
|
||||
|
||||
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
|
||||
void DoubleDensityDrive::did_step(const Storage::Disk::HeadPosition to_position) {
|
||||
// The 800kb drive automatically selects rotation speed as a function of
|
||||
// head position; the 400kb drive doesn't do so.
|
||||
if(is_800k_) {
|
||||
@ -60,7 +60,7 @@ void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
|
||||
void DoubleDensityDrive::set_rotation_speed(const float revolutions_per_minute) {
|
||||
if(!is_800k_) {
|
||||
// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
|
||||
// to sanity.
|
||||
@ -70,12 +70,12 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
|
||||
|
||||
// MARK: - Control input/output.
|
||||
|
||||
void DoubleDensityDrive::set_enabled(bool enabled) {
|
||||
void DoubleDensityDrive::set_enabled(const bool enabled) {
|
||||
// Disabling a drive also stops its motor.
|
||||
if(!enabled) set_motor_on(false);
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_control_lines(int lines) {
|
||||
void DoubleDensityDrive::set_control_lines(const int lines) {
|
||||
const auto old_state = control_state_;
|
||||
control_state_ = lines;
|
||||
|
||||
|
@ -13,36 +13,36 @@
|
||||
namespace Apple::Macintosh {
|
||||
|
||||
class DoubleDensityDrive: public IWMDrive {
|
||||
public:
|
||||
DoubleDensityDrive(int input_clock_rate, bool is_800k);
|
||||
public:
|
||||
DoubleDensityDrive(int input_clock_rate, bool is_800k);
|
||||
|
||||
/*!
|
||||
@returns @c true if this is an 800kb drive; @c false otherwise.
|
||||
*/
|
||||
bool is_800k() const {
|
||||
return is_800k_;
|
||||
}
|
||||
/*!
|
||||
@returns @c true if this is an 800kb drive; @c false otherwise.
|
||||
*/
|
||||
bool is_800k() const {
|
||||
return is_800k_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current rotation speed of this drive only if it is a 400kb drive.
|
||||
800kb drives select their own rotation speed based on head position,
|
||||
and ignore this input.
|
||||
*/
|
||||
void set_rotation_speed(float revolutions_per_minute);
|
||||
/*!
|
||||
Sets the current rotation speed of this drive only if it is a 400kb drive.
|
||||
800kb drives select their own rotation speed based on head position,
|
||||
and ignore this input.
|
||||
*/
|
||||
void set_rotation_speed(float revolutions_per_minute);
|
||||
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) final;
|
||||
void did_set_disk(bool) final;
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) final;
|
||||
void did_set_disk(bool) final;
|
||||
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
int control_state_ = 0;
|
||||
int step_direction_ = 1;
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
int control_state_ = 0;
|
||||
int step_direction_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user