mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Merge pull request #81 from TomHarte/Underscores
Switches hopefully all C++ classes to postfix underscores
This commit is contained in:
commit
875e6619cc
@ -57,64 +57,64 @@ template <class T> class MOS6522 {
|
||||
switch(address)
|
||||
{
|
||||
case 0x0:
|
||||
_registers.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, _registers.data_direction[1]); // TODO: handshake
|
||||
registers_.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((_registers.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
_registers.output[0] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::A, value, _registers.data_direction[0]); // TODO: handshake
|
||||
registers_.output[0] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((_registers.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
// // No handshake, so write directly
|
||||
// _registers.output[0] = value;
|
||||
// registers_.output[0] = value;
|
||||
// static_cast<T *>(this)->set_port_output(0, value);
|
||||
// break;
|
||||
|
||||
case 0x2:
|
||||
_registers.data_direction[1] = value;
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
_registers.data_direction[0] = value;
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: _registers.timer_latch[0] = (_registers.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
_registers.timer_latch[0] = (_registers.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||
_registers.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05)
|
||||
{
|
||||
_registers.next_timer[0] = _registers.timer_latch[0];
|
||||
_timer_is_running[0] = true;
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: _registers.timer_latch[1] = value; break;
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
_registers.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
_registers.next_timer[1] = _registers.timer_latch[1] | (uint16_t)(value << 8);
|
||||
_timer_is_running[1] = true;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: _registers.shift = value; break;
|
||||
case 0xa: registers_.shift = value; break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
_registers.auxiliary_control = value;
|
||||
registers_.auxiliary_control = value;
|
||||
break;
|
||||
case 0xc:
|
||||
// printf("Peripheral control %02x\n", value);
|
||||
_registers.peripheral_control = value;
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08)
|
||||
@ -139,14 +139,14 @@ template <class T> class MOS6522 {
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
_registers.interrupt_flags &= ~value;
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
if(value&0x80)
|
||||
_registers.interrupt_enable |= value;
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
_registers.interrupt_enable &= ~value;
|
||||
registers_.interrupt_enable &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
@ -160,41 +160,41 @@ template <class T> class MOS6522 {
|
||||
switch(address)
|
||||
{
|
||||
case 0x0:
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, _registers.data_direction[1], _registers.output[1]);
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1:
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, _registers.data_direction[0], _registers.output[0]);
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
|
||||
case 0x2: return _registers.data_direction[1];
|
||||
case 0x3: return _registers.data_direction[0];
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
_registers.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return _registers.timer[0] & 0x00ff;
|
||||
case 0x5: return _registers.timer[0] >> 8;
|
||||
case 0x6: return _registers.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return _registers.timer_latch[0] >> 8;
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
_registers.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return _registers.timer[1] & 0x00ff;
|
||||
case 0x9: return _registers.timer[1] >> 8;
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return _registers.shift;
|
||||
case 0xa: return registers_.shift;
|
||||
|
||||
case 0xb: return _registers.auxiliary_control;
|
||||
case 0xc: return _registers.peripheral_control;
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
|
||||
case 0xd: return _registers.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return _registers.interrupt_enable | 0x80;
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
@ -205,65 +205,65 @@ template <class T> class MOS6522 {
|
||||
switch(line)
|
||||
{
|
||||
case Line::One:
|
||||
if( value != _control_inputs[port].line_one &&
|
||||
value == !!(_registers.peripheral_control & (port ? 0x10 : 0x01))
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
)
|
||||
{
|
||||
_registers.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
_control_inputs[port].line_one = value;
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != _control_inputs[port].line_two && // i.e. value has changed ...
|
||||
!(_registers.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(_registers.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
)
|
||||
{
|
||||
_registers.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
_control_inputs[port].line_two = value;
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define phase2() \
|
||||
_registers.last_timer[0] = _registers.timer[0];\
|
||||
_registers.last_timer[1] = _registers.timer[1];\
|
||||
registers_.last_timer[0] = registers_.timer[0];\
|
||||
registers_.last_timer[1] = registers_.timer[1];\
|
||||
\
|
||||
if(_registers.timer_needs_reload)\
|
||||
if(registers_.timer_needs_reload)\
|
||||
{\
|
||||
_registers.timer_needs_reload = false;\
|
||||
_registers.timer[0] = _registers.timer_latch[0];\
|
||||
registers_.timer_needs_reload = false;\
|
||||
registers_.timer[0] = registers_.timer_latch[0];\
|
||||
}\
|
||||
else\
|
||||
_registers.timer[0] --;\
|
||||
registers_.timer[0] --;\
|
||||
\
|
||||
_registers.timer[1] --; \
|
||||
if(_registers.next_timer[0] >= 0) { _registers.timer[0] = (uint16_t)_registers.next_timer[0]; _registers.next_timer[0] = -1; }\
|
||||
if(_registers.next_timer[1] >= 0) { _registers.timer[1] = (uint16_t)_registers.next_timer[1]; _registers.next_timer[1] = -1; }\
|
||||
registers_.timer[1] --; \
|
||||
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
|
||||
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
#define phase1() \
|
||||
if((_registers.timer[1] == 0xffff) && !_registers.last_timer[1] && _timer_is_running[1])\
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1])\
|
||||
{\
|
||||
_timer_is_running[1] = false;\
|
||||
_registers.interrupt_flags |= InterruptFlag::Timer2;\
|
||||
timer_is_running_[1] = false;\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
||||
reevaluate_interrupts();\
|
||||
}\
|
||||
\
|
||||
if((_registers.timer[0] == 0xffff) && !_registers.last_timer[0] && _timer_is_running[0])\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0])\
|
||||
{\
|
||||
_registers.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
reevaluate_interrupts();\
|
||||
\
|
||||
if(_registers.auxiliary_control&0x40)\
|
||||
_registers.timer_needs_reload = true;\
|
||||
if(registers_.auxiliary_control&0x40)\
|
||||
registers_.timer_needs_reload = true;\
|
||||
else\
|
||||
_timer_is_running[0] = false;\
|
||||
timer_is_running_[0] = false;\
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -281,7 +281,7 @@ template <class T> class MOS6522 {
|
||||
*/
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(_is_phase2)
|
||||
if(is_phase2_)
|
||||
{
|
||||
phase2();
|
||||
number_of_cycles--;
|
||||
@ -297,11 +297,11 @@ template <class T> class MOS6522 {
|
||||
if(number_of_cycles)
|
||||
{
|
||||
phase1();
|
||||
_is_phase2 = true;
|
||||
is_phase2_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_is_phase2 = false;
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,14 +326,14 @@ template <class T> class MOS6522 {
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
inline bool get_interrupt_line()
|
||||
{
|
||||
uint8_t interrupt_status = _registers.interrupt_flags & _registers.interrupt_enable & 0x7f;
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
|
||||
MOS6522() :
|
||||
_timer_is_running{false, false},
|
||||
_last_posted_interrupt_status(false),
|
||||
_is_phase2(false)
|
||||
timer_is_running_{false, false},
|
||||
last_posted_interrupt_status_(false),
|
||||
is_phase2_(false)
|
||||
{}
|
||||
|
||||
private:
|
||||
@ -351,16 +351,16 @@ template <class T> class MOS6522 {
|
||||
}
|
||||
|
||||
// Phase toggle
|
||||
bool _is_phase2;
|
||||
bool is_phase2_;
|
||||
|
||||
// Delegate and communications
|
||||
bool _last_posted_interrupt_status;
|
||||
bool last_posted_interrupt_status_;
|
||||
inline void reevaluate_interrupts()
|
||||
{
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != _last_posted_interrupt_status)
|
||||
if(new_interrupt_status != last_posted_interrupt_status_)
|
||||
{
|
||||
_last_posted_interrupt_status = new_interrupt_status;
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
@ -382,15 +382,15 @@ template <class T> class MOS6522 {
|
||||
interrupt_flags(0), interrupt_enable(0),
|
||||
last_timer{0, 0}, timer_needs_reload(false),
|
||||
next_timer{-1, -1} {}
|
||||
} _registers;
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one, line_two;
|
||||
} _control_inputs[2];
|
||||
} control_inputs_[2];
|
||||
|
||||
// Internal state other than the registers
|
||||
bool _timer_is_running[2];
|
||||
bool timer_is_running_[2];
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -404,18 +404,18 @@ class MOS6522IRQDelegate {
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
void set_interrupt_delegate(Delegate *delegate)
|
||||
inline void set_interrupt_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void set_interrupt_status(bool new_status)
|
||||
inline void set_interrupt_status(bool new_status)
|
||||
{
|
||||
if(_delegate) _delegate->mos6522_did_change_interrupt_status(this);
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *_delegate;
|
||||
Delegate *delegate_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ namespace MOS {
|
||||
*/
|
||||
template <class T> class MOS6532 {
|
||||
public:
|
||||
inline void set_ram(uint16_t address, uint8_t value) { _ram[address&0x7f] = value; }
|
||||
inline uint8_t get_ram(uint16_t address) { return _ram[address & 0x7f]; }
|
||||
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
|
||||
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
|
||||
|
||||
inline void set_register(int address, uint8_t value)
|
||||
{
|
||||
@ -36,13 +36,13 @@ template <class T> class MOS6532 {
|
||||
switch(decodedAddress) {
|
||||
// Port output
|
||||
case 0x00: case 0x02:
|
||||
_port[decodedAddress / 2].output = value;
|
||||
static_cast<T *>(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask);
|
||||
port_[decodedAddress / 2].output = value;
|
||||
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
|
||||
set_port_did_change(decodedAddress / 2);
|
||||
break;
|
||||
case 0x01: case 0x03:
|
||||
_port[decodedAddress / 2].output_mask = value;
|
||||
static_cast<T *>(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask);
|
||||
port_[decodedAddress / 2].output_mask = value;
|
||||
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
|
||||
set_port_did_change(decodedAddress / 2);
|
||||
break;
|
||||
|
||||
@ -50,16 +50,16 @@ template <class T> class MOS6532 {
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10)
|
||||
{
|
||||
_timer.writtenShift = _timer.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
_timer.value = ((unsigned int)(value) << _timer.activeShift) | ((1 << _timer.activeShift)-1);
|
||||
_timer.interrupt_enabled = !!(address&0x08);
|
||||
_interrupt_status &= ~InterruptFlag::Timer;
|
||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
timer_.value = ((unsigned int)(value) << timer_.activeShift) | ((1 << timer_.activeShift)-1);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
else
|
||||
{
|
||||
_a7_interrupt.enabled = !!(address&0x2);
|
||||
_a7_interrupt.active_on_positive = !!(address & 0x01);
|
||||
a7_interrupt_.enabled = !!(address&0x2);
|
||||
a7_interrupt_.active_on_positive = !!(address & 0x01);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -74,25 +74,25 @@ template <class T> class MOS6532 {
|
||||
{
|
||||
const int port = decodedAddress / 2;
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~_port[port].output_mask) | (_port[port].output & _port[port].output_mask);
|
||||
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
|
||||
}
|
||||
break;
|
||||
case 0x01: case 0x03:
|
||||
return _port[decodedAddress / 2].output_mask;
|
||||
return port_[decodedAddress / 2].output_mask;
|
||||
break;
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06:
|
||||
{
|
||||
uint8_t value = (uint8_t)(_timer.value >> _timer.activeShift);
|
||||
_timer.interrupt_enabled = !!(address&0x08);
|
||||
_interrupt_status &= ~InterruptFlag::Timer;
|
||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
|
||||
if(_timer.activeShift != _timer.writtenShift) {
|
||||
unsigned int shift = _timer.writtenShift - _timer.activeShift;
|
||||
_timer.value = (_timer.value << shift) | ((1 << shift) - 1);
|
||||
_timer.activeShift = _timer.writtenShift;
|
||||
if(timer_.activeShift != timer_.writtenShift) {
|
||||
unsigned int shift = timer_.writtenShift - timer_.activeShift;
|
||||
timer_.value = (timer_.value << shift) | ((1 << shift) - 1);
|
||||
timer_.activeShift = timer_.writtenShift;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -101,8 +101,8 @@ template <class T> class MOS6532 {
|
||||
|
||||
case 0x05: case 0x07:
|
||||
{
|
||||
uint8_t value = _interrupt_status;
|
||||
_interrupt_status &= ~InterruptFlag::PA7;
|
||||
uint8_t value = interrupt_status_;
|
||||
interrupt_status_ &= ~InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
return value;
|
||||
}
|
||||
@ -115,39 +115,39 @@ template <class T> class MOS6532 {
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(_timer.value >= number_of_cycles) {
|
||||
_timer.value -= number_of_cycles;
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
timer_.value -= number_of_cycles;
|
||||
} else {
|
||||
number_of_cycles -= _timer.value;
|
||||
_timer.value = 0x100 - number_of_cycles;
|
||||
_timer.activeShift = 0;
|
||||
_interrupt_status |= InterruptFlag::Timer;
|
||||
number_of_cycles -= timer_.value;
|
||||
timer_.value = 0x100 - number_of_cycles;
|
||||
timer_.activeShift = 0;
|
||||
interrupt_status_ |= InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
MOS6532() :
|
||||
_interrupt_status(0),
|
||||
_port{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
_a7_interrupt({.last_port_value = 0, .enabled = false}),
|
||||
_interrupt_line(false)
|
||||
interrupt_status_(0),
|
||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
||||
interrupt_line_(false)
|
||||
{}
|
||||
|
||||
inline void set_port_did_change(int port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
uint8_t new_port_a_value = (get_port_input(0) & ~_port[0].output_mask) | (_port[0].output & _port[0].output_mask);
|
||||
uint8_t difference = new_port_a_value ^ _a7_interrupt.last_port_value;
|
||||
_a7_interrupt.last_port_value = new_port_a_value;
|
||||
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
|
||||
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
|
||||
a7_interrupt_.last_port_value = new_port_a_value;
|
||||
if(difference&0x80)
|
||||
{
|
||||
if(
|
||||
((new_port_a_value&0x80) && _a7_interrupt.active_on_positive) ||
|
||||
(!(new_port_a_value&0x80) && !_a7_interrupt.active_on_positive)
|
||||
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
|
||||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
|
||||
)
|
||||
{
|
||||
_interrupt_status |= InterruptFlag::PA7;
|
||||
interrupt_status_ |= InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
}
|
||||
@ -156,34 +156,34 @@ template <class T> class MOS6532 {
|
||||
|
||||
inline bool get_inerrupt_line()
|
||||
{
|
||||
return _interrupt_line;
|
||||
return interrupt_line_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _ram[128];
|
||||
uint8_t ram_[128];
|
||||
|
||||
struct {
|
||||
unsigned int value;
|
||||
unsigned int activeShift, writtenShift;
|
||||
bool interrupt_enabled;
|
||||
} _timer;
|
||||
} timer_;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
bool active_on_positive;
|
||||
uint8_t last_port_value;
|
||||
} _a7_interrupt;
|
||||
} a7_interrupt_;
|
||||
|
||||
struct {
|
||||
uint8_t output_mask, output;
|
||||
} _port[2];
|
||||
} port_[2];
|
||||
|
||||
uint8_t _interrupt_status;
|
||||
uint8_t interrupt_status_;
|
||||
enum InterruptFlag: uint8_t {
|
||||
Timer = 0x80,
|
||||
PA7 = 0x40
|
||||
};
|
||||
bool _interrupt_line;
|
||||
bool interrupt_line_;
|
||||
|
||||
// expected to be overridden
|
||||
uint8_t get_port_input(int port) { return 0xff; }
|
||||
@ -192,10 +192,10 @@ template <class T> class MOS6532 {
|
||||
|
||||
inline void evaluate_interrupts()
|
||||
{
|
||||
_interrupt_line =
|
||||
((_interrupt_status&InterruptFlag::Timer) && _timer.interrupt_enabled) ||
|
||||
((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled);
|
||||
set_irq_line(_interrupt_line);
|
||||
interrupt_line_ =
|
||||
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
|
||||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
|
||||
set_irq_line(interrupt_line_);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,23 +11,23 @@
|
||||
using namespace MOS;
|
||||
|
||||
Speaker::Speaker() :
|
||||
_volume(0),
|
||||
_control_registers{0, 0, 0, 0},
|
||||
_shift_registers{0, 0, 0, 0},
|
||||
_counters{2, 1, 0, 0} // create a slight phase offset for the three channels
|
||||
volume_(0),
|
||||
control_registers_{0, 0, 0, 0},
|
||||
shift_registers_{0, 0, 0, 0},
|
||||
counters_{2, 1, 0, 0} // create a slight phase offset for the three channels
|
||||
{}
|
||||
|
||||
void Speaker::set_volume(uint8_t volume)
|
||||
{
|
||||
enqueue([=]() {
|
||||
_volume = volume;
|
||||
volume_ = volume;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_control(int channel, uint8_t value)
|
||||
{
|
||||
enqueue([=]() {
|
||||
_control_registers[channel] = value;
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
|
||||
@ -99,9 +99,9 @@ static uint8_t noise_pattern[] = {
|
||||
0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f,
|
||||
};
|
||||
|
||||
#define shift(r) _shift_registers[r] = (_shift_registers[r] << 1) | (((_shift_registers[r]^0x80)&_control_registers[r]) >> 7)
|
||||
#define increment(r) _shift_registers[r] = (_shift_registers[r]+1)%8191
|
||||
#define update(r, m, up) _counters[r]++; if((_counters[r] >> m) == 0x80) { up(r); _counters[r] = (unsigned int)(_control_registers[r]&0x7f) << m; }
|
||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
||||
@ -120,11 +120,11 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
// this sums the output of all three sounds channels plus a DC offset for volume;
|
||||
// TODO: what's the real ratio of this stuff?
|
||||
target[c] = (
|
||||
(_shift_registers[0]&1) +
|
||||
(_shift_registers[1]&1) +
|
||||
(_shift_registers[2]&1) +
|
||||
((noise_pattern[_shift_registers[3] >> 3] >> (_shift_registers[3]&7))&(_control_registers[3] >> 7)&1)
|
||||
) * _volume * 700 + _volume * 44;
|
||||
(shift_registers_[0]&1) +
|
||||
(shift_registers_[1]&1) +
|
||||
(shift_registers_[2]&1) +
|
||||
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
|
||||
) * volume_ * 700 + volume_ * 44;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,10 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
|
||||
private:
|
||||
unsigned int _counters[4];
|
||||
unsigned int _shift_registers[4];
|
||||
uint8_t _control_registers[4];
|
||||
uint8_t _volume;
|
||||
unsigned int counters_[4];
|
||||
unsigned int shift_registers_[4];
|
||||
uint8_t control_registers_[4];
|
||||
uint8_t volume_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -43,15 +43,15 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
template <class T> class MOS6560 {
|
||||
public:
|
||||
MOS6560() :
|
||||
_crt(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
|
||||
_speaker(new Speaker),
|
||||
_horizontal_counter(0),
|
||||
_vertical_counter(0),
|
||||
_cycles_since_speaker_update(0),
|
||||
_is_odd_frame(false),
|
||||
_is_odd_line(false)
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false)
|
||||
{
|
||||
_crt->set_composite_sampling_function(
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
@ -69,11 +69,11 @@ template <class T> class MOS6560 {
|
||||
|
||||
void set_clock_rate(double clock_rate)
|
||||
{
|
||||
_speaker->set_input_rate((float)(clock_rate / 4.0));
|
||||
speaker_->set_input_rate((float)(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
|
||||
enum OutputMode {
|
||||
PAL, NTSC
|
||||
@ -84,7 +84,7 @@ template <class T> class MOS6560 {
|
||||
*/
|
||||
void set_output_mode(OutputMode output_mode)
|
||||
{
|
||||
_output_mode = output_mode;
|
||||
output_mode_ = output_mode;
|
||||
uint8_t luminances[16] = { // range is 0–4
|
||||
0, 4, 1, 3, 2, 2, 1, 3,
|
||||
2, 1, 2, 1, 2, 3, 2, 3
|
||||
@ -105,38 +105,38 @@ template <class T> class MOS6560 {
|
||||
case OutputMode::PAL:
|
||||
chrominances = pal_chrominances;
|
||||
display_type = Outputs::CRT::PAL50;
|
||||
_timing.cycles_per_line = 71;
|
||||
_timing.line_counter_increment_offset = 0;
|
||||
_timing.lines_per_progressive_field = 312;
|
||||
_timing.supports_interlacing = false;
|
||||
timing_.cycles_per_line = 71;
|
||||
timing_.line_counter_increment_offset = 0;
|
||||
timing_.lines_per_progressive_field = 312;
|
||||
timing_.supports_interlacing = false;
|
||||
break;
|
||||
|
||||
case OutputMode::NTSC:
|
||||
chrominances = ntsc_chrominances;
|
||||
display_type = Outputs::CRT::NTSC60;
|
||||
_timing.cycles_per_line = 65;
|
||||
_timing.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
||||
_timing.lines_per_progressive_field = 261;
|
||||
_timing.supports_interlacing = true;
|
||||
timing_.cycles_per_line = 65;
|
||||
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
||||
timing_.lines_per_progressive_field = 261;
|
||||
timing_.supports_interlacing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
_crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type);
|
||||
// _crt->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
||||
// crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
|
||||
// switch(output_mode)
|
||||
// {
|
||||
// case OutputMode::PAL:
|
||||
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// case OutputMode::NTSC:
|
||||
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// }
|
||||
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
_colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,88 +146,88 @@ template <class T> class MOS6560 {
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
_cycles_since_speaker_update += number_of_cycles;
|
||||
cycles_since_speaker_update_ += number_of_cycles;
|
||||
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
|
||||
int previous_vertical_counter = _vertical_counter;
|
||||
int previous_vertical_counter = vertical_counter_;
|
||||
|
||||
// keep track of internal time relative to this scanline
|
||||
_horizontal_counter++;
|
||||
_full_frame_counter++;
|
||||
if(_horizontal_counter == _timing.cycles_per_line)
|
||||
horizontal_counter_++;
|
||||
full_frame_counter_++;
|
||||
if(horizontal_counter_ == timing_.cycles_per_line)
|
||||
{
|
||||
if(_horizontal_drawing_latch)
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
_current_character_row++;
|
||||
current_character_row_++;
|
||||
if(
|
||||
(_current_character_row == 16) ||
|
||||
(_current_character_row == 8 && !_registers.tall_characters)
|
||||
(current_character_row_ == 16) ||
|
||||
(current_character_row_ == 8 && !registers_.tall_characters)
|
||||
) {
|
||||
_current_character_row = 0;
|
||||
_current_row++;
|
||||
current_character_row_ = 0;
|
||||
current_row_++;
|
||||
}
|
||||
|
||||
_pixel_line_cycle = -1;
|
||||
_columns_this_line = -1;
|
||||
_column_counter = -1;
|
||||
pixel_line_cycle_ = -1;
|
||||
columns_this_line_ = -1;
|
||||
column_counter_ = -1;
|
||||
}
|
||||
|
||||
_horizontal_counter = 0;
|
||||
if(_output_mode == OutputMode::PAL) _is_odd_line ^= true;
|
||||
_horizontal_drawing_latch = false;
|
||||
horizontal_counter_ = 0;
|
||||
if(output_mode_ == OutputMode::PAL) is_odd_line_ ^= true;
|
||||
horizontal_drawing_latch_ = false;
|
||||
|
||||
_vertical_counter ++;
|
||||
if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field))
|
||||
vertical_counter_ ++;
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
|
||||
{
|
||||
_vertical_counter = 0;
|
||||
_full_frame_counter = 0;
|
||||
vertical_counter_ = 0;
|
||||
full_frame_counter_ = 0;
|
||||
|
||||
if(_output_mode == OutputMode::NTSC) _is_odd_frame ^= true;
|
||||
_current_row = 0;
|
||||
_rows_this_field = -1;
|
||||
_vertical_drawing_latch = false;
|
||||
_base_video_matrix_address_counter = 0;
|
||||
_current_character_row = 0;
|
||||
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
||||
current_row_ = 0;
|
||||
rows_this_field_ = -1;
|
||||
vertical_drawing_latch_ = false;
|
||||
base_video_matrix_address_counter_ = 0;
|
||||
current_character_row_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// check for vertical starting events
|
||||
_vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1);
|
||||
_horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location);
|
||||
vertical_drawing_latch_ |= registers_.first_row_location == (previous_vertical_counter >> 1);
|
||||
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
|
||||
|
||||
if(_pixel_line_cycle >= 0) _pixel_line_cycle++;
|
||||
switch(_pixel_line_cycle)
|
||||
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
||||
switch(pixel_line_cycle_)
|
||||
{
|
||||
case -1:
|
||||
if(_horizontal_drawing_latch)
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
_pixel_line_cycle = 0;
|
||||
_video_matrix_address_counter = _base_video_matrix_address_counter;
|
||||
pixel_line_cycle_ = 0;
|
||||
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
||||
}
|
||||
break;
|
||||
case 1: _columns_this_line = _registers.number_of_columns; break;
|
||||
case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break;
|
||||
case 3: if(_current_row < _rows_this_field) _column_counter = 0; break;
|
||||
case 1: columns_this_line_ = registers_.number_of_columns; break;
|
||||
case 2: if(rows_this_field_ < 0) rows_this_field_ = registers_.number_of_rows; break;
|
||||
case 3: if(current_row_ < rows_this_field_) column_counter_ = 0; break;
|
||||
}
|
||||
|
||||
uint16_t fetch_address = 0x1c;
|
||||
if(_column_counter >= 0 && _column_counter < _columns_this_line*2)
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
|
||||
{
|
||||
if(_column_counter&1)
|
||||
if(column_counter_&1)
|
||||
{
|
||||
fetch_address = _registers.character_cell_start_address + (_character_code*(_registers.tall_characters ? 16 : 8)) + _current_character_row;
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter);
|
||||
_video_matrix_address_counter++;
|
||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
(_current_character_row == 15) ||
|
||||
(_current_character_row == 7 && !_registers.tall_characters)
|
||||
(current_character_row_ == 15) ||
|
||||
(current_character_row_ == 7 && !registers_.tall_characters)
|
||||
) {
|
||||
_base_video_matrix_address_counter = _video_matrix_address_counter;
|
||||
base_video_matrix_address_counter_ = video_matrix_address_counter_;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,99 +242,99 @@ template <class T> class MOS6560 {
|
||||
// divide the byte it is set for 3:1 and then continue as usual.
|
||||
|
||||
// determine output state; colour burst and sync timing are currently a guess
|
||||
if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst;
|
||||
else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync;
|
||||
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
|
||||
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
|
||||
else
|
||||
{
|
||||
_this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border;
|
||||
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
|
||||
}
|
||||
|
||||
// apply vertical sync
|
||||
if(
|
||||
(_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) ||
|
||||
(_registers.interlaced &&
|
||||
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
|
||||
(registers_.interlaced &&
|
||||
(
|
||||
(_vertical_counter == 0 && _horizontal_counter > 32) ||
|
||||
(_vertical_counter == 1) || (_vertical_counter == 2) ||
|
||||
(_vertical_counter == 3 && _horizontal_counter <= 32)
|
||||
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
||||
(vertical_counter_ == 1) || (vertical_counter_ == 2) ||
|
||||
(vertical_counter_ == 3 && horizontal_counter_ <= 32)
|
||||
)
|
||||
))
|
||||
_this_state = State::Sync;
|
||||
this_state_ = State::Sync;
|
||||
|
||||
// update the CRT
|
||||
if(_this_state != _output_state)
|
||||
if(this_state_ != output_state_)
|
||||
{
|
||||
switch(_output_state)
|
||||
switch(output_state_)
|
||||
{
|
||||
case State::Sync: _crt->output_sync(_cycles_in_state * 4); break;
|
||||
case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, (_is_odd_frame || _is_odd_line) ? 128 : 0, 0); break;
|
||||
case State::Border: output_border(_cycles_in_state * 4); break;
|
||||
case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break;
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
||||
}
|
||||
_output_state = _this_state;
|
||||
_cycles_in_state = 0;
|
||||
output_state_ = this_state_;
|
||||
cycles_in_state_ = 0;
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(_output_state == State::Pixels)
|
||||
if(output_state_ == State::Pixels)
|
||||
{
|
||||
pixel_pointer = _crt->allocate_write_area(260);
|
||||
pixel_pointer = crt_->allocate_write_area(260);
|
||||
}
|
||||
}
|
||||
_cycles_in_state++;
|
||||
cycles_in_state_++;
|
||||
|
||||
if(_this_state == State::Pixels)
|
||||
if(this_state_ == State::Pixels)
|
||||
{
|
||||
if(_column_counter&1)
|
||||
if(column_counter_&1)
|
||||
{
|
||||
_character_value = pixel_data;
|
||||
character_value_ = pixel_data;
|
||||
|
||||
if(pixel_pointer)
|
||||
{
|
||||
uint8_t cell_colour = _colours[_character_colour & 0x7];
|
||||
if(!(_character_colour&0x8))
|
||||
uint8_t cell_colour = colours_[character_colour_ & 0x7];
|
||||
if(!(character_colour_&0x8))
|
||||
{
|
||||
uint8_t colours[2];
|
||||
if(_registers.invertedCells)
|
||||
if(registers_.invertedCells)
|
||||
{
|
||||
colours[0] = cell_colour;
|
||||
colours[1] = _registers.backgroundColour;
|
||||
colours[1] = registers_.backgroundColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
colours[0] = _registers.backgroundColour;
|
||||
colours[0] = registers_.backgroundColour;
|
||||
colours[1] = cell_colour;
|
||||
}
|
||||
pixel_pointer[0] = colours[(_character_value >> 7)&1];
|
||||
pixel_pointer[1] = colours[(_character_value >> 6)&1];
|
||||
pixel_pointer[2] = colours[(_character_value >> 5)&1];
|
||||
pixel_pointer[3] = colours[(_character_value >> 4)&1];
|
||||
pixel_pointer[4] = colours[(_character_value >> 3)&1];
|
||||
pixel_pointer[5] = colours[(_character_value >> 2)&1];
|
||||
pixel_pointer[6] = colours[(_character_value >> 1)&1];
|
||||
pixel_pointer[7] = colours[(_character_value >> 0)&1];
|
||||
pixel_pointer[0] = colours[(character_value_ >> 7)&1];
|
||||
pixel_pointer[1] = colours[(character_value_ >> 6)&1];
|
||||
pixel_pointer[2] = colours[(character_value_ >> 5)&1];
|
||||
pixel_pointer[3] = colours[(character_value_ >> 4)&1];
|
||||
pixel_pointer[4] = colours[(character_value_ >> 3)&1];
|
||||
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
|
||||
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
|
||||
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t colours[4] = {_registers.backgroundColour, _registers.borderColour, cell_colour, _registers.auxiliary_colour};
|
||||
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
||||
pixel_pointer[0] =
|
||||
pixel_pointer[1] = colours[(_character_value >> 6)&3];
|
||||
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
||||
pixel_pointer[2] =
|
||||
pixel_pointer[3] = colours[(_character_value >> 4)&3];
|
||||
pixel_pointer[3] = colours[(character_value_ >> 4)&3];
|
||||
pixel_pointer[4] =
|
||||
pixel_pointer[5] = colours[(_character_value >> 2)&3];
|
||||
pixel_pointer[5] = colours[(character_value_ >> 2)&3];
|
||||
pixel_pointer[6] =
|
||||
pixel_pointer[7] = colours[(_character_value >> 0)&3];
|
||||
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
|
||||
}
|
||||
pixel_pointer += 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_character_code = pixel_data;
|
||||
_character_colour = colour_data;
|
||||
character_code_ = pixel_data;
|
||||
character_colour_ = colour_data;
|
||||
}
|
||||
|
||||
_column_counter++;
|
||||
column_counter_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,31 +350,31 @@ template <class T> class MOS6560 {
|
||||
void set_register(int address, uint8_t value)
|
||||
{
|
||||
address &= 0xf;
|
||||
_registers.direct_values[address] = value;
|
||||
registers_.direct_values[address] = value;
|
||||
switch(address)
|
||||
{
|
||||
case 0x0:
|
||||
_registers.interlaced = !!(value&0x80) && _timing.supports_interlacing;
|
||||
_registers.first_column_location = value & 0x7f;
|
||||
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
||||
registers_.first_column_location = value & 0x7f;
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
_registers.first_row_location = value;
|
||||
registers_.first_row_location = value;
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
_registers.number_of_columns = value & 0x7f;
|
||||
_registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
registers_.number_of_columns = value & 0x7f;
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
_registers.number_of_rows = (value >> 1)&0x3f;
|
||||
_registers.tall_characters = !!(value&0x01);
|
||||
registers_.number_of_rows = (value >> 1)&0x3f;
|
||||
registers_.tall_characters = !!(value&0x01);
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
_registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
|
||||
_registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
break;
|
||||
|
||||
case 0xa:
|
||||
@ -382,26 +382,26 @@ template <class T> class MOS6560 {
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
update_audio();
|
||||
_speaker->set_control(address - 0xa, value);
|
||||
speaker_->set_control(address - 0xa, value);
|
||||
break;
|
||||
|
||||
case 0xe:
|
||||
update_audio();
|
||||
_registers.auxiliary_colour = _colours[value >> 4];
|
||||
_speaker->set_volume(value & 0xf);
|
||||
registers_.auxiliary_colour = colours_[value >> 4];
|
||||
speaker_->set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
{
|
||||
uint8_t new_border_colour = _colours[value & 0x07];
|
||||
if(_this_state == State::Border && new_border_colour != _registers.borderColour)
|
||||
uint8_t new_border_colour = colours_[value & 0x07];
|
||||
if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
|
||||
{
|
||||
output_border(_cycles_in_state * 4);
|
||||
_cycles_in_state = 0;
|
||||
output_border(cycles_in_state_ * 4);
|
||||
cycles_in_state_ = 0;
|
||||
}
|
||||
_registers.invertedCells = !((value >> 3)&1);
|
||||
_registers.borderColour = new_border_colour;
|
||||
_registers.backgroundColour = _colours[value >> 4];
|
||||
registers_.invertedCells = !((value >> 3)&1);
|
||||
registers_.borderColour = new_border_colour;
|
||||
registers_.backgroundColour = colours_[value >> 4];
|
||||
}
|
||||
break;
|
||||
|
||||
@ -418,24 +418,24 @@ template <class T> class MOS6560 {
|
||||
uint8_t get_register(int address)
|
||||
{
|
||||
address &= 0xf;
|
||||
int current_line = (_full_frame_counter + _timing.line_counter_increment_offset) / _timing.cycles_per_line;
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address)
|
||||
{
|
||||
default: return _registers.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f);
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Outputs::CRT::CRT> _crt;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
std::shared_ptr<Speaker> _speaker;
|
||||
unsigned int _cycles_since_speaker_update;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio()
|
||||
{
|
||||
_speaker->run_for_cycles(_cycles_since_speaker_update >> 2);
|
||||
_cycles_since_speaker_update &= 3;
|
||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||
cycles_since_speaker_update_ &= 3;
|
||||
}
|
||||
|
||||
// register state
|
||||
@ -448,41 +448,41 @@ template <class T> class MOS6560 {
|
||||
bool invertedCells;
|
||||
|
||||
uint8_t direct_values[16];
|
||||
} _registers;
|
||||
} registers_;
|
||||
|
||||
// output state
|
||||
enum State {
|
||||
Sync, ColourBurst, Border, Pixels
|
||||
} _this_state, _output_state;
|
||||
unsigned int _cycles_in_state;
|
||||
} this_state_, output_state_;
|
||||
unsigned int cycles_in_state_;
|
||||
|
||||
// counters that cover an entire field
|
||||
int _horizontal_counter, _vertical_counter, _full_frame_counter;
|
||||
int horizontal_counter_, vertical_counter_, full_frame_counter_;
|
||||
|
||||
// latches dictating start and length of drawing
|
||||
bool _vertical_drawing_latch, _horizontal_drawing_latch;
|
||||
int _rows_this_field, _columns_this_line;
|
||||
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
||||
int rows_this_field_, columns_this_line_;
|
||||
|
||||
// current drawing position counter
|
||||
int _pixel_line_cycle, _column_counter;
|
||||
int _current_row;
|
||||
uint16_t _current_character_row;
|
||||
uint16_t _video_matrix_address_counter, _base_video_matrix_address_counter;
|
||||
int pixel_line_cycle_, column_counter_;
|
||||
int current_row_;
|
||||
uint16_t current_character_row_;
|
||||
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
|
||||
|
||||
// data latched from the bus
|
||||
uint8_t _character_code, _character_colour, _character_value;
|
||||
uint8_t character_code_, character_colour_, character_value_;
|
||||
|
||||
bool _is_odd_frame, _is_odd_line;
|
||||
bool is_odd_frame_, is_odd_line_;
|
||||
|
||||
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
||||
uint8_t _colours[16];
|
||||
uint8_t colours_[16];
|
||||
|
||||
uint8_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles)
|
||||
{
|
||||
uint8_t *colour_pointer = _crt->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = _registers.borderColour;
|
||||
_crt->output_level(number_of_cycles);
|
||||
uint8_t *colour_pointer = crt_->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
}
|
||||
|
||||
struct {
|
||||
@ -490,8 +490,8 @@ template <class T> class MOS6560 {
|
||||
int line_counter_increment_offset;
|
||||
int lines_per_progressive_field;
|
||||
bool supports_interlacing;
|
||||
} _timing;
|
||||
OutputMode _output_mode;
|
||||
} timing_;
|
||||
OutputMode output_mode_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,14 +11,14 @@
|
||||
using namespace GI;
|
||||
|
||||
AY38910::AY38910() :
|
||||
_selected_register(0),
|
||||
_tone_counters{0, 0, 0}, _tone_periods{0, 0, 0}, _tone_outputs{0, 0, 0},
|
||||
_noise_shift_register(0xffff), _noise_period(0), _noise_counter(0), _noise_output(0),
|
||||
_envelope_divider(0), _envelope_period(0), _envelope_position(0),
|
||||
_master_divider(0),
|
||||
_output_registers{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
selected_register_(0),
|
||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||
master_divider_(0),
|
||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
{
|
||||
_output_registers[8] = _output_registers[9] = _output_registers[10] = 0;
|
||||
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
|
||||
|
||||
// set up envelope lookup tables
|
||||
for(int c = 0; c < 16; c++)
|
||||
@ -28,39 +28,39 @@ AY38910::AY38910() :
|
||||
switch(c)
|
||||
{
|
||||
case 0: case 1: case 2: case 3: case 9:
|
||||
_envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0;
|
||||
_envelope_overflow_masks[c] = 0x1f;
|
||||
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
break;
|
||||
case 4: case 5: case 6: case 7: case 15:
|
||||
_envelope_shapes[c][p] = (p < 16) ? p : 0;
|
||||
_envelope_overflow_masks[c] = 0x1f;
|
||||
envelope_shapes_[c][p] = (p < 16) ? p : 0;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
_envelope_shapes[c][p] = (p & 0xf) ^ 0xf;
|
||||
_envelope_overflow_masks[c] = 0x00;
|
||||
envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
|
||||
envelope_overflow_masks_[c] = 0x00;
|
||||
break;
|
||||
case 12:
|
||||
_envelope_shapes[c][p] = (p & 0xf);
|
||||
_envelope_overflow_masks[c] = 0x00;
|
||||
envelope_shapes_[c][p] = (p & 0xf);
|
||||
envelope_overflow_masks_[c] = 0x00;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
_envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
|
||||
_envelope_overflow_masks[c] = 0x00;
|
||||
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
|
||||
envelope_overflow_masks_[c] = 0x00;
|
||||
break;
|
||||
case 14:
|
||||
_envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
|
||||
_envelope_overflow_masks[c] = 0x00;
|
||||
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
|
||||
envelope_overflow_masks_[c] = 0x00;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
_envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0xf;
|
||||
_envelope_overflow_masks[c] = 0x1f;
|
||||
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
break;
|
||||
case 13:
|
||||
_envelope_shapes[c][p] = (p < 16) ? p : 0xf;
|
||||
_envelope_overflow_masks[c] = 0x1f;
|
||||
envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -71,9 +71,9 @@ AY38910::AY38910() :
|
||||
float root_two = sqrtf(2.0f);
|
||||
for(int v = 0; v < 16; v++)
|
||||
{
|
||||
_volumes[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||
}
|
||||
_volumes[0] = 0;
|
||||
volumes_[0] = 0;
|
||||
}
|
||||
|
||||
void AY38910::set_clock_rate(double clock_rate)
|
||||
@ -84,21 +84,21 @@ void AY38910::set_clock_rate(double clock_rate)
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
int c = 0;
|
||||
while((_master_divider&15) && c < number_of_samples)
|
||||
while((master_divider_&15) && c < number_of_samples)
|
||||
{
|
||||
target[c] = _output_volume;
|
||||
_master_divider++;
|
||||
target[c] = output_volume_;
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
|
||||
while(c < number_of_samples)
|
||||
{
|
||||
#define step_channel(c) \
|
||||
if(_tone_counters[c]) _tone_counters[c]--;\
|
||||
if(tone_counters_[c]) tone_counters_[c]--;\
|
||||
else\
|
||||
{\
|
||||
_tone_outputs[c] ^= 1;\
|
||||
_tone_counters[c] = _tone_periods[c];\
|
||||
tone_outputs_[c] ^= 1;\
|
||||
tone_counters_[c] = tone_periods_[c];\
|
||||
}
|
||||
|
||||
// update the tone channels
|
||||
@ -110,48 +110,48 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
|
||||
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||
// it into the official 17 upon divider underflow.
|
||||
if(_noise_counter) _noise_counter--;
|
||||
if(noise_counter_) noise_counter_--;
|
||||
else
|
||||
{
|
||||
_noise_counter = _noise_period;
|
||||
_noise_output ^= _noise_shift_register&1;
|
||||
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
|
||||
_noise_shift_register >>= 1;
|
||||
noise_counter_ = noise_period_;
|
||||
noise_output_ ^= noise_shift_register_&1;
|
||||
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
||||
noise_shift_register_ >>= 1;
|
||||
}
|
||||
|
||||
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
||||
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||
if(_envelope_divider) _envelope_divider--;
|
||||
if(envelope_divider_) envelope_divider_--;
|
||||
else
|
||||
{
|
||||
_envelope_divider = _envelope_period;
|
||||
_envelope_position ++;
|
||||
if(_envelope_position == 32) _envelope_position = _envelope_overflow_masks[_output_registers[13]];
|
||||
envelope_divider_ = envelope_period_;
|
||||
envelope_position_ ++;
|
||||
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
||||
}
|
||||
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
|
||||
{
|
||||
target[c] = _output_volume;
|
||||
target[c] = output_volume_;
|
||||
c++;
|
||||
_master_divider++;
|
||||
master_divider_++;
|
||||
}
|
||||
}
|
||||
|
||||
_master_divider &= 15;
|
||||
master_divider_ &= 15;
|
||||
}
|
||||
|
||||
void AY38910::evaluate_output_volume()
|
||||
{
|
||||
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
|
||||
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
|
||||
|
||||
// The output level for a channel is:
|
||||
// 1 if neither tone nor noise is enabled;
|
||||
// 0 if either tone or noise is enabled and its value is low.
|
||||
// The tone/noise enable bits use inverse logic — 0 = on, 1 = off — permitting the OR logic below.
|
||||
#define tone_level(c, tone_bit) (_tone_outputs[c] | (_output_registers[7] >> tone_bit))
|
||||
#define noise_level(c, noise_bit) (_noise_output | (_output_registers[7] >> noise_bit))
|
||||
#define tone_level(c, tone_bit) (tone_outputs_[c] | (output_registers_[7] >> tone_bit))
|
||||
#define noise_level(c, noise_bit) (noise_output_ | (output_registers_[7] >> noise_bit))
|
||||
|
||||
#define level(c, tone_bit, noise_bit) tone_level(c, tone_bit) & noise_level(c, noise_bit) & 1
|
||||
const int channel_levels[3] = {
|
||||
@ -163,7 +163,7 @@ void AY38910::evaluate_output_volume()
|
||||
|
||||
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
|
||||
#define channel_volume(c) \
|
||||
((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf)
|
||||
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
|
||||
|
||||
const int volumes[3] = {
|
||||
channel_volume(8),
|
||||
@ -173,24 +173,24 @@ void AY38910::evaluate_output_volume()
|
||||
#undef channel_volume
|
||||
|
||||
// Mix additively.
|
||||
_output_volume = (int16_t)(
|
||||
_volumes[volumes[0]] * channel_levels[0] +
|
||||
_volumes[volumes[1]] * channel_levels[1] +
|
||||
_volumes[volumes[2]] * channel_levels[2]
|
||||
output_volume_ = (int16_t)(
|
||||
volumes_[volumes[0]] * channel_levels[0] +
|
||||
volumes_[volumes[1]] * channel_levels[1] +
|
||||
volumes_[volumes[2]] * channel_levels[2]
|
||||
);
|
||||
}
|
||||
|
||||
void AY38910::select_register(uint8_t r)
|
||||
{
|
||||
_selected_register = r & 0xf;
|
||||
selected_register_ = r & 0xf;
|
||||
}
|
||||
|
||||
void AY38910::set_register_value(uint8_t value)
|
||||
{
|
||||
_registers[_selected_register] = value;
|
||||
if(_selected_register < 14)
|
||||
registers_[selected_register_] = value;
|
||||
if(selected_register_ < 14)
|
||||
{
|
||||
int selected_register = _selected_register;
|
||||
int selected_register = selected_register_;
|
||||
enqueue([=] () {
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register)
|
||||
@ -201,34 +201,34 @@ void AY38910::set_register_value(uint8_t value)
|
||||
int channel = selected_register >> 1;
|
||||
|
||||
if(selected_register & 1)
|
||||
_tone_periods[channel] = (_tone_periods[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
||||
else
|
||||
_tone_periods[channel] = (_tone_periods[channel] & ~0xff) | value;
|
||||
_tone_counters[channel] = _tone_periods[channel];
|
||||
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
||||
tone_counters_[channel] = tone_periods_[channel];
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
_noise_period = value & 0x1f;
|
||||
_noise_counter = _noise_period;
|
||||
noise_period_ = value & 0x1f;
|
||||
noise_counter_ = noise_period_;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
_envelope_period = (_envelope_period & ~0xff) | value;
|
||||
_envelope_divider = _envelope_period;
|
||||
envelope_period_ = (envelope_period_ & ~0xff) | value;
|
||||
envelope_divider_ = envelope_period_;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
_envelope_period = (_envelope_period & 0xff) | (int)(value << 8);
|
||||
_envelope_divider = _envelope_period;
|
||||
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
|
||||
envelope_divider_ = envelope_period_;
|
||||
break;
|
||||
|
||||
case 13:
|
||||
masked_value &= 0xf;
|
||||
_envelope_position = 0;
|
||||
envelope_position_ = 0;
|
||||
break;
|
||||
}
|
||||
_output_registers[selected_register] = masked_value;
|
||||
output_registers_[selected_register] = masked_value;
|
||||
evaluate_output_volume();
|
||||
});
|
||||
}
|
||||
@ -244,22 +244,22 @@ uint8_t AY38910::get_register_value()
|
||||
0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00
|
||||
};
|
||||
|
||||
return _registers[_selected_register] | register_masks[_selected_register];
|
||||
return registers_[selected_register_] | register_masks[selected_register_];
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b)
|
||||
{
|
||||
return _registers[port_b ? 15 : 14];
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
void AY38910::set_data_input(uint8_t r)
|
||||
{
|
||||
_data_input = r;
|
||||
data_input_ = r;
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output()
|
||||
{
|
||||
return _data_output;
|
||||
return data_output_;
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines)
|
||||
@ -277,15 +277,15 @@ void AY38910::set_control_lines(ControlLines control_lines)
|
||||
case (int)(BCDIR | BC2): new_state = Write; break;
|
||||
}
|
||||
|
||||
if(new_state != _control_state)
|
||||
if(new_state != control_state_)
|
||||
{
|
||||
_control_state = new_state;
|
||||
control_state_ = new_state;
|
||||
switch(new_state)
|
||||
{
|
||||
default: break;
|
||||
case LatchAddress: select_register(_data_input); break;
|
||||
case Write: set_register_value(_data_input); break;
|
||||
case Read: _data_output = get_register_value(); break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
case Read: data_output_ = get_register_value(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,42 +51,42 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
int _selected_register;
|
||||
uint8_t _registers[16], _output_registers[16];
|
||||
int selected_register_;
|
||||
uint8_t registers_[16], output_registers_[16];
|
||||
|
||||
int _master_divider;
|
||||
int master_divider_;
|
||||
|
||||
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;
|
||||
int _noise_counter;
|
||||
int _noise_shift_register;
|
||||
int _noise_output;
|
||||
int noise_period_;
|
||||
int noise_counter_;
|
||||
int noise_shift_register_;
|
||||
int noise_output_;
|
||||
|
||||
int _envelope_period;
|
||||
int _envelope_divider;
|
||||
int _envelope_position;
|
||||
int _envelope_shapes[16][32];
|
||||
int _envelope_overflow_masks[16];
|
||||
int envelope_period_;
|
||||
int envelope_divider_;
|
||||
int envelope_position_;
|
||||
int envelope_shapes_[16][32];
|
||||
int envelope_overflow_masks_[16];
|
||||
|
||||
int _volumes[16];
|
||||
int volumes_[16];
|
||||
|
||||
enum ControlState {
|
||||
Inactive,
|
||||
LatchAddress,
|
||||
Read,
|
||||
Write
|
||||
} _control_state;
|
||||
} control_state_;
|
||||
|
||||
void select_register(uint8_t r);
|
||||
void set_register_value(uint8_t value);
|
||||
uint8_t get_register_value();
|
||||
|
||||
uint8_t _data_input, _data_output;
|
||||
uint8_t data_input_, data_output_;
|
||||
|
||||
int16_t _output_volume;
|
||||
int16_t output_volume_;
|
||||
inline void evaluate_output_volume();
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,8 +12,9 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../Components/6532/6532.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "Atari2600Inputs.h"
|
||||
@ -23,56 +24,6 @@ namespace Atari2600 {
|
||||
const unsigned int number_of_upcoming_events = 6;
|
||||
const unsigned int number_of_recorded_counters = 7;
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
Speaker();
|
||||
~Speaker();
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
uint8_t _volume[2];
|
||||
uint8_t _divider[2];
|
||||
uint8_t _control[2];
|
||||
|
||||
int _poly4_counter[2];
|
||||
int _poly5_counter[2];
|
||||
int _poly9_counter[2];
|
||||
int _output_state[2];
|
||||
|
||||
int _divider_counter[2];
|
||||
|
||||
int _pattern_periods[16];
|
||||
int _patterns[16][512];
|
||||
};
|
||||
|
||||
class PIA: public MOS::MOS6532<PIA> {
|
||||
public:
|
||||
inline uint8_t get_port_input(int port)
|
||||
{
|
||||
return _portValues[port];
|
||||
}
|
||||
|
||||
inline void update_port_input(int port, uint8_t mask, bool set)
|
||||
{
|
||||
if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
|
||||
PIA() :
|
||||
_portValues{0xff, 0xff}
|
||||
{}
|
||||
|
||||
private:
|
||||
uint8_t _portValues[2];
|
||||
|
||||
};
|
||||
|
||||
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
@ -95,26 +46,26 @@ class Machine:
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
// TODO: different rate for PAL
|
||||
|
||||
private:
|
||||
uint8_t *_rom, *_romPages[4];
|
||||
size_t _rom_size;
|
||||
uint8_t *rom_, *rom_pages_[4];
|
||||
size_t rom_size_;
|
||||
|
||||
// the RIOT
|
||||
PIA _mos6532;
|
||||
PIA mos6532_;
|
||||
|
||||
// playfield registers
|
||||
uint8_t _playfieldControl;
|
||||
uint8_t _playfieldColour;
|
||||
uint8_t _backgroundColour;
|
||||
uint8_t _playfield[41];
|
||||
uint8_t playfield_control_;
|
||||
uint8_t playfield_colour_;
|
||||
uint8_t background_colour_;
|
||||
uint8_t playfield_[41];
|
||||
|
||||
// ... and derivatives
|
||||
int _ballSize, _missileSize[2];
|
||||
int ball_size_, missile_size_[2];
|
||||
|
||||
// delayed clock events
|
||||
enum OutputState {
|
||||
@ -136,12 +87,12 @@ class Machine:
|
||||
int updates;
|
||||
|
||||
OutputState state;
|
||||
uint8_t playfieldPixel;
|
||||
uint8_t playfield_pixel;
|
||||
int counter;
|
||||
|
||||
Event() : updates(0), playfieldPixel(0) {}
|
||||
} _upcomingEvents[number_of_upcoming_events];
|
||||
unsigned int _upcomingEventsPointer;
|
||||
Event() : updates(0), playfield_pixel(0) {}
|
||||
} upcoming_events_[number_of_upcoming_events];
|
||||
unsigned int upcoming_events_pointer_;
|
||||
|
||||
// object counters
|
||||
struct ObjectCounter {
|
||||
@ -150,74 +101,73 @@ class Machine:
|
||||
int broad_pixel; // for sprite objects, a count of cycles since the last counter reset; otherwise unused
|
||||
|
||||
ObjectCounter() : count(0), pixel(0), broad_pixel(0) {}
|
||||
} _objectCounter[number_of_recorded_counters][5];
|
||||
unsigned int _objectCounterPointer;
|
||||
} object_counter_[number_of_recorded_counters][5];
|
||||
unsigned int object_counter_pointer_;
|
||||
|
||||
// the latched playfield output
|
||||
uint8_t _playfieldOutput, _nextPlayfieldOutput;
|
||||
uint8_t playfield_output_, next_playfield_output_;
|
||||
|
||||
// player registers
|
||||
uint8_t _playerColour[2];
|
||||
uint8_t _playerReflectionMask[2];
|
||||
uint8_t _playerGraphics[2][2];
|
||||
uint8_t _playerGraphicsSelector[2];
|
||||
bool _playerStart[2];
|
||||
uint8_t player_colour_[2];
|
||||
uint8_t player_reflection_mask_[2];
|
||||
uint8_t player_graphics_[2][2];
|
||||
uint8_t player_graphics_selector_[2];
|
||||
|
||||
// object flags
|
||||
bool _hasSecondCopy[2];
|
||||
bool _hasThirdCopy[2];
|
||||
bool _hasFourthCopy[2];
|
||||
uint8_t _objectMotion[5]; // the value stored to this counter's motion register
|
||||
bool has_second_copy_[2];
|
||||
bool has_third_copy_[2];
|
||||
bool has_fourth_copy_[2];
|
||||
uint8_t object_motions_[5]; // the value stored to this counter's motion register
|
||||
|
||||
// player + missile registers
|
||||
uint8_t _playerAndMissileSize[2];
|
||||
uint8_t player_and_missile_size_[2];
|
||||
|
||||
// missile registers
|
||||
uint8_t _missileGraphicsEnable[2];
|
||||
bool _missileGraphicsReset[2];
|
||||
uint8_t missile_graphics_enable_[2];
|
||||
bool missile_graphics_reset_[2];
|
||||
|
||||
// ball registers
|
||||
uint8_t _ballGraphicsEnable[2];
|
||||
uint8_t _ballGraphicsSelector;
|
||||
uint8_t ball_graphics_enable_[2];
|
||||
uint8_t ball_graphics_selector_;
|
||||
|
||||
// graphics output
|
||||
unsigned int _horizontalTimer;
|
||||
bool _vSyncEnabled, _vBlankEnabled;
|
||||
unsigned int horizontal_timer_;
|
||||
bool vsync_enabled_, vblank_enabled_;
|
||||
|
||||
// horizontal motion control
|
||||
uint8_t _hMoveCounter;
|
||||
uint8_t _hMoveFlags;
|
||||
uint8_t hmove_counter_;
|
||||
uint8_t hmove_flags_;
|
||||
|
||||
// joystick state
|
||||
uint8_t _tiaInputValue[2];
|
||||
uint8_t tia_input_value_[2];
|
||||
|
||||
// collisions
|
||||
uint8_t _collisions[8];
|
||||
uint8_t collisions_[8];
|
||||
|
||||
void output_pixels(unsigned int count);
|
||||
uint8_t get_output_pixel();
|
||||
void update_timers(int mask);
|
||||
|
||||
// outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> _crt;
|
||||
std::shared_ptr<Speaker> _speaker;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
// current mode
|
||||
bool _is_pal_region;
|
||||
bool is_pal_region_;
|
||||
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int _cycles_since_speaker_update;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio();
|
||||
|
||||
// latched output state
|
||||
unsigned int _lastOutputStateDuration;
|
||||
OutputState _stateByExtendTime[2][57];
|
||||
OutputState *_stateByTime;
|
||||
OutputState _lastOutputState;
|
||||
uint8_t *_outputBuffer;
|
||||
unsigned int last_output_state_duration_;
|
||||
OutputState state_by_extend_time_[2][57];
|
||||
OutputState *state_by_time_;
|
||||
OutputState last_output_state_;
|
||||
uint8_t *output_buffer_;
|
||||
|
||||
// lookup table for collision reporting
|
||||
uint8_t _reportedCollisions[64][8];
|
||||
uint8_t reported_collisions_[64][8];
|
||||
void setup_reported_collisions();
|
||||
};
|
||||
|
||||
|
40
Machines/Atari2600/PIA.hpp
Normal file
40
Machines/Atari2600/PIA.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// PIA.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_PIA_h
|
||||
#define Atari2600_PIA_h
|
||||
|
||||
#include "../../Components/6532/6532.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class PIA: public MOS::MOS6532<PIA> {
|
||||
public:
|
||||
inline uint8_t get_port_input(int port)
|
||||
{
|
||||
return port_values_[port];
|
||||
}
|
||||
|
||||
inline void update_port_input(int port, uint8_t mask, bool set)
|
||||
{
|
||||
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
|
||||
PIA() :
|
||||
port_values_{0xff, 0xff}
|
||||
{}
|
||||
|
||||
private:
|
||||
uint8_t port_values_[2];
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* PIA_h */
|
137
Machines/Atari2600/Speaker.cpp
Normal file
137
Machines/Atari2600/Speaker.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
//
|
||||
// Speaker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Speaker.hpp"
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Atari2600::Speaker::Speaker() :
|
||||
poly4_counter_{0x00f, 0x00f},
|
||||
poly5_counter_{0x01f, 0x01f},
|
||||
poly9_counter_{0x1ff, 0x1ff}
|
||||
{}
|
||||
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume)
|
||||
{
|
||||
enqueue([=]() {
|
||||
volume_[channel] = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider)
|
||||
{
|
||||
enqueue([=]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control)
|
||||
{
|
||||
enqueue([=]() {
|
||||
control_[channel] = control & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
#define advance_poly4(c) poly4_counter_[channel] = (poly4_counter_[channel] >> 1) | (((poly4_counter_[channel] << 3) ^ (poly4_counter_[channel] << 2))&0x008)
|
||||
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
||||
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
||||
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
target[c] = 0;
|
||||
for(int channel = 0; channel < 2; channel++)
|
||||
{
|
||||
divider_counter_[channel] ++;
|
||||
int level = 0;
|
||||
switch(control_[channel])
|
||||
{
|
||||
case 0x0: case 0xb: // constant 1
|
||||
level = 1;
|
||||
break;
|
||||
|
||||
case 0x4: case 0x5: // div2 tone
|
||||
level = (divider_counter_[channel] / (divider_[channel]+1))&1;
|
||||
break;
|
||||
|
||||
case 0xc: case 0xd: // div6 tone
|
||||
level = (divider_counter_[channel] / ((divider_[channel]+1)*3))&1;
|
||||
break;
|
||||
|
||||
case 0x6: case 0xa: // div31 tone
|
||||
level = (divider_counter_[channel] / (divider_[channel]+1))%30 <= 18;
|
||||
break;
|
||||
|
||||
case 0xe: // div93 tone
|
||||
level = (divider_counter_[channel] / ((divider_[channel]+1)*3))%30 <= 18;
|
||||
break;
|
||||
|
||||
case 0x1: // 4-bit poly
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x2: // 4-bit poly div31
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18)
|
||||
{
|
||||
advance_poly4(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3: // 5/4-bit poly
|
||||
level = output_state_[channel];
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(poly5_counter_[channel]&1)
|
||||
{
|
||||
output_state_[channel] = poly4_counter_[channel]&1;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
advance_poly5(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x7: case 0x9: // 5-bit poly
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf: // 5-bit poly div6
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == (divider_[channel]+1)*3)
|
||||
{
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x8: // 9-bit poly
|
||||
level = poly9_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly9(channel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
target[c] += volume_[channel] * 1024 * level;
|
||||
}
|
||||
}
|
||||
}
|
41
Machines/Atari2600/Speaker.hpp
Normal file
41
Machines/Atari2600/Speaker.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_Speaker_hpp
|
||||
#define Atari2600_Speaker_hpp
|
||||
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
Speaker();
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
uint8_t control_[2];
|
||||
|
||||
int poly4_counter_[2];
|
||||
int poly5_counter_[2];
|
||||
int poly9_counter_[2];
|
||||
int output_state_[2];
|
||||
|
||||
int divider_counter_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
@ -13,21 +13,19 @@
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine() :
|
||||
_shift_register(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300)
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA)
|
||||
{
|
||||
// create a serial port and a VIA to run it
|
||||
_serialPortVIA.reset(new SerialPortVIA);
|
||||
_serialPort.reset(new SerialPort);
|
||||
|
||||
// attach the serial port to its VIA and vice versa
|
||||
_serialPort->set_serial_port_via(_serialPortVIA);
|
||||
_serialPortVIA->set_serial_port(_serialPort);
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_);
|
||||
serial_port_VIA_->set_serial_port(serial_port_);
|
||||
|
||||
// set this instance as the delegate to receive interrupt requests from both VIAs
|
||||
_serialPortVIA->set_interrupt_delegate(this);
|
||||
_driveVIA.set_interrupt_delegate(this);
|
||||
_driveVIA.set_delegate(this);
|
||||
serial_port_VIA_->set_interrupt_delegate(this);
|
||||
drive_VIA_.set_interrupt_delegate(this);
|
||||
drive_VIA_.set_delegate(this);
|
||||
|
||||
// set a bit rate
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
@ -35,27 +33,11 @@ Machine::Machine() :
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
|
||||
{
|
||||
Commodore::Serial::AttachPortAndBus(_serialPort, serial_bus);
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
// static bool log = false;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xF3C0)) log = true;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && log) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF510 && address <= 0xF553)) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A));
|
||||
|
||||
/* static bool log = false;
|
||||
|
||||
if(operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
log = (address >= 0xE85B && address <= 0xE907) || (address >= 0xE9C9 && address <= 0xEA2D);
|
||||
if(log) printf("\n%04x: ", address);
|
||||
}
|
||||
if(log) printf("[%c %04x] ", isReadOperation(operation) ? 'r' : 'w', address);*/
|
||||
|
||||
/*
|
||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||
|
||||
@ -67,39 +49,39 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
if(address < 0x800)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
*value = _ram[address];
|
||||
*value = ram_[address];
|
||||
else
|
||||
_ram[address] = *value;
|
||||
ram_[address] = *value;
|
||||
}
|
||||
else if(address >= 0xc000)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
*value = _rom[address & 0x3fff];
|
||||
*value = rom_[address & 0x3fff];
|
||||
}
|
||||
else if(address >= 0x1800 && address <= 0x180f)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
*value = _serialPortVIA->get_register(address);
|
||||
*value = serial_port_VIA_->get_register(address);
|
||||
else
|
||||
_serialPortVIA->set_register(address, *value);
|
||||
serial_port_VIA_->set_register(address, *value);
|
||||
}
|
||||
else if(address >= 0x1c00 && address <= 0x1c0f)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
*value = _driveVIA.get_register(address);
|
||||
*value = drive_VIA_.get_register(address);
|
||||
else
|
||||
_driveVIA.set_register(address, *value);
|
||||
drive_VIA_.set_register(address, *value);
|
||||
}
|
||||
|
||||
_serialPortVIA->run_for_cycles(1);
|
||||
_driveVIA.run_for_cycles(1);
|
||||
serial_port_VIA_->run_for_cycles(1);
|
||||
drive_VIA_.run_for_cycles(1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::set_rom(const uint8_t *rom)
|
||||
{
|
||||
memcpy(_rom, rom, sizeof(_rom));
|
||||
memcpy(rom_, rom, sizeof(rom_));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
|
||||
@ -112,8 +94,8 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
set_motor_on(_driveVIA.get_motor_enabled());
|
||||
if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
||||
Storage::Disk::Controller::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
@ -122,29 +104,29 @@ void Machine::run_for_cycles(int number_of_cycles)
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
// both VIAs are connected to the IRQ line
|
||||
set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line());
|
||||
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Disk drive
|
||||
|
||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
_shift_register = (_shift_register << 1) | value;
|
||||
if((_shift_register & 0x3ff) == 0x3ff)
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
if((shift_register_ & 0x3ff) == 0x3ff)
|
||||
{
|
||||
_driveVIA.set_sync_detected(true);
|
||||
_bit_window_offset = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
||||
drive_VIA_.set_sync_detected(true);
|
||||
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
||||
}
|
||||
else
|
||||
{
|
||||
_driveVIA.set_sync_detected(false);
|
||||
drive_VIA_.set_sync_detected(false);
|
||||
}
|
||||
_bit_window_offset++;
|
||||
if(_bit_window_offset == 8)
|
||||
bit_window_offset_++;
|
||||
if(bit_window_offset_ == 8)
|
||||
{
|
||||
_driveVIA.set_data_input((uint8_t)_shift_register);
|
||||
_bit_window_offset = 0;
|
||||
if(_driveVIA.get_should_set_overflow())
|
||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_.get_should_set_overflow())
|
||||
{
|
||||
set_overflow_line(true);
|
||||
}
|
||||
@ -171,12 +153,12 @@ void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
|
||||
#pragma mark - SerialPortVIA
|
||||
|
||||
SerialPortVIA::SerialPortVIA() :
|
||||
_portB(0x00), _attention_acknowledge_level(false), _attention_level_input(true), _data_level_output(false)
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false)
|
||||
{}
|
||||
|
||||
uint8_t SerialPortVIA::get_port_input(Port port)
|
||||
{
|
||||
if(port) return _portB;
|
||||
if(port) return port_b_;
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
@ -184,10 +166,10 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
_attention_acknowledge_level = !(value&0x10);
|
||||
_data_level_output = (value&0x02);
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
data_level_output_ = (value&0x02);
|
||||
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!(value&0x08));
|
||||
update_data_line();
|
||||
@ -200,31 +182,30 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
||||
switch(line)
|
||||
{
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: _portB = (_portB & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: _portB = (_portB & ~0x04) | (value ? 0x00 : 0x04); break;
|
||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
|
||||
case ::Commodore::Serial::Line::Attention:
|
||||
_attention_level_input = !value;
|
||||
_portB = (_portB & ~0x80) | (value ? 0x00 : 0x80);
|
||||
attention_level_input_ = !value;
|
||||
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
||||
set_control_line_input(Port::A, Line::One, !value);
|
||||
update_data_line();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort)
|
||||
{
|
||||
_serialPort = serialPort;
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
void SerialPortVIA::update_data_line()
|
||||
{
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data,
|
||||
(::Commodore::Serial::LineLevel)(!_data_level_output
|
||||
&& (_attention_level_input != _attention_acknowledge_level)));
|
||||
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,35 +213,35 @@ void SerialPortVIA::update_data_line()
|
||||
|
||||
void DriveVIA::set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
// write protect tab uncovered
|
||||
DriveVIA::DriveVIA() : _port_b(0xff), _port_a(0xff), _delegate(nullptr) {}
|
||||
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
|
||||
|
||||
uint8_t DriveVIA::get_port_input(Port port) {
|
||||
return port ? _port_b : _port_a;
|
||||
return port ? port_b_ : port_a_;
|
||||
}
|
||||
|
||||
void DriveVIA::set_sync_detected(bool sync_detected) {
|
||||
_port_b = (_port_b & 0x7f) | (sync_detected ? 0x00 : 0x80);
|
||||
port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void DriveVIA::set_data_input(uint8_t value) {
|
||||
_port_a = value;
|
||||
port_a_ = value;
|
||||
}
|
||||
|
||||
bool DriveVIA::get_should_set_overflow() {
|
||||
return _should_set_overflow;
|
||||
return should_set_overflow_;
|
||||
}
|
||||
|
||||
bool DriveVIA::get_motor_enabled() {
|
||||
return _drive_motor;
|
||||
return drive_motor_;
|
||||
}
|
||||
|
||||
void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
_should_set_overflow = value;
|
||||
should_set_overflow_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,36 +249,36 @@ void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
|
||||
if(port)
|
||||
{
|
||||
// record drive motor state
|
||||
_drive_motor = !!(value&4);
|
||||
drive_motor_ = !!(value&4);
|
||||
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (_previous_port_b_output&3))&3;
|
||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference)
|
||||
{
|
||||
if(_delegate) _delegate->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (_previous_port_b_output^value) & (3 << 5);
|
||||
if(density_difference && _delegate)
|
||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_)
|
||||
{
|
||||
_delegate->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// TODO: something with the drive LED
|
||||
// printf("LED: %s\n", value&8 ? "On" : "Off");
|
||||
|
||||
_previous_port_b_output = value;
|
||||
previous_port_b_output_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - SerialPort
|
||||
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||
std::shared_ptr<SerialPortVIA> serialPortVIA = _serialPortVIA.lock();
|
||||
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
|
||||
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, (bool)level);
|
||||
}
|
||||
|
||||
void SerialPort::set_serial_port_via(std::shared_ptr<SerialPortVIA> serialPortVIA) {
|
||||
_serialPortVIA = serialPortVIA;
|
||||
void SerialPort::set_serial_port_via(const std::shared_ptr<SerialPortVIA> &serialPortVIA) {
|
||||
serial_port_VIA_ = serialPortVIA;
|
||||
}
|
||||
|
@ -41,17 +41,17 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
|
||||
|
||||
SerialPortVIA();
|
||||
|
||||
uint8_t get_port_input(Port port);
|
||||
uint8_t get_port_input(Port);
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask);
|
||||
void set_serial_line_state(::Commodore::Serial::Line line, bool value);
|
||||
void set_port_output(Port, uint8_t value, uint8_t mask);
|
||||
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
||||
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
||||
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
||||
|
||||
private:
|
||||
uint8_t _portB;
|
||||
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
|
||||
bool _attention_acknowledge_level, _attention_level_input, _data_level_output;
|
||||
uint8_t port_b_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
|
||||
|
||||
void update_data_line();
|
||||
};
|
||||
@ -79,7 +79,7 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
||||
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
@ -87,21 +87,21 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
|
||||
uint8_t get_port_input(Port port);
|
||||
|
||||
void set_sync_detected(bool sync_detected);
|
||||
void set_data_input(uint8_t value);
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
bool get_should_set_overflow();
|
||||
bool get_motor_enabled();
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value);
|
||||
void set_control_line_output(Port, Line, bool value);
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
|
||||
void set_port_output(Port, uint8_t value, uint8_t direction_mask);
|
||||
|
||||
private:
|
||||
uint8_t _port_b, _port_a;
|
||||
bool _should_set_overflow;
|
||||
bool _drive_motor;
|
||||
uint8_t _previous_port_b_output;
|
||||
Delegate *_delegate;
|
||||
uint8_t port_b_, port_a_;
|
||||
bool should_set_overflow_;
|
||||
bool drive_motor_;
|
||||
uint8_t previous_port_b_output_;
|
||||
Delegate *delegate_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -109,11 +109,11 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
*/
|
||||
class SerialPort : public ::Commodore::Serial::Port {
|
||||
public:
|
||||
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level);
|
||||
void set_serial_port_via(std::shared_ptr<SerialPortVIA> serialPortVIA);
|
||||
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
||||
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
||||
|
||||
private:
|
||||
std::weak_ptr<SerialPortVIA> _serialPortVIA;
|
||||
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -152,16 +152,14 @@ class Machine:
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
|
||||
private:
|
||||
uint8_t _ram[0x800];
|
||||
uint8_t _rom[0x4000];
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
std::shared_ptr<SerialPortVIA> _serialPortVIA;
|
||||
std::shared_ptr<SerialPort> _serialPort;
|
||||
DriveVIA _driveVIA;
|
||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
DriveVIA drive_VIA_;
|
||||
|
||||
std::shared_ptr<Storage::Disk::Disk> _disk;
|
||||
|
||||
int _shift_register, _bit_window_offset;
|
||||
int shift_register_, bit_window_offset_;
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
virtual void process_index_hole();
|
||||
};
|
||||
|
@ -30,14 +30,14 @@ void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shar
|
||||
|
||||
void Bus::add_port(std::shared_ptr<Port> port)
|
||||
{
|
||||
_ports.push_back(port);
|
||||
ports_.push_back(port);
|
||||
for(int line = (int)ServiceRequest; line <= (int)Reset; line++)
|
||||
{
|
||||
// the addition of a new device may change the line output...
|
||||
set_line_output_did_change((Line)line);
|
||||
|
||||
// ... but the new device will need to be told the current state regardless
|
||||
port->set_input((Line)line, _line_levels[line]);
|
||||
port->set_input((Line)line, line_levels_[line]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ void Bus::set_line_output_did_change(Line line)
|
||||
{
|
||||
// i.e. I believe these lines to be open collector
|
||||
LineLevel new_line_level = High;
|
||||
for(std::weak_ptr<Port> port : _ports)
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
@ -55,11 +55,11 @@ void Bus::set_line_output_did_change(Line line)
|
||||
}
|
||||
|
||||
// post an update only if one occurred
|
||||
if(new_line_level != _line_levels[line])
|
||||
if(new_line_level != line_levels_[line])
|
||||
{
|
||||
_line_levels[line] = new_line_level;
|
||||
line_levels_[line] = new_line_level;
|
||||
|
||||
for(std::weak_ptr<Port> port : _ports)
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
@ -74,20 +74,20 @@ void Bus::set_line_output_did_change(Line line)
|
||||
|
||||
void DebugPort::set_input(Line line, LineLevel value)
|
||||
{
|
||||
_input_levels[line] = value;
|
||||
input_levels_[line] = value;
|
||||
|
||||
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
||||
if(!_incoming_count)
|
||||
if(!incoming_count_)
|
||||
{
|
||||
_incoming_count = (!_input_levels[Line::Clock] && !_input_levels[Line::Data]) ? 8 : 0;
|
||||
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(line == Line::Clock && value)
|
||||
{
|
||||
_incoming_byte = (_incoming_byte >> 1) | (_input_levels[Line::Data] ? 0x80 : 0x00);
|
||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
||||
}
|
||||
_incoming_count--;
|
||||
if(_incoming_count == 0) printf("[Bus] Observed %02x\n", _incoming_byte);
|
||||
incoming_count_--;
|
||||
if(incoming_count_ == 0) printf("[Bus] Observed %02x\n", incoming_byte_);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ namespace Serial {
|
||||
*/
|
||||
class Bus {
|
||||
public:
|
||||
Bus() : _line_levels{High, High, High, High, High} {}
|
||||
Bus() : line_levels_{High, High, High, High, High} {}
|
||||
|
||||
/*!
|
||||
Adds the supplied port to the bus.
|
||||
@ -62,8 +62,8 @@ namespace Serial {
|
||||
void set_line_output_did_change(Line line);
|
||||
|
||||
private:
|
||||
LineLevel _line_levels[5];
|
||||
std::vector<std::weak_ptr<Port>> _ports;
|
||||
LineLevel line_levels_[5];
|
||||
std::vector<std::weak_ptr<Port>> ports_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -72,16 +72,16 @@ namespace Serial {
|
||||
*/
|
||||
class Port {
|
||||
public:
|
||||
Port() : _line_levels{High, High, High, High, High} {}
|
||||
Port() : line_levels_{High, High, High, High, High} {}
|
||||
|
||||
/*!
|
||||
Sets the current level of an output line on this serial port.
|
||||
*/
|
||||
void set_output(Line line, LineLevel level) {
|
||||
if(_line_levels[line] != level)
|
||||
if(line_levels_[line] != level)
|
||||
{
|
||||
_line_levels[line] = level;
|
||||
std::shared_ptr<Bus> bus = _serial_bus.lock();
|
||||
line_levels_[line] = level;
|
||||
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
||||
if(bus) bus->set_line_output_did_change(line);
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ namespace Serial {
|
||||
Gets the previously set level of an output line.
|
||||
*/
|
||||
LineLevel get_output(Line line) {
|
||||
return _line_levels[line];
|
||||
return line_levels_[line];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -102,12 +102,12 @@ namespace Serial {
|
||||
Sets the supplied serial bus as that to which line levels will be communicated.
|
||||
*/
|
||||
inline void set_serial_bus(std::shared_ptr<Bus> serial_bus) {
|
||||
_serial_bus = serial_bus;
|
||||
serial_bus_ = serial_bus;
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<Bus> _serial_bus;
|
||||
LineLevel _line_levels[5];
|
||||
std::weak_ptr<Bus> serial_bus_;
|
||||
LineLevel line_levels_[5];
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -117,12 +117,12 @@ namespace Serial {
|
||||
public:
|
||||
void set_input(Line line, LineLevel value);
|
||||
|
||||
DebugPort() : _incoming_count(0) {}
|
||||
DebugPort() : incoming_count_(0) {}
|
||||
|
||||
private:
|
||||
uint8_t _incoming_byte;
|
||||
int _incoming_count;
|
||||
LineLevel _input_levels[5];
|
||||
uint8_t incoming_byte_;
|
||||
int incoming_count_;
|
||||
LineLevel input_levels_[5];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,28 +15,28 @@
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
Machine::Machine() :
|
||||
_rom(nullptr),
|
||||
_is_running_at_zero_cost(false),
|
||||
_tape(1022727)
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(1022727)
|
||||
{
|
||||
// create 6522s, serial port and bus
|
||||
_userPortVIA.reset(new UserPortVIA);
|
||||
_keyboardVIA.reset(new KeyboardVIA);
|
||||
_serialPort.reset(new SerialPort);
|
||||
_serialBus.reset(new ::Commodore::Serial::Bus);
|
||||
user_port_via_.reset(new UserPortVIA);
|
||||
keyboard_via_.reset(new KeyboardVIA);
|
||||
serial_port_.reset(new SerialPort);
|
||||
serial_bus_.reset(new ::Commodore::Serial::Bus);
|
||||
|
||||
// wire up the serial bus and serial port
|
||||
Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus);
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||
|
||||
// wire up 6522s and serial port
|
||||
_userPortVIA->set_serial_port(_serialPort);
|
||||
_keyboardVIA->set_serial_port(_serialPort);
|
||||
_serialPort->set_user_port_via(_userPortVIA);
|
||||
user_port_via_->set_serial_port(serial_port_);
|
||||
keyboard_via_->set_serial_port(serial_port_);
|
||||
serial_port_->set_user_port_via(user_port_via_);
|
||||
|
||||
// wire up the 6522s, tape and machine
|
||||
_userPortVIA->set_interrupt_delegate(this);
|
||||
_keyboardVIA->set_interrupt_delegate(this);
|
||||
_tape.set_delegate(this);
|
||||
user_port_via_->set_interrupt_delegate(this);
|
||||
keyboard_via_->set_interrupt_delegate(this);
|
||||
tape_.set_delegate(this);
|
||||
|
||||
// establish the memory maps
|
||||
set_memory_size(MemorySize::Default);
|
||||
@ -44,44 +44,44 @@ Machine::Machine() :
|
||||
// set the NTSC clock rate
|
||||
set_region(NTSC);
|
||||
// _debugPort.reset(new ::Commodore::Serial::DebugPort);
|
||||
// _debugPort->set_serial_bus(_serialBus);
|
||||
// _serialBus->add_port(_debugPort);
|
||||
// _debugPort->set_serial_bus(serial_bus_);
|
||||
// serial_bus_->add_port(_debugPort);
|
||||
}
|
||||
|
||||
void Machine::set_memory_size(MemorySize size)
|
||||
{
|
||||
memset(_processorReadMemoryMap, 0, sizeof(_processorReadMemoryMap));
|
||||
memset(_processorWriteMemoryMap, 0, sizeof(_processorWriteMemoryMap));
|
||||
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||
|
||||
switch(size)
|
||||
{
|
||||
default: break;
|
||||
case ThreeKB:
|
||||
write_to_map(_processorReadMemoryMap, _expansionRAM, 0x0000, 0x1000);
|
||||
write_to_map(_processorWriteMemoryMap, _expansionRAM, 0x0000, 0x1000);
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
break;
|
||||
case ThirtyTwoKB:
|
||||
write_to_map(_processorReadMemoryMap, _expansionRAM, 0x0000, 0x8000);
|
||||
write_to_map(_processorWriteMemoryMap, _expansionRAM, 0x0000, 0x8000);
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000);
|
||||
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000);
|
||||
break;
|
||||
}
|
||||
|
||||
// install the system ROMs and VIC-visible memory
|
||||
write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
|
||||
write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
|
||||
write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
|
||||
write_to_map(_processorReadMemoryMap, _characterROM, 0x8000, sizeof(_characterROM));
|
||||
write_to_map(_processorReadMemoryMap, _basicROM, 0xc000, sizeof(_basicROM));
|
||||
write_to_map(_processorReadMemoryMap, _kernelROM, 0xe000, sizeof(_kernelROM));
|
||||
write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
||||
write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
||||
write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||
write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_));
|
||||
write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_));
|
||||
write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_));
|
||||
|
||||
write_to_map(_processorWriteMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
|
||||
write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
|
||||
write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
|
||||
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
||||
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
||||
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||
|
||||
// install the inserted ROM if there is one
|
||||
if(_rom)
|
||||
if(rom_)
|
||||
{
|
||||
write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length);
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint1
|
||||
|
||||
Machine::~Machine()
|
||||
{
|
||||
delete[] _rom;
|
||||
delete[] rom_;
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
@ -117,17 +117,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// }
|
||||
|
||||
// run the phase-1 part of this cycle, in which the VIC accesses memory
|
||||
if(!_is_running_at_zero_cost) _mos6560->run_for_cycles(1);
|
||||
if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1);
|
||||
|
||||
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
uint8_t result = _processorReadMemoryMap[address >> 10] ? _processorReadMemoryMap[address >> 10][address & 0x3ff] : 0xff;
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
if((address&0xff00) == 0x9000) result &= _mos6560->get_register(address);
|
||||
if((address&0xfc10) == 0x9010) result &= _userPortVIA->get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= _keyboardVIA->get_register(address);
|
||||
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
||||
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
|
||||
}
|
||||
*value = result;
|
||||
|
||||
@ -135,40 +135,40 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
|
||||
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
|
||||
// CPU or 6560 costs.
|
||||
if(_use_fast_tape_hack && _tape.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
while(!_userPortVIA->get_interrupt_line() && !_keyboardVIA->get_interrupt_line() && !_tape.get_tape()->is_at_end())
|
||||
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end())
|
||||
{
|
||||
_userPortVIA->run_for_cycles(1);
|
||||
_keyboardVIA->run_for_cycles(1);
|
||||
_tape.run_for_cycles(1);
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
tape_.run_for_cycles(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t *ram = _processorWriteMemoryMap[address >> 10];
|
||||
uint8_t *ram = processor_write_memory_map_[address >> 10];
|
||||
if(ram) ram[address & 0x3ff] = *value;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
if((address&0xff00) == 0x9000) _mos6560->set_register(address, *value);
|
||||
if((address&0xfc10) == 0x9010) _userPortVIA->set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) _keyboardVIA->set_register(address, *value);
|
||||
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
||||
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
_userPortVIA->run_for_cycles(1);
|
||||
_keyboardVIA->run_for_cycles(1);
|
||||
if(_typer && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
|
||||
{
|
||||
if(!_typer->type_next_character())
|
||||
if(!typer_->type_next_character())
|
||||
{
|
||||
clear_all_keys();
|
||||
_typer.reset();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
_tape.run_for_cycles(1);
|
||||
if(_c1540) _c1540->run_for_cycles(1);
|
||||
tape_.run_for_cycles(1);
|
||||
if(c1540_) c1540_->run_for_cycles(1);
|
||||
|
||||
// If using fast tape then:
|
||||
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
|
||||
@ -181,19 +181,19 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
|
||||
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
|
||||
// the CPU out of the action.
|
||||
if(_use_fast_tape_hack && _tape.has_tape())
|
||||
if(use_fast_tape_hack_ && tape_.has_tape())
|
||||
{
|
||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
_is_running_at_zero_cost = true;
|
||||
is_running_at_zero_cost_ = true;
|
||||
set_clock_is_unlimited(true);
|
||||
}
|
||||
if(
|
||||
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
||||
_tape.get_tape()->is_at_end()
|
||||
tape_.get_tape()->is_at_end()
|
||||
)
|
||||
{
|
||||
_is_running_at_zero_cost = false;
|
||||
is_running_at_zero_cost_ = false;
|
||||
set_clock_is_unlimited(false);
|
||||
}
|
||||
}
|
||||
@ -205,31 +205,31 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
set_nmi_line(_userPortVIA->get_interrupt_line());
|
||||
set_irq_line(_keyboardVIA->get_interrupt_line());
|
||||
set_nmi_line(user_port_via_->get_interrupt_line());
|
||||
set_irq_line(keyboard_via_->get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
void Machine::set_region(Commodore::Vic20::Region region)
|
||||
{
|
||||
_region = region;
|
||||
region_ = region;
|
||||
switch(region)
|
||||
{
|
||||
case PAL:
|
||||
set_clock_rate(1108404);
|
||||
if(_mos6560)
|
||||
if(mos6560_)
|
||||
{
|
||||
_mos6560->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||
_mos6560->set_clock_rate(1108404);
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||
mos6560_->set_clock_rate(1108404);
|
||||
}
|
||||
break;
|
||||
case NTSC:
|
||||
set_clock_rate(1022727);
|
||||
if(_mos6560)
|
||||
if(mos6560_)
|
||||
{
|
||||
_mos6560->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||
_mos6560->set_clock_rate(1022727);
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||
mos6560_->set_clock_rate(1022727);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -237,20 +237,20 @@ void Machine::set_region(Commodore::Vic20::Region region)
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
_mos6560.reset(new Vic6560());
|
||||
_mos6560->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
set_region(_region);
|
||||
mos6560_.reset(new Vic6560());
|
||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
set_region(region_);
|
||||
|
||||
memset(_mos6560->_videoMemoryMap, 0, sizeof(_mos6560->_videoMemoryMap));
|
||||
write_to_map(_mos6560->_videoMemoryMap, _characterROM, 0x0000, sizeof(_characterROM));
|
||||
write_to_map(_mos6560->_videoMemoryMap, _userBASICMemory, 0x2000, sizeof(_userBASICMemory));
|
||||
write_to_map(_mos6560->_videoMemoryMap, _screenMemory, 0x3000, sizeof(_screenMemory));
|
||||
_mos6560->_colorMemory = _colorMemory;
|
||||
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
|
||||
write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_));
|
||||
write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_));
|
||||
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_));
|
||||
mos6560_->colour_memory = colour_memory_;
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
_mos6560 = nullptr;
|
||||
mos6560_ = nullptr;
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
@ -259,12 +259,12 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
size_t max_length = 0x2000;
|
||||
switch(slot)
|
||||
{
|
||||
case Kernel: target = _kernelROM; break;
|
||||
case Characters: target = _characterROM; max_length = 0x1000; break;
|
||||
case BASIC: target = _basicROM; break;
|
||||
case Kernel: target = kernel_rom_; break;
|
||||
case Characters: target = character_rom_; max_length = 0x1000; break;
|
||||
case BASIC: target = basic_rom_; break;
|
||||
case Drive:
|
||||
_driveROM.reset(new uint8_t[length]);
|
||||
memcpy(_driveROM.get(), data, length);
|
||||
drive_rom_.reset(new uint8_t[length]);
|
||||
memcpy(drive_rom_.get(), data, length);
|
||||
install_disk_rom();
|
||||
return;
|
||||
}
|
||||
@ -288,7 +288,7 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
// {
|
||||
// _rom = new uint8_t[0x2000];
|
||||
// memcpy(_rom, &data[2], length - 2);
|
||||
// write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000);
|
||||
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
@ -303,19 +303,19 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
_tape.set_tape(target.tapes.front());
|
||||
tape_.set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.disks.size())
|
||||
{
|
||||
// construct the 1540
|
||||
_c1540.reset(new ::Commodore::C1540::Machine);
|
||||
c1540_.reset(new ::Commodore::C1540::Machine);
|
||||
|
||||
// attach it to the serial bus
|
||||
_c1540->set_serial_bus(_serialBus);
|
||||
c1540_->set_serial_bus(serial_bus_);
|
||||
|
||||
// hand it the disk
|
||||
_c1540->set_disk(target.disks.front());
|
||||
c1540_->set_disk(target.disks.front());
|
||||
|
||||
// install the ROM if it was previously set
|
||||
install_disk_rom();
|
||||
@ -323,16 +323,16 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
|
||||
if(target.cartridges.size())
|
||||
{
|
||||
_rom_address = 0xa000;
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
||||
_rom_length = (uint16_t)(rom_image.size());
|
||||
rom_length_ = (uint16_t)(rom_image.size());
|
||||
|
||||
_rom = new uint8_t[0x2000];
|
||||
memcpy(_rom, rom_image.data(), rom_image.size());
|
||||
write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000);
|
||||
rom_ = new uint8_t[0x2000];
|
||||
memcpy(rom_, rom_image.data(), rom_image.size());
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||
}
|
||||
|
||||
if(_should_automatically_load_media)
|
||||
if(should_automatically_load_media_)
|
||||
{
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
@ -356,18 +356,18 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape)
|
||||
{
|
||||
_keyboardVIA->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||
}
|
||||
|
||||
#pragma mark - Disc
|
||||
|
||||
void Machine::install_disk_rom()
|
||||
{
|
||||
if(_driveROM && _c1540)
|
||||
if(drive_rom_ && c1540_)
|
||||
{
|
||||
_c1540->set_rom(_driveROM.get());
|
||||
_c1540->run_for_cycles(2000000);
|
||||
_driveROM.reset();
|
||||
c1540_->set_rom(drive_rom_.get());
|
||||
c1540_->run_for_cycles(2000000);
|
||||
drive_rom_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +377,7 @@ uint8_t UserPortVIA::get_port_input(Port port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
return _portA; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
@ -394,8 +394,8 @@ void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool val
|
||||
switch(line)
|
||||
{
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: _portA = (_portA & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Clock: _portA = (_portA & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input != JoystickInput::Right)
|
||||
{
|
||||
_portA = (_portA & ~input) | (value ? 0 : input);
|
||||
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,22 +412,22 @@ void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
// Line 7 of port A is inverted and output as serial ATN
|
||||
if(!port)
|
||||
{
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
||||
}
|
||||
}
|
||||
|
||||
UserPortVIA::UserPortVIA() : _portA(0xbf) {}
|
||||
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
|
||||
|
||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
_serialPort = serialPort;
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
#pragma mark - KeyboardVIA
|
||||
|
||||
KeyboardVIA::KeyboardVIA() : _portB(0xff)
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff)
|
||||
{
|
||||
clear_all_keys();
|
||||
}
|
||||
@ -435,14 +435,14 @@ KeyboardVIA::KeyboardVIA() : _portB(0xff)
|
||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
if(isPressed)
|
||||
_columns[key & 7] &= ~(key >> 3);
|
||||
columns_[key & 7] &= ~(key >> 3);
|
||||
else
|
||||
_columns[key & 7] |= (key >> 3);
|
||||
columns_[key & 7] |= (key >> 3);
|
||||
}
|
||||
|
||||
void KeyboardVIA::clear_all_keys()
|
||||
{
|
||||
memset(_columns, 0xff, sizeof(_columns));
|
||||
memset(columns_, 0xff, sizeof(columns_));
|
||||
}
|
||||
|
||||
uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
@ -452,26 +452,26 @@ uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
uint8_t result = 0xff;
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
if(!(_activation_mask&(1 << c)))
|
||||
result &= _columns[c];
|
||||
if(!(activation_mask_&(1 << c)))
|
||||
result &= columns_[c];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return _portB;
|
||||
return port_b_;
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
if(port)
|
||||
_activation_mask = (value & mask) | (~mask);
|
||||
activation_mask_ = (value & mask) | (~mask);
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
if(line == Line::Two)
|
||||
{
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
@ -487,24 +487,24 @@ void KeyboardVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input == JoystickInput::Right)
|
||||
{
|
||||
_portB = (_portB & ~input) | (value ? 0 : input);
|
||||
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
_serialPort = serialPort;
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
#pragma mark - SerialPort
|
||||
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level)
|
||||
{
|
||||
std::shared_ptr<UserPortVIA> userPortVIA = _userPortVIA.lock();
|
||||
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
||||
}
|
||||
|
||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA)
|
||||
{
|
||||
_userPortVIA = userPortVIA;
|
||||
user_port_via_ = userPortVIA;
|
||||
}
|
||||
|
@ -89,8 +89,8 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
||||
|
||||
private:
|
||||
uint8_t _portA;
|
||||
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
|
||||
uint8_t port_a_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
};
|
||||
|
||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||
@ -112,10 +112,10 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
||||
|
||||
private:
|
||||
uint8_t _portB;
|
||||
uint8_t _columns[8];
|
||||
uint8_t _activation_mask;
|
||||
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
|
||||
uint8_t port_b_;
|
||||
uint8_t columns_[8];
|
||||
uint8_t activation_mask_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
};
|
||||
|
||||
class SerialPort : public ::Commodore::Serial::Port {
|
||||
@ -124,19 +124,19 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA);
|
||||
|
||||
private:
|
||||
std::weak_ptr<UserPortVIA> _userPortVIA;
|
||||
std::weak_ptr<UserPortVIA> user_port_via_;
|
||||
};
|
||||
|
||||
class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||
public:
|
||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data)
|
||||
{
|
||||
*pixel_data = _videoMemoryMap[address >> 10] ? _videoMemoryMap[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||
*colour_data = _colorMemory[address & 0x03ff];
|
||||
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||
*colour_data = colour_memory[address & 0x03ff];
|
||||
}
|
||||
|
||||
uint8_t *_videoMemoryMap[16];
|
||||
uint8_t *_colorMemory;
|
||||
uint8_t *video_memory_map[16];
|
||||
uint8_t *colour_memory;
|
||||
};
|
||||
|
||||
class Machine:
|
||||
@ -153,34 +153,30 @@ class Machine:
|
||||
|
||||
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
// void set_prg(const char *file_name, size_t length, const uint8_t *data);
|
||||
// void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
|
||||
// void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) { _keyboardVIA->set_key_state(key, isPressed); }
|
||||
void clear_all_keys() { _keyboardVIA->clear_all_keys(); }
|
||||
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
|
||||
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
|
||||
void set_joystick_state(JoystickInput input, bool isPressed) {
|
||||
_userPortVIA->set_joystick_state(input, isPressed);
|
||||
_keyboardVIA->set_joystick_state(input, isPressed);
|
||||
user_port_via_->set_joystick_state(input, isPressed);
|
||||
keyboard_via_->set_joystick_state(input, isPressed);
|
||||
}
|
||||
|
||||
void set_memory_size(MemorySize size);
|
||||
void set_region(Region region);
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; }
|
||||
inline void set_should_automatically_load_media(bool activate) { _should_automatically_load_media = activate; }
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
inline void set_should_automatically_load_media(bool activate) { should_automatically_load_media_ = activate; }
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise() { _mos6560->synchronise(); }
|
||||
void synchronise() { mos6560_->synchronise(); }
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _mos6560->get_crt(); }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _mos6560->get_speaker(); }
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
// TODO: or 1108405 for PAL; see http://www.antimon.org/dl/c64/code/stable.txt
|
||||
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
@ -192,40 +188,38 @@ class Machine:
|
||||
virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape);
|
||||
|
||||
private:
|
||||
uint8_t _characterROM[0x1000];
|
||||
uint8_t _basicROM[0x2000];
|
||||
uint8_t _kernelROM[0x2000];
|
||||
uint8_t _expansionRAM[0x8000];
|
||||
uint8_t character_rom_[0x1000];
|
||||
uint8_t basic_rom_[0x2000];
|
||||
uint8_t kernel_rom_[0x2000];
|
||||
uint8_t expansion_ram_[0x8000];
|
||||
|
||||
uint8_t *_rom;
|
||||
uint16_t _rom_address, _rom_length;
|
||||
uint8_t *rom_;
|
||||
uint16_t rom_address_, rom_length_;
|
||||
|
||||
uint8_t _userBASICMemory[0x0400];
|
||||
uint8_t _screenMemory[0x1000];
|
||||
uint8_t _colorMemory[0x0400];
|
||||
uint8_t _junkMemory[0x0400];
|
||||
std::unique_ptr<uint8_t> _driveROM;
|
||||
uint8_t user_basic_memory_[0x0400];
|
||||
uint8_t screen_memory_[0x1000];
|
||||
uint8_t colour_memory_[0x0400];
|
||||
std::unique_ptr<uint8_t> drive_rom_;
|
||||
|
||||
uint8_t *_processorReadMemoryMap[64];
|
||||
uint8_t *_processorWriteMemoryMap[64];
|
||||
uint8_t *processor_read_memory_map_[64];
|
||||
uint8_t *processor_write_memory_map_[64];
|
||||
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length);
|
||||
|
||||
Region _region;
|
||||
Region region_;
|
||||
|
||||
std::unique_ptr<Vic6560> _mos6560;
|
||||
std::shared_ptr<UserPortVIA> _userPortVIA;
|
||||
std::shared_ptr<KeyboardVIA> _keyboardVIA;
|
||||
std::shared_ptr<SerialPort> _serialPort;
|
||||
std::shared_ptr<::Commodore::Serial::Bus> _serialBus;
|
||||
// std::shared_ptr<::Commodore::Serial::DebugPort> _debugPort;
|
||||
std::unique_ptr<Vic6560> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
|
||||
// Tape
|
||||
Storage::Tape::BinaryTapePlayer _tape;
|
||||
bool _use_fast_tape_hack, _should_automatically_load_media;
|
||||
bool _is_running_at_zero_cost;
|
||||
Storage::Tape::BinaryTapePlayer tape_;
|
||||
bool use_fast_tape_hack_, should_automatically_load_media_;
|
||||
bool is_running_at_zero_cost_;
|
||||
|
||||
// Disk
|
||||
std::shared_ptr<::Commodore::C1540::Machine> _c1540;
|
||||
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
||||
void install_disk_rom();
|
||||
|
||||
// Autoload string
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,9 @@
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../Typer.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@ -35,15 +38,6 @@ enum ROMSlot: uint8_t {
|
||||
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
|
||||
};
|
||||
|
||||
enum Interrupt: uint8_t {
|
||||
PowerOnReset = 0x02,
|
||||
DisplayEnd = 0x04,
|
||||
RealTimeClock = 0x08,
|
||||
ReceiveDataFull = 0x10,
|
||||
TransmitDataEmpty = 0x20,
|
||||
HighToneDetect = 0x40
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
|
||||
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
|
||||
@ -65,72 +59,6 @@ enum Key: uint16_t {
|
||||
TerminateSequence = 0xffff, NotMapped = 0xfffe,
|
||||
};
|
||||
|
||||
class Tape: public Storage::Tape::TapePlayer {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
inline void run_for_cycles(unsigned int number_of_cycles);
|
||||
|
||||
inline uint8_t get_data_register();
|
||||
inline void set_data_register(uint8_t value);
|
||||
inline void set_counter(uint8_t value);
|
||||
|
||||
inline uint8_t get_interrupt_status() { return _interrupt_status; }
|
||||
inline void clear_interrupts(uint8_t interrupts);
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { _delegate = delegate; }
|
||||
|
||||
inline void set_is_running(bool is_running) { _is_running = is_running; }
|
||||
inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; }
|
||||
inline void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
private:
|
||||
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
struct {
|
||||
int minimum_bits_until_full;
|
||||
} _input;
|
||||
struct {
|
||||
unsigned int cycles_into_pulse;
|
||||
unsigned int bits_remaining_until_empty;
|
||||
} _output;
|
||||
|
||||
bool _is_running;
|
||||
bool _is_enabled;
|
||||
bool _is_in_input_mode;
|
||||
|
||||
inline void evaluate_interrupts();
|
||||
uint16_t _data_register;
|
||||
|
||||
uint8_t _interrupt_status, _last_posted_interrupt_status;
|
||||
Delegate *_delegate;
|
||||
|
||||
enum {
|
||||
Long, Short, Unrecognised, Recognised
|
||||
} _crossings[4];
|
||||
};
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
void set_divider(uint8_t divider);
|
||||
|
||||
void set_is_enabled(bool is_enabled);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
|
||||
private:
|
||||
unsigned int _counter;
|
||||
unsigned int _divider;
|
||||
bool _is_enabled;
|
||||
};
|
||||
|
||||
/*!
|
||||
@abstract Represents an Acorn Electron.
|
||||
|
||||
@ -152,7 +80,7 @@ class Machine:
|
||||
void set_key_state(uint16_t key, bool isPressed);
|
||||
void clear_all_keys();
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; }
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
@ -164,8 +92,8 @@ class Machine:
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
|
||||
// to satisfy Tape::Delegate
|
||||
@ -189,25 +117,25 @@ class Machine:
|
||||
inline void evaluate_interrupts();
|
||||
|
||||
// Things that directly constitute the memory map.
|
||||
uint8_t _roms[16][16384];
|
||||
bool _rom_write_masks[16];
|
||||
uint8_t _os[16384], _ram[32768];
|
||||
std::vector<uint8_t> _dfs, _adfs;
|
||||
uint8_t roms_[16][16384];
|
||||
bool rom_write_masks_[16];
|
||||
uint8_t os_[16384], ram_[32768];
|
||||
std::vector<uint8_t> dfs_, adfs_;
|
||||
|
||||
// Things affected by registers, explicitly or otherwise.
|
||||
uint8_t _interrupt_status, _interrupt_control;
|
||||
uint8_t _palette[16];
|
||||
uint8_t _key_states[14];
|
||||
ROMSlot _active_rom;
|
||||
bool _keyboard_is_active, _basic_is_active;
|
||||
uint8_t _screen_mode;
|
||||
uint16_t _screenModeBaseAddress;
|
||||
uint16_t _startScreenAddress;
|
||||
uint8_t interrupt_status_, interrupt_control_;
|
||||
uint8_t palette_[16];
|
||||
uint8_t key_states_[14];
|
||||
ROMSlot active_rom_;
|
||||
bool keyboard_is_active_, basic_is_active_;
|
||||
uint8_t screen_mode_;
|
||||
uint16_t screen_mode_base_address_;
|
||||
uint16_t start_screen_address_;
|
||||
|
||||
// Counters related to simultaneous subsystems
|
||||
unsigned int _frameCycles, _displayOutputPosition;
|
||||
unsigned int _audioOutputPosition, _audioOutputPositionError;
|
||||
uint8_t _phase;
|
||||
unsigned int frame_cycles_, display_output_position_;
|
||||
unsigned int audio_output_position_, audio_output_position_error_;
|
||||
uint8_t phase_;
|
||||
|
||||
struct {
|
||||
uint16_t forty1bpp[256];
|
||||
@ -215,30 +143,30 @@ class Machine:
|
||||
uint32_t eighty1bpp[256];
|
||||
uint16_t eighty2bpp[256];
|
||||
uint8_t eighty4bpp[256];
|
||||
} _paletteTables;
|
||||
} palette_tables_;
|
||||
|
||||
// Display generation.
|
||||
uint16_t _startLineAddress, _currentScreenAddress;
|
||||
int _current_pixel_line, _current_pixel_column, _current_character_row;
|
||||
uint8_t _last_pixel_byte;
|
||||
bool _isBlankLine;
|
||||
uint16_t start_line_address_, current_screen_address_;
|
||||
int current_pixel_line_, current_pixel_column_, current_character_row_;
|
||||
uint8_t last_pixel_byte_;
|
||||
bool is_blank_line_;
|
||||
|
||||
// CRT output
|
||||
uint8_t *_current_output_target, *_initial_output_target;
|
||||
unsigned int _current_output_divider;
|
||||
uint8_t *current_output_target_, *initial_output_target_;
|
||||
unsigned int current_output_divider_;
|
||||
|
||||
// Tape
|
||||
Tape _tape;
|
||||
bool _use_fast_tape_hack;
|
||||
bool _fast_load_is_in_data;
|
||||
Tape tape_;
|
||||
bool use_fast_tape_hack_;
|
||||
bool fast_load_is_in_data_;
|
||||
|
||||
// Disk
|
||||
std::unique_ptr<Plus3> _plus3;
|
||||
std::unique_ptr<Plus3> plus3_;
|
||||
bool is_holding_shift_;
|
||||
|
||||
// Outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> _crt;
|
||||
std::shared_ptr<Speaker> _speaker;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
bool speaker_is_enabled_;
|
||||
};
|
||||
|
||||
|
27
Machines/Electron/Interrupts.hpp
Normal file
27
Machines/Electron/Interrupts.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Interrupts.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Interrupts_h
|
||||
#define Interrupts_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
enum Interrupt: uint8_t {
|
||||
PowerOnReset = 0x02,
|
||||
DisplayEnd = 0x04,
|
||||
RealTimeClock = 0x08,
|
||||
ReceiveDataFull = 0x10,
|
||||
TransmitDataEmpty = 0x20,
|
||||
HighToneDetect = 0x40
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Interrupts_h */
|
48
Machines/Electron/Speaker.cpp
Normal file
48
Machines/Electron/Speaker.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Speaker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Speaker.hpp"
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
while(number_of_samples--)
|
||||
{
|
||||
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
||||
target++;
|
||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(target, 0, sizeof(int16_t) * number_of_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples)
|
||||
{
|
||||
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
void Speaker::set_divider(uint8_t divider)
|
||||
{
|
||||
enqueue([=]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_is_enabled(bool is_enabled)
|
||||
{
|
||||
enqueue([=]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
});
|
||||
}
|
35
Machines/Electron/Speaker.hpp
Normal file
35
Machines/Electron/Speaker.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Electron_Speaker_hpp
|
||||
#define Electron_Speaker_hpp
|
||||
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
void set_divider(uint8_t divider);
|
||||
|
||||
void set_is_enabled(bool is_enabled);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
|
||||
static const unsigned int clock_rate_divider = 8;
|
||||
|
||||
private:
|
||||
unsigned int counter_;
|
||||
unsigned int divider_;
|
||||
bool is_enabled_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
135
Machines/Electron/Tape.cpp
Normal file
135
Machines/Electron/Tape.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// Tape.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Tape::Tape() :
|
||||
TapePlayer(2000000),
|
||||
is_running_(false),
|
||||
data_register_(0),
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0)
|
||||
{}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit)
|
||||
{
|
||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
||||
|
||||
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
|
||||
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
||||
if(!input_.minimum_bits_until_full)
|
||||
{
|
||||
if((data_register_&0x3) == 0x1)
|
||||
{
|
||||
interrupt_status_ |= Interrupt::ReceiveDataFull;
|
||||
if(is_in_input_mode_) input_.minimum_bits_until_full = 9;
|
||||
}
|
||||
}
|
||||
|
||||
if(output_.bits_remaining_until_empty) output_.bits_remaining_until_empty--;
|
||||
if(!output_.bits_remaining_until_empty) interrupt_status_ |= Interrupt::TransmitDataEmpty;
|
||||
|
||||
if(data_register_ == 0x3ff) interrupt_status_ |= Interrupt::HighToneDetect;
|
||||
else interrupt_status_ &= ~Interrupt::HighToneDetect;
|
||||
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::evaluate_interrupts()
|
||||
{
|
||||
if(last_posted_interrupt_status_ != interrupt_status_)
|
||||
{
|
||||
last_posted_interrupt_status_ = interrupt_status_;
|
||||
if(delegate_) delegate_->tape_did_change_interrupt_status(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::clear_interrupts(uint8_t interrupts)
|
||||
{
|
||||
interrupt_status_ &= ~interrupts;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::set_is_in_input_mode(bool is_in_input_mode)
|
||||
{
|
||||
is_in_input_mode_ = is_in_input_mode;
|
||||
}
|
||||
|
||||
void Tape::set_counter(uint8_t value)
|
||||
{
|
||||
output_.cycles_into_pulse = 0;
|
||||
output_.bits_remaining_until_empty = 0;
|
||||
}
|
||||
|
||||
void Tape::set_data_register(uint8_t value)
|
||||
{
|
||||
data_register_ = (uint16_t)((value << 2) | 1);
|
||||
output_.bits_remaining_until_empty = 9;
|
||||
}
|
||||
|
||||
uint8_t Tape::get_data_register()
|
||||
{
|
||||
return (uint8_t)(data_register_ >> 2);
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
crossings_[0] = crossings_[1];
|
||||
crossings_[1] = crossings_[2];
|
||||
crossings_[2] = crossings_[3];
|
||||
|
||||
crossings_[3] = Tape::Unrecognised;
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero)
|
||||
{
|
||||
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
|
||||
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
|
||||
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
|
||||
}
|
||||
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long)
|
||||
{
|
||||
push_tape_bit(0);
|
||||
crossings_[0] = crossings_[1] = Tape::Recognised;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short)
|
||||
{
|
||||
push_tape_bit(1);
|
||||
crossings_[0] = crossings_[1] =
|
||||
crossings_[2] = crossings_[3] = Tape::Recognised;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
if(is_in_input_mode_)
|
||||
{
|
||||
if(is_running_)
|
||||
{
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output_.cycles_into_pulse += number_of_cycles;
|
||||
while(output_.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||
{ // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
output_.cycles_into_pulse -= 1664;
|
||||
push_tape_bit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Machines/Electron/Tape.hpp
Normal file
72
Machines/Electron/Tape.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Tape.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Electron_Tape_h
|
||||
#define Electron_Tape_h
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Tape: public Storage::Tape::TapePlayer {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
|
||||
uint8_t get_data_register();
|
||||
void set_data_register(uint8_t value);
|
||||
void set_counter(uint8_t value);
|
||||
|
||||
inline uint8_t get_interrupt_status() { return interrupt_status_; }
|
||||
void clear_interrupts(uint8_t interrupts);
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
inline void set_is_running(bool is_running) { is_running_ = is_running; }
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
private:
|
||||
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
struct {
|
||||
int minimum_bits_until_full;
|
||||
} input_;
|
||||
struct {
|
||||
unsigned int cycles_into_pulse;
|
||||
unsigned int bits_remaining_until_empty;
|
||||
} output_;
|
||||
|
||||
bool is_running_;
|
||||
bool is_enabled_;
|
||||
bool is_in_input_mode_;
|
||||
|
||||
inline void evaluate_interrupts();
|
||||
uint16_t data_register_;
|
||||
|
||||
uint8_t interrupt_status_, last_posted_interrupt_status_;
|
||||
Delegate *delegate_;
|
||||
|
||||
enum {
|
||||
Long, Short, Unrecognised, Recognised
|
||||
} crossings_[4];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Electron_Tape_h */
|
@ -140,15 +140,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
}
|
||||
}
|
||||
|
||||
if(_typer && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
|
||||
// do anything until at least the second, regardless of machine
|
||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||
else if(!_typer->type_next_character())
|
||||
else if(!typer_->type_next_character())
|
||||
{
|
||||
clear_all_keys();
|
||||
_typer.reset();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,32 +12,32 @@
|
||||
using namespace Utility;
|
||||
|
||||
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
|
||||
_counter(-delay), _frequency(frequency), _string_pointer(0), _delegate(delegate), _phase(0)
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0)
|
||||
{
|
||||
size_t string_size = strlen(string) + 3;
|
||||
_string = (char *)malloc(string_size);
|
||||
snprintf(_string, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
string_ = (char *)malloc(string_size);
|
||||
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
}
|
||||
|
||||
void Typer::update(int duration)
|
||||
{
|
||||
if(_string)
|
||||
if(string_)
|
||||
{
|
||||
if(_counter < 0 && _counter + duration >= 0)
|
||||
if(counter_ < 0 && counter_ + duration >= 0)
|
||||
{
|
||||
if(!type_next_character())
|
||||
{
|
||||
_delegate->typer_reset(this);
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
|
||||
_counter += duration;
|
||||
while(_string && _counter > _frequency)
|
||||
counter_ += duration;
|
||||
while(string_ && counter_ > frequency_)
|
||||
{
|
||||
_counter -= _frequency;
|
||||
counter_ -= frequency_;
|
||||
if(!type_next_character())
|
||||
{
|
||||
_delegate->typer_reset(this);
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,23 +45,23 @@ void Typer::update(int duration)
|
||||
|
||||
bool Typer::type_next_character()
|
||||
{
|
||||
if(_string == nullptr) return false;
|
||||
if(string_ == nullptr) return false;
|
||||
|
||||
if(_delegate->typer_set_next_character(this, _string[_string_pointer], _phase))
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_))
|
||||
{
|
||||
_phase = 0;
|
||||
if(!_string[_string_pointer])
|
||||
phase_ = 0;
|
||||
if(!string_[string_pointer_])
|
||||
{
|
||||
free(_string);
|
||||
_string = nullptr;
|
||||
free(string_);
|
||||
string_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
_string_pointer++;
|
||||
string_pointer_++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_phase++;
|
||||
phase_++;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -69,7 +69,7 @@ bool Typer::type_next_character()
|
||||
|
||||
Typer::~Typer()
|
||||
{
|
||||
free(_string);
|
||||
free(string_);
|
||||
}
|
||||
|
||||
#pragma mark - Delegate
|
||||
|
@ -35,31 +35,31 @@ class Typer {
|
||||
const char EndString = 0x03; // i.e. ASCII end of text
|
||||
|
||||
private:
|
||||
char *_string;
|
||||
int _frequency;
|
||||
int _counter;
|
||||
int _phase;
|
||||
Delegate *_delegate;
|
||||
size_t _string_pointer;
|
||||
char *string_;
|
||||
int frequency_;
|
||||
int counter_;
|
||||
int phase_;
|
||||
Delegate *delegate_;
|
||||
size_t string_pointer_;
|
||||
};
|
||||
|
||||
class TypeRecipient: public Typer::Delegate {
|
||||
public:
|
||||
void set_typer_for_string(const char *string)
|
||||
{
|
||||
_typer.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
|
||||
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
|
||||
}
|
||||
|
||||
void typer_reset(Typer *typer)
|
||||
{
|
||||
clear_all_keys();
|
||||
_typer.reset();
|
||||
typer_.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual int get_typer_delay() { return 0; }
|
||||
virtual int get_typer_frequency() { return 0; }
|
||||
std::unique_ptr<Typer> _typer;
|
||||
std::unique_ptr<Typer> typer_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -383,6 +383,9 @@
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
@ -892,6 +895,14 @@
|
||||
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
|
||||
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
|
||||
4BEA52611DF339D7007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = Electron/Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52621DF339D7007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = Electron/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
@ -1042,8 +1053,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
||||
);
|
||||
path = Atari2600;
|
||||
sourceTree = "<group>";
|
||||
@ -1052,10 +1066,15 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2E2D9B1C3A070400138695 /* Electron.cpp */,
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
|
||||
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */,
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
|
||||
4BEA52611DF339D7007E74F2 /* Speaker.cpp */,
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */,
|
||||
4BC8A62B1DCE60E000DAC693 /* Typer.cpp */,
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */,
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
|
||||
4BEA52621DF339D7007E74F2 /* Speaker.hpp */,
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */,
|
||||
);
|
||||
name = Electron;
|
||||
sourceTree = "<group>";
|
||||
@ -2314,6 +2333,7 @@
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
|
||||
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
@ -2335,6 +2355,7 @@
|
||||
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */,
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
@ -2378,6 +2399,7 @@
|
||||
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
|
||||
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
|
||||
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,
|
||||
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
|
||||
|
@ -40,13 +40,13 @@ class Speaker {
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
{
|
||||
// return twice the cut off, if applicable
|
||||
if(_high_frequency_cut_off > 0.0f && _input_cycles_per_second >= _high_frequency_cut_off * 3.0f && _input_cycles_per_second <= _high_frequency_cut_off * 3.0f) return _high_frequency_cut_off * 3.0f;
|
||||
if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f;
|
||||
|
||||
// return exactly the input rate if possible
|
||||
if(_input_cycles_per_second >= minimum && _input_cycles_per_second <= maximum) return _input_cycles_per_second;
|
||||
if(input_cycles_per_second_ >= minimum && input_cycles_per_second_ <= maximum) return input_cycles_per_second_;
|
||||
|
||||
// if the input rate is lower, return the minimum
|
||||
if(_input_cycles_per_second < minimum) return minimum;
|
||||
if(input_cycles_per_second_ < minimum) return minimum;
|
||||
|
||||
// otherwise, return the maximum
|
||||
return maximum;
|
||||
@ -54,29 +54,29 @@ class Speaker {
|
||||
|
||||
void set_output_rate(float cycles_per_second, int buffer_size)
|
||||
{
|
||||
_output_cycles_per_second = cycles_per_second;
|
||||
if(_buffer_size != buffer_size)
|
||||
output_cycles_per_second_ = cycles_per_second;
|
||||
if(buffer_size_ != buffer_size)
|
||||
{
|
||||
_buffer_in_progress.reset(new int16_t[buffer_size]);
|
||||
_buffer_size = buffer_size;
|
||||
buffer_in_progress_.reset(new int16_t[buffer_size]);
|
||||
buffer_size_ = buffer_size;
|
||||
}
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_output_quality(int number_of_taps)
|
||||
{
|
||||
_requested_number_of_taps = number_of_taps;
|
||||
requested_number_of_taps_ = number_of_taps;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void set_input_rate(float cycles_per_second)
|
||||
{
|
||||
_input_cycles_per_second = cycles_per_second;
|
||||
input_cycles_per_second_ = cycles_per_second;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
@ -85,19 +85,19 @@ class Speaker {
|
||||
*/
|
||||
void set_high_frequency_cut_off(float high_frequency)
|
||||
{
|
||||
_high_frequency_cut_off = high_frequency;
|
||||
high_frequency_cut_off_ = high_frequency;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0), _high_frequency_cut_off(-1.0), _queue(new Concurrency::AsyncTaskQueue) {}
|
||||
Speaker() : buffer_in_progress_pointer_(0), requested_number_of_taps_(0), high_frequency_cut_off_(-1.0), _queue(new Concurrency::AsyncTaskQueue) {}
|
||||
|
||||
/*!
|
||||
Ensures any deferred processing occurs now.
|
||||
*/
|
||||
void flush()
|
||||
{
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = _queued_functions;
|
||||
_queued_functions.reset();
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
|
||||
queued_functions_.reset();
|
||||
_queue->enqueue([queued_functions] {
|
||||
for(auto function : *queued_functions)
|
||||
{
|
||||
@ -109,24 +109,24 @@ class Speaker {
|
||||
protected:
|
||||
void enqueue(std::function<void(void)> function)
|
||||
{
|
||||
if(!_queued_functions) _queued_functions.reset(new std::list<std::function<void(void)>>);
|
||||
_queued_functions->push_back(function);
|
||||
if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>);
|
||||
queued_functions_->push_back(function);
|
||||
}
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> _queued_functions;
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions_;
|
||||
|
||||
std::unique_ptr<int16_t> _buffer_in_progress;
|
||||
float _high_frequency_cut_off;
|
||||
int _buffer_size;
|
||||
int _buffer_in_progress_pointer;
|
||||
int _number_of_taps, _requested_number_of_taps;
|
||||
bool _coefficients_are_dirty;
|
||||
Delegate *_delegate;
|
||||
std::unique_ptr<int16_t> buffer_in_progress_;
|
||||
float high_frequency_cut_off_;
|
||||
int buffer_size_;
|
||||
int buffer_in_progress_pointer_;
|
||||
int number_of_taps_, requested_number_of_taps_;
|
||||
bool coefficients_are_dirty_;
|
||||
Delegate *delegate_;
|
||||
|
||||
float _input_cycles_per_second, _output_cycles_per_second;
|
||||
float input_cycles_per_second_, output_cycles_per_second_;
|
||||
|
||||
void set_needs_updated_filter_coefficients()
|
||||
{
|
||||
_coefficients_are_dirty = true;
|
||||
coefficients_are_dirty_ = true;
|
||||
}
|
||||
|
||||
void get_samples(unsigned int quantity, int16_t *target) {}
|
||||
@ -160,26 +160,26 @@ template <class T> class Filter: public Speaker {
|
||||
{
|
||||
enqueue([=]() {
|
||||
unsigned int cycles_remaining = input_cycles;
|
||||
if(_coefficients_are_dirty) update_filter_coefficients();
|
||||
if(coefficients_are_dirty_) update_filter_coefficients();
|
||||
|
||||
// if input and output rates exactly match, just accumulate results and pass on
|
||||
if(_input_cycles_per_second == _output_cycles_per_second && _high_frequency_cut_off < 0.0)
|
||||
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0)
|
||||
{
|
||||
while(cycles_remaining)
|
||||
{
|
||||
unsigned int cycles_to_read = (unsigned int)(_buffer_size - _buffer_in_progress_pointer);
|
||||
unsigned int cycles_to_read = (unsigned int)(buffer_size_ - buffer_in_progress_pointer_);
|
||||
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
|
||||
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &_buffer_in_progress.get()[_buffer_in_progress_pointer]);
|
||||
_buffer_in_progress_pointer += cycles_to_read;
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &buffer_in_progress_.get()[buffer_in_progress_pointer_]);
|
||||
buffer_in_progress_pointer_ += cycles_to_read;
|
||||
|
||||
// announce to delegate if full
|
||||
if(_buffer_in_progress_pointer == _buffer_size)
|
||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
||||
{
|
||||
_buffer_in_progress_pointer = 0;
|
||||
if(_delegate)
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_)
|
||||
{
|
||||
_delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size);
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,45 +190,45 @@ template <class T> class Filter: public Speaker {
|
||||
}
|
||||
|
||||
// if the output rate is less than the input rate, use the filter
|
||||
if(_input_cycles_per_second > _output_cycles_per_second)
|
||||
if(input_cycles_per_second_ > output_cycles_per_second_)
|
||||
{
|
||||
while(cycles_remaining)
|
||||
{
|
||||
unsigned int cycles_to_read = (unsigned int)std::min((int)cycles_remaining, _number_of_taps - _input_buffer_depth);
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &_input_buffer.get()[_input_buffer_depth]);
|
||||
unsigned int cycles_to_read = (unsigned int)std::min((int)cycles_remaining, number_of_taps_ - input_buffer_depth_);
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_.get()[input_buffer_depth_]);
|
||||
cycles_remaining -= cycles_to_read;
|
||||
_input_buffer_depth += cycles_to_read;
|
||||
input_buffer_depth_ += cycles_to_read;
|
||||
|
||||
if(_input_buffer_depth == _number_of_taps)
|
||||
if(input_buffer_depth_ == number_of_taps_)
|
||||
{
|
||||
_buffer_in_progress.get()[_buffer_in_progress_pointer] = _filter->apply(_input_buffer.get());
|
||||
_buffer_in_progress_pointer++;
|
||||
buffer_in_progress_.get()[buffer_in_progress_pointer_] = filter_->apply(input_buffer_.get());
|
||||
buffer_in_progress_pointer_++;
|
||||
|
||||
// announce to delegate if full
|
||||
if(_buffer_in_progress_pointer == _buffer_size)
|
||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
||||
{
|
||||
_buffer_in_progress_pointer = 0;
|
||||
if(_delegate)
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_)
|
||||
{
|
||||
_delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size);
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
||||
}
|
||||
}
|
||||
|
||||
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
uint64_t steps = _stepper->step();
|
||||
if(steps < _number_of_taps)
|
||||
uint64_t steps = stepper_->step();
|
||||
if(steps < number_of_taps_)
|
||||
{
|
||||
int16_t *input_buffer = _input_buffer.get();
|
||||
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps));
|
||||
_input_buffer_depth -= steps;
|
||||
int16_t *input_buffer = input_buffer_.get();
|
||||
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps));
|
||||
input_buffer_depth_ -= steps;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(steps > _number_of_taps)
|
||||
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)_number_of_taps);
|
||||
_input_buffer_depth = 0;
|
||||
if(steps > number_of_taps_)
|
||||
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,44 +241,44 @@ template <class T> class Filter: public Speaker {
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SignalProcessing::Stepper> _stepper;
|
||||
std::unique_ptr<SignalProcessing::FIRFilter> _filter;
|
||||
std::unique_ptr<SignalProcessing::Stepper> stepper_;
|
||||
std::unique_ptr<SignalProcessing::FIRFilter> filter_;
|
||||
|
||||
std::unique_ptr<int16_t> _input_buffer;
|
||||
int _input_buffer_depth;
|
||||
std::unique_ptr<int16_t> input_buffer_;
|
||||
int input_buffer_depth_;
|
||||
|
||||
void update_filter_coefficients()
|
||||
{
|
||||
// make a guess at a good number of taps if this hasn't been provided explicitly
|
||||
if(_requested_number_of_taps)
|
||||
if(requested_number_of_taps_)
|
||||
{
|
||||
_number_of_taps = _requested_number_of_taps;
|
||||
number_of_taps_ = requested_number_of_taps_;
|
||||
}
|
||||
else
|
||||
{
|
||||
_number_of_taps = (int)ceilf((_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second);
|
||||
_number_of_taps *= 2;
|
||||
_number_of_taps |= 1;
|
||||
number_of_taps_ = (int)ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_);
|
||||
number_of_taps_ *= 2;
|
||||
number_of_taps_ |= 1;
|
||||
}
|
||||
|
||||
_coefficients_are_dirty = false;
|
||||
_buffer_in_progress_pointer = 0;
|
||||
coefficients_are_dirty_ = false;
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
|
||||
_stepper.reset(new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second));
|
||||
stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_));
|
||||
|
||||
float high_pass_frequency;
|
||||
if(_high_frequency_cut_off > 0.0)
|
||||
if(high_frequency_cut_off_ > 0.0)
|
||||
{
|
||||
high_pass_frequency = std::min((float)_output_cycles_per_second / 2.0f, _high_frequency_cut_off);
|
||||
high_pass_frequency = std::min((float)output_cycles_per_second_ / 2.0f, high_frequency_cut_off_);
|
||||
}
|
||||
else
|
||||
{
|
||||
high_pass_frequency = (float)_output_cycles_per_second / 2.0f;
|
||||
high_pass_frequency = (float)output_cycles_per_second_ / 2.0f;
|
||||
}
|
||||
_filter.reset(new SignalProcessing::FIRFilter((unsigned int)_number_of_taps, (float)_input_cycles_per_second, 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
filter_.reset(new SignalProcessing::FIRFilter((unsigned int)number_of_taps_, (float)input_cycles_per_second_, 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
|
||||
_input_buffer.reset(new int16_t[_number_of_taps]);
|
||||
_input_buffer_depth = 0;
|
||||
input_buffer_.reset(new int16_t[number_of_taps_]);
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -51,10 +51,10 @@ class Cartridge {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
const std::list<Segment> &get_segments() { return _segments; }
|
||||
const std::list<Segment> &get_segments() { return segments_; }
|
||||
|
||||
protected:
|
||||
std::list<Segment> _segments;
|
||||
std::list<Segment> segments_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ BinaryDump::BinaryDump(const char *file_name)
|
||||
fclose(file);
|
||||
|
||||
// enshrine
|
||||
_segments.emplace_back(
|
||||
segments_.emplace_back(
|
||||
::Storage::Cartridge::Cartridge::Segment::UnknownAddress,
|
||||
::Storage::Cartridge::Cartridge::Segment::UnknownAddress,
|
||||
std::move(contents));
|
||||
|
@ -46,5 +46,5 @@ PRG::PRG(const char *file_name)
|
||||
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(contents))
|
||||
throw ErrorNotROM;
|
||||
|
||||
_segments.emplace_back(0xa000, 0xa000 + data_length, std::move(contents));
|
||||
segments_.emplace_back(0xa000, 0xa000 + data_length, std::move(contents));
|
||||
}
|
||||
|
@ -13,58 +13,58 @@
|
||||
using namespace Storage;
|
||||
|
||||
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) :
|
||||
_clocks_per_bit(clocks_per_bit),
|
||||
_tolerance(tolerance),
|
||||
clocks_per_bit_(clocks_per_bit),
|
||||
tolerance_(tolerance),
|
||||
|
||||
_phase(0),
|
||||
_window_length(clocks_per_bit),
|
||||
phase_(0),
|
||||
window_length_(clocks_per_bit),
|
||||
|
||||
_phase_error_pointer(0)
|
||||
phase_error_pointer_(0)
|
||||
{
|
||||
_phase_error_history.reset(new std::vector<int>(length_of_history, 0));
|
||||
phase_error_history_.reset(new std::vector<int>(length_of_history, 0));
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
_phase += number_of_cycles;
|
||||
if(_phase >= _window_length)
|
||||
phase_ += number_of_cycles;
|
||||
if(phase_ >= window_length_)
|
||||
{
|
||||
int windows_crossed = _phase / _window_length;
|
||||
int windows_crossed = phase_ / window_length_;
|
||||
|
||||
// check whether this triggers any 0s, if anybody cares
|
||||
if(_delegate)
|
||||
if(delegate_)
|
||||
{
|
||||
if(_window_was_filled) windows_crossed--;
|
||||
if(window_was_filled_) windows_crossed--;
|
||||
for(int c = 0; c < windows_crossed; c++)
|
||||
_delegate->digital_phase_locked_loop_output_bit(0);
|
||||
delegate_->digital_phase_locked_loop_output_bit(0);
|
||||
}
|
||||
|
||||
_window_was_filled = false;
|
||||
_phase %= _window_length;
|
||||
window_was_filled_ = false;
|
||||
phase_ %= window_length_;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::add_pulse()
|
||||
{
|
||||
if(!_window_was_filled)
|
||||
if(!window_was_filled_)
|
||||
{
|
||||
if(_delegate) _delegate->digital_phase_locked_loop_output_bit(1);
|
||||
_window_was_filled = true;
|
||||
post_phase_error(_phase - (_window_length >> 1));
|
||||
if(delegate_) delegate_->digital_phase_locked_loop_output_bit(1);
|
||||
window_was_filled_ = true;
|
||||
post_phase_error(phase_ - (window_length_ >> 1));
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::post_phase_error(int error)
|
||||
{
|
||||
// use a simple spring mechanism as a lowpass filter for phase
|
||||
_phase -= (error + 1) >> 1;
|
||||
phase_ -= (error + 1) >> 1;
|
||||
|
||||
// use the average of the last few errors to affect frequency
|
||||
std::vector<int> *phase_error_history = _phase_error_history.get();
|
||||
std::vector<int> *phase_error_history = phase_error_history_.get();
|
||||
size_t phase_error_history_size = phase_error_history->size();
|
||||
|
||||
(*phase_error_history)[_phase_error_pointer] = error;
|
||||
_phase_error_pointer = (_phase_error_pointer + 1)%phase_error_history_size;
|
||||
(*phase_error_history)[phase_error_pointer_] = error;
|
||||
phase_error_pointer_ = (phase_error_pointer_ + 1)%phase_error_history_size;
|
||||
|
||||
int total_error = 0;
|
||||
for(size_t c = 0; c < phase_error_history_size; c++)
|
||||
@ -72,6 +72,6 @@ void DigitalPhaseLockedLoop::post_phase_error(int error)
|
||||
total_error += (*phase_error_history)[c];
|
||||
}
|
||||
int denominator = (int)(phase_error_history_size * 4);
|
||||
_window_length += (total_error + (denominator >> 1)) / denominator;
|
||||
_window_length = std::max(std::min(_window_length, _clocks_per_bit + _tolerance), _clocks_per_bit - _tolerance);
|
||||
window_length_ += (total_error + (denominator >> 1)) / denominator;
|
||||
window_length_ = std::max(std::min(window_length_, clocks_per_bit_ + tolerance_), clocks_per_bit_ - tolerance_);
|
||||
}
|
||||
|
@ -46,22 +46,22 @@ class DigitalPhaseLockedLoop {
|
||||
};
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *_delegate;
|
||||
Delegate *delegate_;
|
||||
|
||||
void post_phase_error(int error);
|
||||
std::unique_ptr<std::vector<int>> _phase_error_history;
|
||||
size_t _phase_error_pointer;
|
||||
std::unique_ptr<std::vector<int>> phase_error_history_;
|
||||
size_t phase_error_pointer_;
|
||||
|
||||
int _phase;
|
||||
int _window_length;
|
||||
bool _window_was_filled;
|
||||
int phase_;
|
||||
int window_length_;
|
||||
bool window_was_filled_;
|
||||
|
||||
int _clocks_per_bit;
|
||||
int _tolerance;
|
||||
int clocks_per_bit_;
|
||||
int tolerance_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,14 +11,14 @@
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
|
||||
_clock_rate(clock_rate * clock_rate_multiplier),
|
||||
_clock_rate_multiplier(clock_rate_multiplier),
|
||||
clock_rate_(clock_rate * clock_rate_multiplier),
|
||||
clock_rate_multiplier_(clock_rate_multiplier),
|
||||
|
||||
TimedEventLoop(clock_rate * clock_rate_multiplier)
|
||||
{
|
||||
_rotational_multiplier.length = 60;
|
||||
_rotational_multiplier.clock_rate = revolutions_per_minute;
|
||||
_rotational_multiplier.simplify();
|
||||
rotational_multiplier_.length = 60;
|
||||
rotational_multiplier_.clock_rate = revolutions_per_minute;
|
||||
rotational_multiplier_.simplify();
|
||||
|
||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||
Time one;
|
||||
@ -27,38 +27,38 @@ Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multipli
|
||||
|
||||
void Controller::setup_track()
|
||||
{
|
||||
_track = _drive->get_track();
|
||||
track_ = drive_->get_track();
|
||||
|
||||
Time offset;
|
||||
if(_track && _time_into_track.length > 0)
|
||||
if(track_ && time_into_track_.length > 0)
|
||||
{
|
||||
Time time_found = _track->seek_to(_time_into_track).simplify();
|
||||
offset = (_time_into_track - time_found).simplify();
|
||||
_time_into_track = time_found;
|
||||
Time time_found = track_->seek_to(time_into_track_).simplify();
|
||||
offset = (time_into_track_ - time_found).simplify();
|
||||
time_into_track_ = time_found;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = _time_into_track;
|
||||
_time_into_track.set_zero();
|
||||
offset = time_into_track_;
|
||||
time_into_track_.set_zero();
|
||||
}
|
||||
|
||||
reset_timer_to_offset(offset * _rotational_multiplier);
|
||||
reset_timer_to_offset(offset * rotational_multiplier_);
|
||||
get_next_event();
|
||||
}
|
||||
|
||||
void Controller::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
if(_drive && _drive->has_disk() && _motor_is_on)
|
||||
if(drive_ && drive_->has_disk() && motor_is_on_)
|
||||
{
|
||||
if(!_track) setup_track();
|
||||
number_of_cycles *= _clock_rate_multiplier;
|
||||
if(!track_) setup_track();
|
||||
number_of_cycles *= clock_rate_multiplier_;
|
||||
while(number_of_cycles)
|
||||
{
|
||||
int cycles_until_next_event = (int)get_cycles_until_next_event();
|
||||
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
|
||||
_cycles_since_index_hole += (unsigned int)cycles_to_run_for;
|
||||
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
|
||||
number_of_cycles -= cycles_to_run_for;
|
||||
_pll->run_for_cycles(cycles_to_run_for);
|
||||
pll_->run_for_cycles(cycles_to_run_for);
|
||||
TimedEventLoop::run_for_cycles(cycles_to_run_for);
|
||||
}
|
||||
}
|
||||
@ -68,31 +68,31 @@ void Controller::run_for_cycles(int number_of_cycles)
|
||||
|
||||
void Controller::get_next_event()
|
||||
{
|
||||
if(_track)
|
||||
_current_event = _track->get_next_event();
|
||||
if(track_)
|
||||
current_event_ = track_->get_next_event();
|
||||
else
|
||||
{
|
||||
_current_event.length.length = 1;
|
||||
_current_event.length.clock_rate = 1;
|
||||
_current_event.type = Track::Event::IndexHole;
|
||||
current_event_.length.length = 1;
|
||||
current_event_.length.clock_rate = 1;
|
||||
current_event_.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
// divide interval, which is in terms of a rotation of the disk, by rotation speed, and
|
||||
// convert it into revolutions per second
|
||||
set_next_event_time_interval(_current_event.length * _rotational_multiplier);
|
||||
set_next_event_time_interval(current_event_.length * rotational_multiplier_);
|
||||
}
|
||||
|
||||
void Controller::process_next_event()
|
||||
{
|
||||
switch(_current_event.type)
|
||||
switch(current_event_.type)
|
||||
{
|
||||
case Track::Event::FluxTransition:
|
||||
_pll->add_pulse();
|
||||
_time_into_track += _current_event.length;
|
||||
pll_->add_pulse();
|
||||
time_into_track_ += current_event_.length;
|
||||
break;
|
||||
case Track::Event::IndexHole:
|
||||
_cycles_since_index_hole = 0;
|
||||
_time_into_track.set_zero();
|
||||
cycles_since_index_hole_ = 0;
|
||||
time_into_track_.set_zero();
|
||||
process_index_hole();
|
||||
break;
|
||||
}
|
||||
@ -103,57 +103,57 @@ void Controller::process_next_event()
|
||||
|
||||
void Controller::set_expected_bit_length(Time bit_length)
|
||||
{
|
||||
_bit_length = bit_length;
|
||||
bit_length_ = bit_length;
|
||||
|
||||
// this conversion doesn't need to be exact because there's a lot of variation to be taken
|
||||
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
|
||||
int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate);
|
||||
_pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
|
||||
_pll->set_delegate(this);
|
||||
int clocks_per_bit = (int)((bit_length.length * clock_rate_) / bit_length.clock_rate);
|
||||
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
|
||||
pll_->set_delegate(this);
|
||||
}
|
||||
|
||||
void Controller::digital_phase_locked_loop_output_bit(int value)
|
||||
{
|
||||
process_input_bit(value, _cycles_since_index_hole);
|
||||
process_input_bit(value, cycles_since_index_hole_);
|
||||
}
|
||||
|
||||
#pragma mark - Drive actions
|
||||
|
||||
bool Controller::get_is_track_zero()
|
||||
{
|
||||
if(!_drive) return false;
|
||||
return _drive->get_is_track_zero();
|
||||
if(!drive_) return false;
|
||||
return drive_->get_is_track_zero();
|
||||
}
|
||||
|
||||
bool Controller::get_drive_is_ready()
|
||||
{
|
||||
if(!_drive) return false;
|
||||
return _drive->has_disk();
|
||||
if(!drive_) return false;
|
||||
return drive_->has_disk();
|
||||
}
|
||||
|
||||
void Controller::step(int direction)
|
||||
{
|
||||
if(_drive) _drive->step(direction);
|
||||
if(drive_) drive_->step(direction);
|
||||
invalidate_track();
|
||||
}
|
||||
|
||||
void Controller::set_motor_on(bool motor_on)
|
||||
{
|
||||
_motor_is_on = motor_on;
|
||||
motor_is_on_ = motor_on;
|
||||
}
|
||||
|
||||
bool Controller::get_motor_on()
|
||||
{
|
||||
return _motor_is_on;
|
||||
return motor_is_on_;
|
||||
}
|
||||
|
||||
void Controller::set_drive(std::shared_ptr<Drive> drive)
|
||||
{
|
||||
_drive = drive;
|
||||
drive_ = drive;
|
||||
invalidate_track();
|
||||
}
|
||||
|
||||
void Controller::invalidate_track()
|
||||
{
|
||||
_track = nullptr;
|
||||
track_ = nullptr;
|
||||
}
|
||||
|
@ -81,20 +81,20 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
virtual bool get_drive_is_ready();
|
||||
|
||||
private:
|
||||
Time _bit_length;
|
||||
unsigned int _clock_rate;
|
||||
unsigned int _clock_rate_multiplier;
|
||||
Time _rotational_multiplier;
|
||||
Time bit_length_;
|
||||
unsigned int clock_rate_;
|
||||
unsigned int clock_rate_multiplier_;
|
||||
Time rotational_multiplier_;
|
||||
|
||||
std::shared_ptr<DigitalPhaseLockedLoop> _pll;
|
||||
std::shared_ptr<Drive> _drive;
|
||||
std::shared_ptr<Track> _track;
|
||||
unsigned int _cycles_since_index_hole;
|
||||
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
|
||||
std::shared_ptr<Drive> drive_;
|
||||
std::shared_ptr<Track> track_;
|
||||
unsigned int cycles_since_index_hole_;
|
||||
|
||||
inline void get_next_event();
|
||||
Track::Event _current_event;
|
||||
Time _time_into_track;
|
||||
bool _motor_is_on;
|
||||
Track::Event current_event_;
|
||||
Time time_into_track_;
|
||||
bool motor_is_on_;
|
||||
|
||||
void setup_track();
|
||||
};
|
||||
|
@ -12,35 +12,35 @@
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Drive::Drive()
|
||||
: _head_position(0), _head(0) {}
|
||||
: head_position_(0), head_(0) {}
|
||||
|
||||
void Drive::set_disk(std::shared_ptr<Disk> disk)
|
||||
{
|
||||
_disk = disk;
|
||||
disk_ = disk;
|
||||
}
|
||||
|
||||
bool Drive::has_disk()
|
||||
{
|
||||
return (bool)_disk;
|
||||
return (bool)disk_;
|
||||
}
|
||||
|
||||
bool Drive::get_is_track_zero()
|
||||
{
|
||||
return _head_position == 0;
|
||||
return head_position_ == 0;
|
||||
}
|
||||
|
||||
void Drive::step(int direction)
|
||||
{
|
||||
_head_position = std::max(_head_position + direction, 0);
|
||||
head_position_ = std::max(head_position_ + direction, 0);
|
||||
}
|
||||
|
||||
void Drive::set_head(unsigned int head)
|
||||
{
|
||||
_head = head;
|
||||
head_ = head;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Drive::get_track()
|
||||
{
|
||||
if(_disk) return _disk->get_track_at_position(_head, (unsigned int)_head_position);
|
||||
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -47,9 +47,9 @@ class Drive {
|
||||
std::shared_ptr<Track> get_track();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Disk> _disk;
|
||||
int _head_position;
|
||||
unsigned int _head;
|
||||
std::shared_ptr<Disk> disk_;
|
||||
int head_position_;
|
||||
unsigned int head_;
|
||||
};
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ using namespace Storage::Disk;
|
||||
|
||||
PCMTrack::PCMTrack(std::vector<PCMSegment> segments)
|
||||
{
|
||||
_segments = std::move(segments);
|
||||
segments_ = std::move(segments);
|
||||
fix_length();
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ PCMTrack::PCMTrack(PCMSegment segment)
|
||||
{
|
||||
segment.length_of_a_bit.length = 1;
|
||||
segment.length_of_a_bit.clock_rate = 1;
|
||||
_segments.push_back(std::move(segment));
|
||||
segments_.push_back(std::move(segment));
|
||||
fix_length();
|
||||
}
|
||||
|
||||
@ -29,54 +29,54 @@ PCMTrack::Event PCMTrack::get_next_event()
|
||||
{
|
||||
// find the next 1 in the input stream, keeping count of length as we go, and assuming it's going
|
||||
// to be a flux transition
|
||||
_next_event.type = Track::Event::FluxTransition;
|
||||
_next_event.length.length = 0;
|
||||
while(_segment_pointer < _segments.size())
|
||||
next_event_.type = Track::Event::FluxTransition;
|
||||
next_event_.length.length = 0;
|
||||
while(segment_pointer_ < segments_.size())
|
||||
{
|
||||
unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate;
|
||||
unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length;
|
||||
unsigned int clock_multiplier = track_clock_rate_ / segments_[segment_pointer_].length_of_a_bit.clock_rate;
|
||||
unsigned int bit_length = clock_multiplier * segments_[segment_pointer_].length_of_a_bit.length;
|
||||
|
||||
const uint8_t *segment_data = &_segments[_segment_pointer].data[0];
|
||||
while(_bit_pointer < _segments[_segment_pointer].number_of_bits)
|
||||
const uint8_t *segment_data = &segments_[segment_pointer_].data[0];
|
||||
while(bit_pointer_ < segments_[segment_pointer_].number_of_bits)
|
||||
{
|
||||
// for timing simplicity, bits are modelled as happening at the end of their window
|
||||
// TODO: should I account for the converse bit ordering? Or can I assume MSB first?
|
||||
int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7));
|
||||
_bit_pointer++;
|
||||
_next_event.length.length += bit_length;
|
||||
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
|
||||
bit_pointer_++;
|
||||
next_event_.length.length += bit_length;
|
||||
|
||||
if(bit) return _next_event;
|
||||
if(bit) return next_event_;
|
||||
}
|
||||
_bit_pointer = 0;
|
||||
_segment_pointer++;
|
||||
bit_pointer_ = 0;
|
||||
segment_pointer_++;
|
||||
}
|
||||
|
||||
// check whether we actually reached the index hole
|
||||
if(_segment_pointer == _segments.size())
|
||||
if(segment_pointer_ == segments_.size())
|
||||
{
|
||||
_segment_pointer = 0;
|
||||
_next_event.type = Track::Event::IndexHole;
|
||||
segment_pointer_ = 0;
|
||||
next_event_.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
return _next_event;
|
||||
return next_event_;
|
||||
}
|
||||
|
||||
Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
|
||||
{
|
||||
_segment_pointer = 0;
|
||||
segment_pointer_ = 0;
|
||||
|
||||
// pick a common clock rate for counting time on this track and multiply up the time being sought appropriately
|
||||
Time time_so_far;
|
||||
time_so_far.clock_rate = NumberTheory::least_common_multiple(_next_event.length.clock_rate, time_since_index_hole.clock_rate);
|
||||
time_so_far.clock_rate = NumberTheory::least_common_multiple(next_event_.length.clock_rate, time_since_index_hole.clock_rate);
|
||||
time_since_index_hole.length *= time_so_far.clock_rate / time_since_index_hole.clock_rate;
|
||||
time_since_index_hole.clock_rate = time_so_far.clock_rate;
|
||||
|
||||
while(_segment_pointer < _segments.size())
|
||||
while(segment_pointer_ < segments_.size())
|
||||
{
|
||||
// determine how long this segment is in terms of the master clock
|
||||
unsigned int clock_multiplier = time_so_far.clock_rate / _next_event.length.clock_rate;
|
||||
unsigned int bit_length = ((clock_multiplier / _track_clock_rate) / _segments[_segment_pointer].length_of_a_bit.clock_rate) * _segments[_segment_pointer].length_of_a_bit.length;
|
||||
unsigned int time_in_this_segment = bit_length * _segments[_segment_pointer].number_of_bits;
|
||||
unsigned int clock_multiplier = time_so_far.clock_rate / next_event_.length.clock_rate;
|
||||
unsigned int bit_length = ((clock_multiplier / track_clock_rate_) / segments_[segment_pointer_].length_of_a_bit.clock_rate) * segments_[segment_pointer_].length_of_a_bit.length;
|
||||
unsigned int time_in_this_segment = bit_length * segments_[segment_pointer_].number_of_bits;
|
||||
|
||||
// if this segment goes on longer than the time being sought, end here
|
||||
unsigned int time_remaining = time_since_index_hole.length - time_so_far.length;
|
||||
@ -86,7 +86,7 @@ Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
|
||||
unsigned int time_found = time_remaining - (time_remaining % bit_length);
|
||||
|
||||
// resolve that into the stateful bit count
|
||||
_bit_pointer = 1 + (time_remaining / bit_length);
|
||||
bit_pointer_ = 1 + (time_remaining / bit_length);
|
||||
|
||||
// update and return the time sought to
|
||||
time_so_far.length += time_found;
|
||||
@ -95,7 +95,7 @@ Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
|
||||
|
||||
// otherwise, accumulate time and keep moving
|
||||
time_so_far.length += time_in_this_segment;
|
||||
_segment_pointer++;
|
||||
segment_pointer_++;
|
||||
}
|
||||
return time_since_index_hole;
|
||||
}
|
||||
@ -103,19 +103,19 @@ Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
|
||||
void PCMTrack::fix_length()
|
||||
{
|
||||
// find the least common multiple of all segment clock rates
|
||||
_track_clock_rate = _segments[0].length_of_a_bit.clock_rate;
|
||||
for(size_t c = 1; c < _segments.size(); c++)
|
||||
track_clock_rate_ = segments_[0].length_of_a_bit.clock_rate;
|
||||
for(size_t c = 1; c < segments_.size(); c++)
|
||||
{
|
||||
_track_clock_rate = NumberTheory::least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.clock_rate);
|
||||
track_clock_rate_ = NumberTheory::least_common_multiple(track_clock_rate_, segments_[c].length_of_a_bit.clock_rate);
|
||||
}
|
||||
|
||||
// thereby determine the total length, storing it to next_event as the track-total divisor
|
||||
_next_event.length.clock_rate = 0;
|
||||
for(size_t c = 0; c < _segments.size(); c++)
|
||||
next_event_.length.clock_rate = 0;
|
||||
for(size_t c = 0; c < segments_.size(); c++)
|
||||
{
|
||||
unsigned int multiplier = _track_clock_rate / _segments[c].length_of_a_bit.clock_rate;
|
||||
_next_event.length.clock_rate += _segments[c].length_of_a_bit.length * _segments[c].number_of_bits * multiplier;
|
||||
unsigned int multiplier = track_clock_rate_ / segments_[c].length_of_a_bit.clock_rate;
|
||||
next_event_.length.clock_rate += segments_[c].length_of_a_bit.length * segments_[c].number_of_bits * multiplier;
|
||||
}
|
||||
|
||||
_segment_pointer = _bit_pointer = 0;
|
||||
segment_pointer_ = bit_pointer_ = 0;
|
||||
}
|
||||
|
@ -52,21 +52,21 @@ class PCMTrack: public Track {
|
||||
|
||||
private:
|
||||
// storage for the segments that describe this track
|
||||
std::vector<PCMSegment> _segments;
|
||||
std::vector<PCMSegment> segments_;
|
||||
|
||||
// a helper to determine the overall track clock rate and it's length
|
||||
void fix_length();
|
||||
|
||||
// the event perpetually returned; impliedly contains the length of the entire track
|
||||
// as its clock rate, per the need for everything on a Track to sum to a length of 1
|
||||
PCMTrack::Event _next_event;
|
||||
PCMTrack::Event next_event_;
|
||||
|
||||
// contains the master clock rate
|
||||
unsigned int _track_clock_rate;
|
||||
unsigned int track_clock_rate_;
|
||||
|
||||
// a pointer to the first bit to consider as the next event
|
||||
size_t _segment_pointer;
|
||||
size_t _bit_pointer;
|
||||
size_t segment_pointer_;
|
||||
size_t bit_pointer_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ using namespace Storage::Tape::Acorn;
|
||||
|
||||
Parser::Parser() :
|
||||
::Storage::Tape::Parser<WaveType, SymbolType>(),
|
||||
_crc(0x1021, 0x0000) {}
|
||||
crc_(0x1021, 0x0000) {}
|
||||
|
||||
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
@ -38,7 +38,7 @@ int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
set_error_flag();
|
||||
return -1;
|
||||
}
|
||||
_crc.add((uint8_t)value);
|
||||
crc_.add((uint8_t)value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -56,8 +56,8 @@ int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Parser::reset_crc() { _crc.reset(); }
|
||||
uint16_t Parser::get_crc() { return _crc.get_value(); }
|
||||
void Parser::reset_crc() { crc_.reset(); }
|
||||
uint16_t Parser::get_crc() { return crc_.get_value(); }
|
||||
|
||||
void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
private:
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
NumberTheory::CRC16 _crc;
|
||||
NumberTheory::CRC16 crc_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ using namespace Storage::Tape::Commodore;
|
||||
|
||||
Parser::Parser() :
|
||||
Storage::Tape::Parser<WaveType, SymbolType>(),
|
||||
_wave_period(0.0f),
|
||||
_previous_was_high(false),
|
||||
_parity_byte(0) {}
|
||||
wave_period_(0.0f),
|
||||
previous_was_high_(false),
|
||||
parity_byte_(0) {}
|
||||
|
||||
/*!
|
||||
Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it.
|
||||
@ -193,9 +193,9 @@ void Parser::expect_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, uint8
|
||||
if(next_byte != value) set_error_flag();
|
||||
}
|
||||
|
||||
void Parser::reset_parity_byte() { _parity_byte = 0; }
|
||||
uint8_t Parser::get_parity_byte() { return _parity_byte; }
|
||||
void Parser::add_parity_byte(uint8_t byte) { _parity_byte ^= byte; }
|
||||
void Parser::reset_parity_byte() { parity_byte_ = 0; }
|
||||
uint8_t Parser::get_parity_byte() { return parity_byte_; }
|
||||
void Parser::add_parity_byte(uint8_t byte) { parity_byte_ ^= byte; }
|
||||
|
||||
/*!
|
||||
Proceeds to the next word marker then returns the result of @c get_next_byte_contents.
|
||||
@ -255,19 +255,19 @@ void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
// medium: 262µs => 0.000524s cycle
|
||||
// long: 342µs => 0.000684s cycle
|
||||
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
if(!is_high && _previous_was_high)
|
||||
if(!is_high && previous_was_high_)
|
||||
{
|
||||
if(_wave_period >= 0.000764) push_wave(WaveType::Unrecognised);
|
||||
else if(_wave_period >= 0.000604) push_wave(WaveType::Long);
|
||||
else if(_wave_period >= 0.000444) push_wave(WaveType::Medium);
|
||||
else if(_wave_period >= 0.000284) push_wave(WaveType::Short);
|
||||
if(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised);
|
||||
else if(wave_period_ >= 0.000604) push_wave(WaveType::Long);
|
||||
else if(wave_period_ >= 0.000444) push_wave(WaveType::Medium);
|
||||
else if(wave_period_ >= 0.000284) push_wave(WaveType::Short);
|
||||
else push_wave(WaveType::Unrecognised);
|
||||
|
||||
_wave_period = 0.0f;
|
||||
wave_period_ = 0.0f;
|
||||
}
|
||||
|
||||
_wave_period += pulse.length.get_float();
|
||||
_previous_was_high = is_high;
|
||||
wave_period_ += pulse.length.get_float();
|
||||
previous_was_high_ = is_high;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -94,7 +94,7 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
*/
|
||||
void expect_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, uint8_t value);
|
||||
|
||||
uint8_t _parity_byte;
|
||||
uint8_t parity_byte_;
|
||||
void reset_parity_byte();
|
||||
uint8_t get_parity_byte();
|
||||
void add_parity_byte(uint8_t byte);
|
||||
@ -122,8 +122,8 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
a long, medium, short or unrecognised wave period.
|
||||
*/
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
bool _previous_was_high;
|
||||
float _wave_period;
|
||||
bool previous_was_high_;
|
||||
float wave_period_;
|
||||
|
||||
/*!
|
||||
Per the contract with StaticAnalyser::TapeParser; produces any of a word marker, an end-of-block marker,
|
||||
|
@ -12,8 +12,8 @@ using namespace Storage::Tape::Oric;
|
||||
|
||||
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding)
|
||||
{
|
||||
_detection_mode = use_fast_encoding ? FastZero : SlowZero;
|
||||
_cycle_length = 0.0f;
|
||||
detection_mode_ = use_fast_encoding ? FastZero : SlowZero;
|
||||
cycle_length_ = 0.0f;
|
||||
|
||||
int result = 0;
|
||||
int bit_count = 0;
|
||||
@ -21,7 +21,7 @@ int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool
|
||||
{
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
if(!bit_count && symbol != SymbolType::Zero) continue;
|
||||
_detection_mode = use_fast_encoding ? FastData : SlowData;
|
||||
detection_mode_ = use_fast_encoding ? FastData : SlowData;
|
||||
result |= ((symbol == SymbolType::One) ? 1 : 0) << bit_count;
|
||||
bit_count++;
|
||||
}
|
||||
@ -31,7 +31,7 @@ int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool
|
||||
|
||||
bool Parser::sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
_detection_mode = Sync;
|
||||
detection_mode_ = Sync;
|
||||
while(!tape->is_at_end())
|
||||
{
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
@ -52,22 +52,22 @@ void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
const float maximum_long_length = 0.001456f;
|
||||
|
||||
bool wave_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
if(!_wave_was_high && wave_is_high != _wave_was_high)
|
||||
if(!wave_was_high_ && wave_is_high != wave_was_high_)
|
||||
{
|
||||
if(_cycle_length < maximum_short_length) push_wave(WaveType::Short);
|
||||
else if(_cycle_length < maximum_medium_length) push_wave(WaveType::Medium);
|
||||
else if(_cycle_length < maximum_long_length) push_wave(WaveType::Long);
|
||||
if(cycle_length_ < maximum_short_length) push_wave(WaveType::Short);
|
||||
else if(cycle_length_ < maximum_medium_length) push_wave(WaveType::Medium);
|
||||
else if(cycle_length_ < maximum_long_length) push_wave(WaveType::Long);
|
||||
else push_wave(WaveType::Unrecognised);
|
||||
|
||||
_cycle_length = 0.0f;
|
||||
cycle_length_ = 0.0f;
|
||||
}
|
||||
_wave_was_high = wave_is_high;
|
||||
_cycle_length += pulse.length.get_float();
|
||||
wave_was_high_ = wave_is_high;
|
||||
cycle_length_ += pulse.length.get_float();
|
||||
}
|
||||
|
||||
void Parser::inspect_waves(const std::vector<WaveType> &waves)
|
||||
{
|
||||
switch(_detection_mode)
|
||||
switch(detection_mode_)
|
||||
{
|
||||
case FastZero:
|
||||
if(waves.empty()) return;
|
||||
|
@ -41,9 +41,9 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
FastZero,
|
||||
SlowZero,
|
||||
Sync
|
||||
} _detection_mode;
|
||||
bool _wave_was_high;
|
||||
float _cycle_length;
|
||||
} detection_mode_;
|
||||
bool wave_was_high_;
|
||||
float cycle_length_;
|
||||
|
||||
struct Pattern
|
||||
{
|
||||
|
@ -21,23 +21,23 @@ TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
|
||||
void Storage::Tape::Tape::seek(Time &seek_time)
|
||||
{
|
||||
_current_time.set_zero();
|
||||
_next_time.set_zero();
|
||||
while(_next_time < seek_time) get_next_pulse();
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
while(next_time_ < seek_time) get_next_pulse();
|
||||
}
|
||||
|
||||
void Storage::Tape::Tape::reset()
|
||||
{
|
||||
_current_time.set_zero();
|
||||
_next_time.set_zero();
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
Tape::Pulse Tape::get_next_pulse()
|
||||
{
|
||||
Tape::Pulse pulse = virtual_get_next_pulse();
|
||||
_current_time = _next_time;
|
||||
_next_time += pulse.length;
|
||||
current_time_ = next_time_;
|
||||
next_time_ += pulse.length;
|
||||
return pulse;
|
||||
}
|
||||
|
||||
@ -45,34 +45,34 @@ Tape::Pulse Tape::get_next_pulse()
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape)
|
||||
{
|
||||
_tape = tape;
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape()
|
||||
{
|
||||
return _tape;
|
||||
return tape_;
|
||||
}
|
||||
|
||||
bool TapePlayer::has_tape()
|
||||
{
|
||||
return (bool)_tape;
|
||||
return (bool)tape_;
|
||||
}
|
||||
|
||||
void TapePlayer::get_next_pulse()
|
||||
{
|
||||
// get the new pulse
|
||||
if(_tape)
|
||||
_current_pulse = _tape->get_next_pulse();
|
||||
if(tape_)
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
else
|
||||
{
|
||||
_current_pulse.length.length = 1;
|
||||
_current_pulse.length.clock_rate = 1;
|
||||
_current_pulse.type = Tape::Pulse::Zero;
|
||||
current_pulse_.length.length = 1;
|
||||
current_pulse_.length.clock_rate = 1;
|
||||
current_pulse_.type = Tape::Pulse::Zero;
|
||||
}
|
||||
|
||||
set_next_event_time_interval(_current_pulse.length);
|
||||
set_next_event_time_interval(current_pulse_.length);
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_cycles(int number_of_cycles)
|
||||
@ -90,19 +90,19 @@ void TapePlayer::run_for_input_pulse()
|
||||
|
||||
void TapePlayer::process_next_event()
|
||||
{
|
||||
process_input_pulse(_current_pulse);
|
||||
process_input_pulse(current_pulse_);
|
||||
get_next_pulse();
|
||||
}
|
||||
|
||||
#pragma mark - Binary Player
|
||||
|
||||
BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate), _motor_is_running(false)
|
||||
TapePlayer(input_clock_rate), motor_is_running_(false)
|
||||
{}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled)
|
||||
{
|
||||
_motor_is_running = enabled;
|
||||
motor_is_running_ = enabled;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_tape_output(bool set)
|
||||
@ -112,26 +112,26 @@ void BinaryTapePlayer::set_tape_output(bool set)
|
||||
|
||||
bool BinaryTapePlayer::get_input()
|
||||
{
|
||||
return _input_level;
|
||||
return input_level_;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
if(_motor_is_running) TapePlayer::run_for_cycles(number_of_cycles);
|
||||
if(motor_is_running_) TapePlayer::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
bool new_input_level = pulse.type == Tape::Pulse::Low;
|
||||
if(_input_level != new_input_level)
|
||||
if(input_level_ != new_input_level)
|
||||
{
|
||||
_input_level = new_input_level;
|
||||
if(_delegate) _delegate->tape_did_change_input(this);
|
||||
input_level_ = new_input_level;
|
||||
if(delegate_) delegate_->tape_did_change_input(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,13 +53,13 @@ class Tape {
|
||||
virtual bool is_at_end() = 0;
|
||||
|
||||
/// @returns the amount of time preceeding the most recently-returned pulse.
|
||||
virtual Time get_current_time() { return _current_time; }
|
||||
virtual Time get_current_time() { return current_time_; }
|
||||
|
||||
/// Advances or reverses the tape to the last time before or at @c time from which a pulse starts.
|
||||
virtual void seek(Time &time);
|
||||
|
||||
private:
|
||||
Time _current_time, _next_time;
|
||||
Time current_time_, next_time_;
|
||||
|
||||
virtual Pulse virtual_get_next_pulse() = 0;
|
||||
virtual void virtual_reset() = 0;
|
||||
@ -90,8 +90,8 @@ class TapePlayer: public TimedEventLoop {
|
||||
private:
|
||||
inline void get_next_pulse();
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> _tape;
|
||||
Tape::Pulse _current_pulse;
|
||||
std::shared_ptr<Storage::Tape::Tape> tape_;
|
||||
Tape::Pulse current_pulse_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -118,10 +118,10 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
protected:
|
||||
Delegate *_delegate;
|
||||
Delegate *delegate_;
|
||||
virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
bool _input_level;
|
||||
bool _motor_is_running;
|
||||
bool input_level_;
|
||||
bool motor_is_running_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,12 +13,12 @@
|
||||
using namespace Storage;
|
||||
|
||||
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
|
||||
_input_clock_rate(input_clock_rate) {}
|
||||
input_clock_rate_(input_clock_rate) {}
|
||||
|
||||
void TimedEventLoop::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
_cycles_until_event -= number_of_cycles;
|
||||
while(_cycles_until_event <= 0)
|
||||
cycles_until_event_ -= number_of_cycles;
|
||||
while(cycles_until_event_ <= 0)
|
||||
{
|
||||
process_next_event();
|
||||
}
|
||||
@ -26,13 +26,13 @@ void TimedEventLoop::run_for_cycles(int number_of_cycles)
|
||||
|
||||
unsigned int TimedEventLoop::get_cycles_until_next_event()
|
||||
{
|
||||
return (unsigned int)std::max(_cycles_until_event, 0);
|
||||
return (unsigned int)std::max(cycles_until_event_, 0);
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer()
|
||||
{
|
||||
_subcycles_until_event.set_zero();
|
||||
_cycles_until_event = 0;
|
||||
subcycles_until_event_.set_zero();
|
||||
cycles_until_event_ = 0;
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer_to_offset(Time offset)
|
||||
@ -49,10 +49,10 @@ void TimedEventLoop::jump_to_next_event()
|
||||
void TimedEventLoop::set_next_event_time_interval(Time interval)
|
||||
{
|
||||
// Calculate [interval]*[input clock rate] + [subcycles until this event].
|
||||
int64_t denominator = (int64_t)interval.clock_rate * (int64_t)_subcycles_until_event.clock_rate;
|
||||
int64_t denominator = (int64_t)interval.clock_rate * (int64_t)subcycles_until_event_.clock_rate;
|
||||
int64_t numerator =
|
||||
(int64_t)_subcycles_until_event.clock_rate * (int64_t)_input_clock_rate * (int64_t)interval.length +
|
||||
(int64_t)interval.clock_rate * (int64_t)_subcycles_until_event.length;
|
||||
(int64_t)subcycles_until_event_.clock_rate * (int64_t)input_clock_rate_ * (int64_t)interval.length +
|
||||
(int64_t)interval.clock_rate * (int64_t)subcycles_until_event_.length;
|
||||
|
||||
// Simplify now, to prepare for stuffing into possibly 32-bit quantities
|
||||
int64_t common_divisor = NumberTheory::greatest_common_divisor(numerator % denominator, denominator);
|
||||
@ -61,9 +61,9 @@ void TimedEventLoop::set_next_event_time_interval(Time interval)
|
||||
|
||||
// So this event will fire in the integral number of cycles from now, putting us at the remainder
|
||||
// number of subcycles
|
||||
_cycles_until_event = (int)(numerator / denominator);
|
||||
_subcycles_until_event.length = (unsigned int)(numerator % denominator);
|
||||
_subcycles_until_event.clock_rate = (unsigned int)denominator;
|
||||
cycles_until_event_ = (int)(numerator / denominator);
|
||||
subcycles_until_event_.length = (unsigned int)(numerator % denominator);
|
||||
subcycles_until_event_.clock_rate = (unsigned int)denominator;
|
||||
}
|
||||
|
||||
Time TimedEventLoop::get_time_into_next_event()
|
||||
|
@ -90,10 +90,9 @@ namespace Storage {
|
||||
Time get_time_into_next_event();
|
||||
|
||||
private:
|
||||
unsigned int _input_clock_rate;
|
||||
int _cycles_until_event;
|
||||
Time _subcycles_until_event;
|
||||
Time _event_interval;
|
||||
unsigned int input_clock_rate_;
|
||||
int cycles_until_event_;
|
||||
Time subcycles_until_event_;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user