1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-18 13:29:41 +00:00

Completed curly bracket movement.

This commit is contained in:
Thomas Harte 2017-03-26 14:34:47 -04:00
parent 3229502fa1
commit e01f3f06c8
83 changed files with 1542 additions and 3077 deletions

View File

@ -12,38 +12,35 @@
using namespace WD;
WD1770::Status::Status() :
type(Status::One),
write_protect(false),
record_type(false),
spin_up(false),
record_not_found(false),
crc_error(false),
seek_error(false),
lost_data(false),
data_request(false),
interrupt_request(false),
busy(false)
{}
type(Status::One),
write_protect(false),
record_type(false),
spin_up(false),
record_not_found(false),
crc_error(false),
seek_error(false),
lost_data(false),
data_request(false),
interrupt_request(false),
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::Controller(8000000, 16, 300),
crc_generator_(0x1021, 0xffff),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
is_awaiting_marker_value_(false),
data_mode_(DataMode::Scanning),
delegate_(nullptr),
personality_(p),
head_is_loaded_(false)
{
Storage::Disk::Controller(8000000, 16, 300),
crc_generator_(0x1021, 0xffff),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
is_awaiting_marker_value_(false),
data_mode_(DataMode::Scanning),
delegate_(nullptr),
personality_(p),
head_is_loaded_(false) {
set_is_double_density(false);
posit_event(Event::Command);
}
void WD1770::set_is_double_density(bool is_double_density)
{
void WD1770::set_is_double_density(bool is_double_density) {
is_double_density_ = is_double_density;
Storage::Time bit_length;
bit_length.length = 1;
@ -53,21 +50,15 @@ void WD1770::set_is_double_density(bool is_double_density)
if(!is_double_density) is_awaiting_marker_value_ = false;
}
void WD1770::set_register(int address, uint8_t value)
{
switch(address&3)
{
case 0:
{
if((value&0xf0) == 0xd0)
{
void WD1770::set_register(int address, uint8_t value) {
switch(address&3) {
case 0: {
if((value&0xf0) == 0xd0) {
printf("!!!TODO: force interrupt!!!\n");
update_status([] (Status &status) {
status.type = Status::One;
});
}
else
{
} else {
command_ = value;
posit_event(Event::Command);
}
@ -84,12 +75,9 @@ void WD1770::set_register(int address, uint8_t value)
}
}
uint8_t WD1770::get_register(int address)
{
switch(address&3)
{
default:
{
uint8_t WD1770::get_register(int address) {
switch(address&3) {
default: {
update_status([] (Status &status) {
status.interrupt_request = false;
});
@ -97,8 +85,7 @@ uint8_t WD1770::get_register(int address)
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
switch(status_.type)
{
switch(status_.type) {
case Status::One:
status |=
(get_is_track_zero() ? Flag::TrackZero : 0) |
@ -116,14 +103,11 @@ uint8_t WD1770::get_register(int address)
break;
}
if(!has_motor_on_line())
{
if(!has_motor_on_line()) {
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
if(status_.type == Status::One)
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
}
else
{
} else {
status |= (get_motor_on() ? Flag::MotorOn : 0);
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
@ -140,38 +124,29 @@ uint8_t WD1770::get_register(int address)
}
}
void WD1770::run_for_cycles(unsigned int number_of_cycles)
{
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
if(delay_time_)
{
if(delay_time_ <= number_of_cycles)
{
if(delay_time_) {
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(Event::Timer);
}
else
{
} else {
delay_time_ -= number_of_cycles;
}
}
}
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) {
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
if(data_mode_ == DataMode::Scanning)
{
if(data_mode_ == DataMode::Scanning) {
Token::Type token_type = Token::Byte;
if(!is_double_density_)
{
switch(shift_register_ & 0xffff)
{
if(!is_double_density_) {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
crc_generator_.reset();
@ -195,11 +170,8 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
default:
break;
}
}
else
{
switch(shift_register_ & 0xffff)
{
} else {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
@ -220,8 +192,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
}
}
if(token_type != Token::Byte)
{
if(token_type != Token::Byte) {
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event(Event::Token);
@ -229,8 +200,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
}
}
if(bits_since_token_ == 16)
{
if(bits_since_token_ == 16) {
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
@ -243,11 +213,9 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_)
{
if(is_awaiting_marker_value_ && is_double_density_) {
is_awaiting_marker_value_ = false;
switch(latest_token_.byte_value)
{
switch(latest_token_.byte_value) {
case Storage::Encodings::MFM::IndexAddressByte:
latest_token_.type = Token::Index;
break;
@ -270,31 +238,26 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
}
}
void WD1770::process_index_hole()
{
void WD1770::process_index_hole() {
index_hole_count_++;
posit_event(Event::IndexHole);
if(index_hole_count_target_ == index_hole_count_)
{
if(index_hole_count_target_ == index_hole_count_) {
posit_event(Event::IndexHoleTarget);
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line())
{
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
set_motor_on(false);
}
// head unload
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line())
{
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
set_head_load_request(false);
}
}
void WD1770::process_write_completed()
{
void WD1770::process_write_completed() {
posit_event(Event::DataWritten);
}
@ -305,11 +268,9 @@ void WD1770::process_write_completed()
#define END_SECTION() 0; }
#define READ_ID() \
if(new_event_type == Event::Token) \
{ \
if(new_event_type == Event::Token) { \
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
{ \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { \
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
distance_into_section_++; \
} \
@ -343,8 +304,7 @@ void WD1770::process_write_completed()
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(Event new_event_type)
{
void WD1770::posit_event(Event new_event_type) {
if(!(interesting_event_mask_ & (int)new_event_type)) return;
interesting_event_mask_ &= ~new_event_type;
@ -405,8 +365,7 @@ void WD1770::posit_event(Event new_event_type)
goto begin_type1_load_head;
begin_type1_load_head:
if(!(command_&0x08))
{
if(!(command_&0x08)) {
set_head_load_request(false);
goto test_type1_type;
}
@ -426,8 +385,7 @@ void WD1770::posit_event(Event new_event_type)
if((command_ >> 5) != 0) goto perform_step_command;
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
if(!(command_ & 0x10))
{
if(!(command_ & 0x10)) {
track_ = 0xff;
data_ = 0;
}
@ -440,15 +398,13 @@ void WD1770::posit_event(Event new_event_type)
if(step_direction_) track_++; else track_--;
perform_step:
if(!step_direction_ && get_is_track_zero())
{
if(!step_direction_ && get_is_track_zero()) {
track_ = 0;
goto verify;
}
step(step_direction_ ? 1 : -1);
int time_to_wait;
switch(command_ & 3)
{
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
case 1: time_to_wait = 12; break;
@ -464,8 +420,7 @@ void WD1770::posit_event(Event new_event_type)
goto perform_step;
verify:
if(!(command_ & 0x04))
{
if(!(command_ & 0x04)) {
goto wait_for_command;
}
@ -476,26 +431,22 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
READ_ID();
if(index_hole_count_ == 6)
{
if(index_hole_count_ == 6) {
update_status([] (Status &status) {
status.seek_error = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
if(distance_into_section_ == 7) {
data_mode_ = DataMode::Scanning;
if(crc_generator_.get_value())
{
if(crc_generator_.get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
goto verify_read_data;
}
if(header_[0] == track_)
{
if(header_[0] == track_) {
printf("Reached track %d\n", track_);
update_status([] (Status &status) {
status.crc_error = false;
@ -553,8 +504,7 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20 && get_drive_is_read_only())
{
if(command_&0x20 && get_drive_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@ -565,24 +515,20 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
READ_ID();
if(index_hole_count_ == 5)
{
if(index_hole_count_ == 5) {
printf("Failed to find sector %d\n", sector_);
update_status([] (Status &status) {
status.record_not_found = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
if(distance_into_section_ == 7) {
printf("Considering %d/%d\n", header_[0], header_[2]);
data_mode_ = DataMode::Scanning;
if(header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
{
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
printf("Found %d/%d\n", header_[0], header_[2]);
if(crc_generator_.get_value())
{
if(crc_generator_.get_value()) {
printf("CRC error; back to searching\n");
update_status([] (Status &status) {
status.crc_error = true;
@ -607,8 +553,7 @@ void WD1770::posit_event(Event new_event_type)
type2_read_data:
WAIT_FOR_EVENT(Event::Token);
// TODO: timeout
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
{
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) {
update_status([this] (Status &status) {
status.record_type = (latest_token_.type == Token::DeletedData);
});
@ -627,8 +572,7 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3])
{
if(distance_into_section_ == 128 << header_[3]) {
distance_into_section_ = 0;
goto type2_check_crc;
}
@ -639,10 +583,8 @@ void WD1770::posit_event(Event new_event_type)
if(latest_token_.type != Token::Byte) goto type2_read_byte;
header_[distance_into_section_] = latest_token_.byte_value;
distance_into_section_++;
if(distance_into_section_ == 2)
{
if(crc_generator_.get_value())
{
if(distance_into_section_ == 2) {
if(crc_generator_.get_value()) {
printf("CRC error; terminating\n");
update_status([this] (Status &status) {
status.crc_error = true;
@ -650,8 +592,7 @@ void WD1770::posit_event(Event new_event_type)
goto wait_for_command;
}
if(command_ & 0x10)
{
if(command_ & 0x10) {
sector_++;
goto test_type2_write_protection;
}
@ -667,35 +608,29 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_BYTES(9);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
WAIT_FOR_BYTES(1);
if(is_double_density_)
{
if(is_double_density_) {
WAIT_FOR_BYTES(11);
}
data_mode_ = DataMode::Writing;
begin_writing();
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++)
{
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) {
write_byte(0);
}
WAIT_FOR_EVENT(Event::DataWritten);
if(is_double_density_)
{
if(is_double_density_) {
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
}
else
{
} else {
crc_generator_.reset();
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
@ -715,8 +650,7 @@ void WD1770::posit_event(Event new_event_type)
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3])
{
if(distance_into_section_ == 128 << header_[3]) {
goto type2_write_crc;
}
@ -724,8 +658,7 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_EVENT(Event::DataWritten);
if(status_.data_request)
{
if(status_.data_request) {
end_writing();
update_status([] (Status &status) {
status.lost_data = true;
@ -735,8 +668,7 @@ void WD1770::posit_event(Event new_event_type)
goto type2_write_loop;
type2_write_crc:
{
type2_write_crc: {
uint16_t crc = crc_generator_.get_value();
write_byte(crc >> 8);
write_byte(crc & 0xff);
@ -745,8 +677,7 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_EVENT(Event::DataWritten);
end_writing();
if(command_ & 0x10)
{
if(command_ & 0x10) {
sector_++;
goto test_type2_write_protection;
}
@ -802,13 +733,10 @@ void WD1770::posit_event(Event new_event_type)
read_address_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
if(new_event_type == Event::Token)
{
if(new_event_type == Event::Token) {
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)
{
if(status_.data_request)
{
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
@ -821,10 +749,8 @@ void WD1770::posit_event(Event new_event_type)
});
distance_into_section_++;
if(distance_into_section_ == 7)
{
if(crc_generator_.get_value())
{
if(distance_into_section_ == 7) {
if(crc_generator_.get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
@ -834,8 +760,7 @@ void WD1770::posit_event(Event new_event_type)
}
}
if(index_hole_count_ == 6)
{
if(index_hole_count_ == 6) {
update_status([] (Status &status) {
status.record_not_found = true;
});
@ -849,12 +774,10 @@ void WD1770::posit_event(Event new_event_type)
read_track_read_byte:
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
if(index_hole_count_)
{
if(index_hole_count_) {
goto wait_for_command;
}
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
@ -873,8 +796,7 @@ void WD1770::posit_event(Event new_event_type)
});
write_track_test_write_protect:
if(get_drive_is_read_only())
{
if(get_drive_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@ -885,8 +807,7 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_BYTES(3);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
@ -898,10 +819,8 @@ void WD1770::posit_event(Event new_event_type)
index_hole_count_ = 0;
write_track_write_loop:
if(is_double_density_)
{
switch(data_)
{
if(is_double_density_) {
switch(data_) {
case 0xf5:
write_raw_short(Storage::Encodings::MFM::MFMSync);
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
@ -918,11 +837,8 @@ void WD1770::posit_event(Event new_event_type)
write_byte(data_);
break;
}
}
else
{
switch(data_)
{
} else {
switch(data_) {
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
case 0xfd: case 0xfe:
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
@ -960,16 +876,14 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_EVENT(Event::DataWritten);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
end_writing();
goto wait_for_command;
}
if(index_hole_count_)
{
if(index_hole_count_) {
end_writing();
goto wait_for_command;
}
@ -979,10 +893,8 @@ void WD1770::posit_event(Event new_event_type)
END_SECTION()
}
void WD1770::update_status(std::function<void(Status &)> updater)
{
if(delegate_)
{
void WD1770::update_status(std::function<void(Status &)> updater) {
if(delegate_) {
Status old_status = status_;
updater(status_);
bool did_change =
@ -995,37 +907,29 @@ void WD1770::update_status(std::function<void(Status &)> updater)
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_head_loaded(bool head_loaded)
{
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(Event::HeadLoad);
}
void WD1770::write_bit(int bit)
{
if(is_double_density_)
{
void WD1770::write_bit(int bit) {
if(is_double_density_) {
Controller::write_bit(!bit && !last_bit_);
Controller::write_bit(!!bit);
last_bit_ = bit;
}
else
{
} else {
Controller::write_bit(true);
Controller::write_bit(!!bit);
}
}
void WD1770::write_byte(uint8_t byte)
{
void WD1770::write_byte(uint8_t byte) {
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
crc_generator_.add(byte);
}
void WD1770::write_raw_short(uint16_t value)
{
for(int c = 0; c < 16; c++)
{
void WD1770::write_raw_short(uint16_t value) {
for(int c = 0; c < 16; c++) {
Controller::write_bit(!!((value << c)&0x8000));
}
}

View File

@ -50,12 +50,10 @@ template <class T> class MOS6522 {
};
/*! Sets a register value. */
inline void set_register(int address, uint8_t value)
{
inline void set_register(int address, uint8_t value) {
address &= 0xf;
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
switch(address)
{
switch(address) {
case 0x0:
registers_.output[1] = value;
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
@ -88,8 +86,7 @@ template <class T> class MOS6522 {
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05)
{
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
}
@ -117,19 +114,15 @@ template <class T> class MOS6522 {
registers_.peripheral_control = value;
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
if(value & 0x08)
{
switch(value & 0x0e)
{
if(value & 0x08) {
switch(value & 0x0e) {
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
}
}
if(value & 0x80)
{
switch(value & 0xe0)
{
if(value & 0x80) {
switch(value & 0xe0) {
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
@ -153,12 +146,10 @@ template <class T> class MOS6522 {
}
/*! Gets a register value. */
inline uint8_t get_register(int address)
{
inline uint8_t get_register(int address) {
address &= 0xf;
// printf("6522 %p: %d\n", this, address);
switch(address)
{
switch(address) {
case 0x0:
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
@ -200,15 +191,12 @@ template <class T> class MOS6522 {
return 0xff;
}
inline void set_control_line_input(Port port, Line line, bool value)
{
switch(line)
{
inline void set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if( value != control_inputs_[port].line_one &&
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
)
{
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
@ -220,8 +208,7 @@ template <class T> class MOS6522 {
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;
reevaluate_interrupts();
}
@ -234,8 +221,7 @@ template <class T> class MOS6522 {
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];\
}\
@ -248,15 +234,13 @@ template <class T> class MOS6522 {
// 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;\
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;\
reevaluate_interrupts();\
\
@ -279,28 +263,22 @@ template <class T> class MOS6522 {
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_half_cycles(unsigned int number_of_cycles)
{
if(is_phase2_)
{
inline void run_for_half_cycles(unsigned int number_of_cycles) {
if(is_phase2_) {
phase2();
number_of_cycles--;
}
while(number_of_cycles >= 2)
{
while(number_of_cycles >= 2) {
phase1();
phase2();
number_of_cycles -= 2;
}
if(number_of_cycles)
{
if(number_of_cycles) {
phase1();
is_phase2_ = true;
}
else
{
} else {
is_phase2_ = false;
}
}
@ -311,10 +289,8 @@ template <class T> class MOS6522 {
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
while(number_of_cycles--)
{
inline void run_for_cycles(unsigned int number_of_cycles) {
while(number_of_cycles--) {
phase1();
phase2();
}
@ -324,8 +300,7 @@ template <class T> class MOS6522 {
#undef phase2
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
inline bool get_interrupt_line()
{
inline bool get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
@ -333,8 +308,7 @@ template <class T> class MOS6522 {
MOS6522() :
timer_is_running_{false, false},
last_posted_interrupt_status_(false),
is_phase2_(false)
{}
is_phase2_(false) {}
private:
// Expected to be overridden
@ -344,8 +318,7 @@ template <class T> class MOS6522 {
void set_interrupt_status(bool status) {}
// Input/output multiplexer
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
{
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
@ -355,11 +328,9 @@ template <class T> class MOS6522 {
// Delegate and communications
bool last_posted_interrupt_status_;
inline void reevaluate_interrupts()
{
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;
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
}
@ -404,13 +375,11 @@ class MOS6522IRQDelegate {
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
inline void set_interrupt_delegate(Delegate *delegate)
{
inline void set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
inline void set_interrupt_status(bool new_status)
{
inline void set_interrupt_status(bool new_status) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}

View File

@ -30,8 +30,7 @@ template <class T> class MOS6532 {
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)
{
inline void set_register(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
@ -48,16 +47,13 @@ template <class T> class MOS6532 {
// The timer and edge detect control
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10)
{
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) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
}
else
{
} else {
a7_interrupt_.enabled = !!(address&0x2);
a7_interrupt_.active_on_positive = !!(address & 0x01);
}
@ -65,13 +61,11 @@ template <class T> class MOS6532 {
}
}
inline uint8_t get_register(int address)
{
inline uint8_t get_register(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
case 0x00: case 0x02:
{
case 0x00: case 0x02: {
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);
@ -82,8 +76,7 @@ template <class T> class MOS6532 {
break;
// Timer and interrupt control
case 0x04: case 0x06:
{
case 0x04: case 0x06: {
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
@ -99,8 +92,7 @@ template <class T> class MOS6532 {
}
break;
case 0x05: case 0x07:
{
case 0x05: case 0x07: {
uint8_t value = interrupt_status_;
interrupt_status_ &= ~InterruptFlag::PA7;
evaluate_interrupts();
@ -112,8 +104,7 @@ template <class T> class MOS6532 {
return 0xff;
}
inline void run_for_cycles(unsigned int number_of_cycles)
{
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;
@ -131,23 +122,18 @@ template <class T> class MOS6532 {
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
a7_interrupt_({.last_port_value = 0, .enabled = false}),
interrupt_line_(false),
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false}
{}
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
inline void set_port_did_change(int port)
{
if(!port)
{
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;
if(difference&0x80)
{
if(difference&0x80) {
if(
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
)
{
) {
interrupt_status_ |= InterruptFlag::PA7;
evaluate_interrupts();
}
@ -155,8 +141,7 @@ template <class T> class MOS6532 {
}
}
inline bool get_inerrupt_line()
{
inline bool get_inerrupt_line() {
return interrupt_line_;
}
@ -191,8 +176,7 @@ template <class T> class MOS6532 {
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
void set_irq_line(bool new_value) {}
inline void evaluate_interrupts()
{
inline void evaluate_interrupts() {
interrupt_line_ =
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);

View File

@ -14,18 +14,15 @@ 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
{}
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
void Speaker::set_volume(uint8_t volume)
{
void Speaker::set_volume(uint8_t volume) {
enqueue([=]() {
volume_ = volume;
});
}
void Speaker::set_control(int channel, uint8_t value)
{
void Speaker::set_control(int channel, uint8_t value) {
enqueue([=]() {
control_registers_[channel] = value;
});
@ -108,10 +105,8 @@ static uint8_t noise_pattern[] = {
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);
@ -128,10 +123,8 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
}
}
void Speaker::skip_samples(unsigned int number_of_samples)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void Speaker::skip_samples(unsigned int number_of_samples) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);

View File

@ -43,14 +43,13 @@ 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(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
@ -67,8 +66,7 @@ template <class T> class MOS6560 {
set_output_mode(OutputMode::NTSC);
}
void set_clock_rate(double clock_rate)
{
void set_clock_rate(double clock_rate) {
speaker_->set_input_rate((float)(clock_rate / 4.0));
}
@ -82,8 +80,7 @@ template <class T> class MOS6560 {
/*!
Sets the output mode to either PAL or NTSC.
*/
void set_output_mode(OutputMode output_mode)
{
void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode;
uint8_t luminances[16] = { // range is 04
0, 4, 1, 3, 2, 2, 1, 3,
@ -100,8 +97,7 @@ template <class T> class MOS6560 {
uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
switch(output_mode)
{
switch(output_mode) {
case OutputMode::PAL:
chrominances = pal_chrominances;
display_type = Outputs::CRT::PAL50;
@ -124,8 +120,7 @@ template <class T> class MOS6560 {
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)
// {
// 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));
// break;
@ -134,8 +129,7 @@ template <class T> class MOS6560 {
// break;
// }
for(int c = 0; c < 16; c++)
{
for(int c = 0; c < 16; c++) {
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
}
}
@ -143,23 +137,19 @@ template <class T> class MOS6560 {
/*!
Runs for cycles. Derr.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
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;
while(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_;
// keep track of internal time relative to this scanline
horizontal_counter_++;
full_frame_counter_++;
if(horizontal_counter_ == timing_.cycles_per_line)
{
if(horizontal_drawing_latch_)
{
if(horizontal_counter_ == timing_.cycles_per_line) {
if(horizontal_drawing_latch_) {
current_character_row_++;
if(
(current_character_row_ == 16) ||
@ -179,8 +169,7 @@ template <class T> class MOS6560 {
horizontal_drawing_latch_ = false;
vertical_counter_ ++;
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
{
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
vertical_counter_ = 0;
full_frame_counter_ = 0;
@ -198,11 +187,9 @@ template <class T> class MOS6560 {
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
switch(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_;
}
@ -213,14 +200,10 @@ template <class T> class MOS6560 {
}
uint16_t fetch_address = 0x1c;
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
{
if(column_counter_&1)
{
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
}
else
{
} else {
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
@ -244,8 +227,7 @@ template <class T> class MOS6560 {
// 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;
else
{
else {
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
}
@ -262,10 +244,8 @@ template <class T> class MOS6560 {
this_state_ = State::Sync;
// update the CRT
if(this_state_ != output_state_)
{
switch(output_state_)
{
if(this_state_ != 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;
@ -275,32 +255,24 @@ template <class T> class MOS6560 {
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels)
{
if(output_state_ == State::Pixels) {
pixel_pointer = crt_->allocate_write_area(260);
}
}
cycles_in_state_++;
if(this_state_ == State::Pixels)
{
if(column_counter_&1)
{
if(this_state_ == State::Pixels) {
if(column_counter_&1) {
character_value_ = pixel_data;
if(pixel_pointer)
{
if(pixel_pointer) {
uint8_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8))
{
if(!(character_colour_&0x8)) {
uint8_t colours[2];
if(registers_.invertedCells)
{
if(registers_.invertedCells) {
colours[0] = cell_colour;
colours[1] = registers_.backgroundColour;
}
else
{
} else {
colours[0] = registers_.backgroundColour;
colours[1] = cell_colour;
}
@ -312,9 +284,7 @@ template <class T> class MOS6560 {
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
{
} else {
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
pixel_pointer[0] =
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
@ -327,9 +297,7 @@ template <class T> class MOS6560 {
}
pixel_pointer += 8;
}
}
else
{
} else {
character_code_ = pixel_data;
character_colour_ = colour_data;
}
@ -347,12 +315,10 @@ template <class T> class MOS6560 {
/*!
Writes to a 6560 register.
*/
void set_register(int address, uint8_t value)
{
void set_register(int address, uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address)
{
switch(address) {
case 0x0:
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
registers_.first_column_location = value & 0x7f;
@ -391,11 +357,9 @@ template <class T> class MOS6560 {
speaker_->set_volume(value & 0xf);
break;
case 0xf:
{
case 0xf: {
uint8_t new_border_colour = colours_[value & 0x07];
if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
{
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
output_border(cycles_in_state_ * 4);
cycles_in_state_ = 0;
}
@ -415,12 +379,10 @@ template <class T> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t get_register(int address)
{
uint8_t get_register(int address) {
address &= 0xf;
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
switch(address)
{
switch(address) {
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;
@ -432,8 +394,7 @@ template <class T> class MOS6560 {
std::shared_ptr<Speaker> speaker_;
unsigned int cycles_since_speaker_update_;
void update_audio()
{
void update_audio() {
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
cycles_since_speaker_update_ &= 3;
}
@ -478,8 +439,7 @@ template <class T> class MOS6560 {
uint8_t colours_[16];
uint8_t *pixel_pointer;
void output_border(unsigned int number_of_cycles)
{
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);

View File

@ -11,22 +11,18 @@
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;
// set up envelope lookup tables
for(int c = 0; c < 16; c++)
{
for(int p = 0; p < 32; p++)
{
switch(c)
{
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
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;
@ -69,34 +65,28 @@ AY38910::AY38910() :
// set up volume lookup table
float max_volume = 8192;
float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++)
{
for(int v = 0; v < 16; v++) {
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
}
volumes_[0] = 0;
}
void AY38910::set_clock_rate(double clock_rate)
{
void AY38910::set_clock_rate(double clock_rate) {
set_input_rate((float)clock_rate);
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
{
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
int c = 0;
while((master_divider_&7) && c < number_of_samples)
{
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples)
{
while(c < number_of_samples) {
#define step_channel(c) \
if(tone_counters_[c]) tone_counters_[c]--;\
else\
{\
else {\
tone_outputs_[c] ^= 1;\
tone_counters_[c] = tone_periods_[c];\
}
@ -111,8 +101,7 @@ 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_--;
else
{
else {
noise_counter_ = noise_period_;
noise_output_ ^= noise_shift_register_&1;
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
@ -122,8 +111,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
// ... 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_--;
else
{
else {
envelope_divider_ = envelope_period_;
envelope_position_ ++;
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
@ -131,8 +119,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ic++)
{
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
target[c] = output_volume_;
c++;
master_divider_++;
@ -142,8 +129,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
master_divider_ &= 7;
}
void AY38910::evaluate_output_volume()
{
void AY38910::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
// The output level for a channel is:
@ -180,24 +166,19 @@ void AY38910::evaluate_output_volume()
);
}
void AY38910::select_register(uint8_t r)
{
void AY38910::select_register(uint8_t r) {
selected_register_ = r & 0xf;
}
void AY38910::set_register_value(uint8_t value)
{
void AY38910::set_register_value(uint8_t value) {
registers_[selected_register_] = value;
if(selected_register_ < 14)
{
if(selected_register_ < 14) {
int selected_register = selected_register_;
enqueue([=] () {
uint8_t masked_value = value;
switch(selected_register)
{
switch(selected_register) {
case 0: case 2: case 4:
case 1: case 3: case 5:
{
case 1: case 3: case 5: {
int channel = selected_register >> 1;
if(selected_register & 1)
@ -234,8 +215,7 @@ void AY38910::set_register_value(uint8_t value)
}
}
uint8_t AY38910::get_register_value()
{
uint8_t AY38910::get_register_value() {
// This table ensures that bits that aren't defined within the AY are returned as 1s
// when read. I can't find documentation on this and don't have a machine to test, so
// this is provisionally a guess. TODO: investigate.
@ -247,26 +227,21 @@ uint8_t AY38910::get_register_value()
return registers_[selected_register_] | register_masks[selected_register_];
}
uint8_t AY38910::get_port_output(bool port_b)
{
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
void AY38910::set_data_input(uint8_t r)
{
void AY38910::set_data_input(uint8_t r) {
data_input_ = r;
}
uint8_t AY38910::get_data_output()
{
uint8_t AY38910::get_data_output() {
return data_output_;
}
void AY38910::set_control_lines(ControlLines control_lines)
{
void AY38910::set_control_lines(ControlLines control_lines) {
ControlState new_state;
switch((int)control_lines)
{
switch((int)control_lines) {
default: new_state = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
@ -277,11 +252,9 @@ 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;
switch(new_state)
{
switch(new_state) {
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;

View File

@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_.reset(new std::thread([this]() {
while(!should_destruct_)
{
while(!should_destruct_) {
std::function<void(void)> next_function;
// Take lock, check for a new task
std::unique_lock<std::mutex> lock(queue_mutex_);
if(!pending_tasks_.empty())
{
if(!pending_tasks_.empty()) {
next_function = pending_tasks_.front();
pending_tasks_.pop_front();
}
if(next_function)
{
if(next_function) {
// If there is a task, release lock and perform it
lock.unlock();
next_function();
}
else
{
} else {
// If there isn't a task, atomically block on the processing condition and release the lock
// until there's something pending (and then release it again via scope)
processing_condition_.wait(lock);
@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue()
#endif
}
AsyncTaskQueue::~AsyncTaskQueue()
{
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
dispatch_release(serial_dispatch_queue_);
#else
@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
#endif
}
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
{
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
#ifdef __APPLE__
dispatch_async(serial_dispatch_queue_, ^{function();});
#else
@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
#endif
}
void AsyncTaskQueue::flush()
{
void AsyncTaskQueue::flush() {
#ifdef __APPLE__
dispatch_sync(serial_dispatch_queue_, ^{});
#else

View File

@ -13,11 +13,10 @@
using namespace Commodore::C1540;
Machine::Machine() :
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
serial_port_(new SerialPort),
serial_port_VIA_(new SerialPortVIA)
{
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
serial_port_(new SerialPort),
serial_port_VIA_(new SerialPortVIA) {
// attach the serial port to its VIA and vice versa
serial_port_->set_serial_port_via(serial_port_VIA_);
serial_port_VIA_->set_serial_port(serial_port_);
@ -31,13 +30,11 @@ Machine::Machine() :
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
{
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
/*
Memory map (given that I'm unsure yet on any potential mirroring):
@ -46,27 +43,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
0x1c000x1c0f the drive VIA
0xc0000xffff ROM
*/
if(address < 0x800)
{
if(address < 0x800) {
if(isReadOperation(operation))
*value = ram_[address];
else
ram_[address] = *value;
}
else if(address >= 0xc000)
{
} else if(address >= 0xc000) {
if(isReadOperation(operation))
*value = rom_[address & 0x3fff];
}
else if(address >= 0x1800 && address <= 0x180f)
{
} else if(address >= 0x1800 && address <= 0x180f) {
if(isReadOperation(operation))
*value = serial_port_VIA_->get_register(address);
else
serial_port_VIA_->set_register(address, *value);
}
else if(address >= 0x1c00 && address <= 0x1c0f)
{
} else if(address >= 0x1c00 && address <= 0x1c0f) {
if(isReadOperation(operation))
*value = drive_VIA_.get_register(address);
else
@ -79,20 +69,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
return 1;
}
void Machine::set_rom(const uint8_t *rom)
{
void Machine::set_rom(const uint8_t *rom) {
memcpy(rom_, rom, sizeof(rom_));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
{
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
}
void Machine::run_for_cycles(int number_of_cycles)
{
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
@ -101,38 +88,30 @@ void Machine::run_for_cycles(int number_of_cycles)
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
// both VIAs are connected to the IRQ 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)
{
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff)
{
if((shift_register_ & 0x3ff) == 0x3ff) {
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
{
} else {
drive_VIA_.set_sync_detected(false);
}
bit_window_offset_++;
if(bit_window_offset_ == 8)
{
if(bit_window_offset_ == 8) {
drive_VIA_.set_data_input((uint8_t)shift_register_);
bit_window_offset_ = 0;
if(drive_VIA_.get_should_set_overflow())
{
if(drive_VIA_.get_should_set_overflow()) {
set_overflow_line(true);
}
}
else
set_overflow_line(false);
else set_overflow_line(false);
}
// the 1540 does not recognise index holes
@ -140,32 +119,26 @@ void Machine::process_index_hole() {}
#pragma mak - Drive VIA delegate
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
{
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
step(direction);
}
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
{
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
}
#pragma mark - SerialPortVIA
SerialPortVIA::SerialPortVIA() :
port_b_(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)
{
uint8_t SerialPortVIA::get_port_input(Port port) {
if(port) return port_b_;
return 0xff;
}
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
if(port)
{
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
if(port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
attention_acknowledge_level_ = !(value&0x10);
@ -177,10 +150,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
}
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
{
switch(line)
{
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
default: 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;
@ -193,16 +164,13 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
}
}
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort)
{
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
serial_port_ = serialPort;
}
void SerialPortVIA::update_data_line()
{
void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort)
{
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_)));
@ -211,8 +179,7 @@ void SerialPortVIA::update_data_line()
#pragma mark - DriveVIA
void DriveVIA::set_delegate(Delegate *delegate)
{
void DriveVIA::set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
@ -246,22 +213,19 @@ void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
}
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port)
{
if(port) {
// record drive motor state
drive_motor_ = !!(value&4);
// check for a head step
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference)
{
if(step_difference) {
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_)
{
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
}

View File

@ -10,10 +10,8 @@
using namespace Commodore::Serial;
const char *::Commodore::Serial::StringForLine(Line line)
{
switch(line)
{
const char *::Commodore::Serial::StringForLine(Line line) {
switch(line) {
case ServiceRequest: return "Service request";
case Attention: return "Attention";
case Clock: return "Clock";
@ -22,17 +20,14 @@ const char *::Commodore::Serial::StringForLine(Line line)
}
}
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus)
{
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
port->set_serial_bus(bus);
bus->add_port(port);
}
void Bus::add_port(std::shared_ptr<Port> port)
{
void Bus::add_port(std::shared_ptr<Port> port) {
ports_.push_back(port);
for(int line = (int)ServiceRequest; line <= (int)Reset; line++)
{
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);
@ -41,29 +36,23 @@ void Bus::add_port(std::shared_ptr<Port> port)
}
}
void Bus::set_line_output_did_change(Line line)
{
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)
{
if(locked_port) {
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(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;
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)
{
if(locked_port) {
locked_port->set_input(line, new_line_level);
}
}
@ -72,19 +61,14 @@ void Bus::set_line_output_did_change(Line line)
#pragma mark - The debug port
void DebugPort::set_input(Line line, LineLevel value)
{
void DebugPort::set_input(Line line, LineLevel 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;
}
else
{
if(line == Line::Clock && value)
{
} else {
if(line == Line::Clock && value) {
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
}
incoming_count_--;

View File

@ -78,8 +78,7 @@ namespace Serial {
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();
if(bus) bus->set_line_output_did_change(line);

View File

@ -8,8 +8,7 @@
#include "Vic20.hpp"
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
#define X {NotMapped}

View File

@ -15,10 +15,9 @@
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
user_port_via_.reset(new UserPortVIA);
keyboard_via_.reset(new KeyboardVIA);
@ -48,13 +47,11 @@ Machine::Machine() :
// serial_bus_->add_port(_debugPort);
}
void Machine::set_memory_size(MemorySize size)
{
void Machine::set_memory_size(MemorySize size) {
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
switch(size)
{
switch(size) {
default: break;
case ThreeKB:
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
@ -79,31 +76,26 @@ void Machine::set_memory_size(MemorySize size)
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(processor_read_memory_map_, rom_, rom_address_, rom_length_);
}
}
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length)
{
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
address >>= 10;
length >>= 10;
while(length--)
{
while(length--) {
map[address] = area;
area += 0x400;
address++;
}
}
Machine::~Machine()
{
Machine::~Machine() {
delete[] rom_;
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
// static int logCount = 0;
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
@ -111,8 +103,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// printf("%04x\n", address);
// }
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192))
// {
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) {
// printf("\n[%04x] <- %02x\n", address, *value);
// }
@ -120,11 +111,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
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))
{
if(isReadOperation(operation)) {
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
if((address&0xfc00) == 0x9000)
{
if((address&0xfc00) == 0x9000) {
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);
@ -135,22 +124,17 @@ 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)
{
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end())
{
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) {
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) {
user_port_via_->run_for_cycles(1);
keyboard_via_->run_for_cycles(1);
tape_.run_for_cycles(1);
}
}
}
else
{
} else {
uint8_t *ram = processor_write_memory_map_[address >> 10];
if(ram) ram[address & 0x3ff] = *value;
if((address&0xfc00) == 0x9000)
{
if((address&0xfc00) == 0x9000) {
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);
@ -159,10 +143,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
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_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
@ -181,18 +163,15 @@ 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(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
{
if(use_fast_tape_hack_ && tape_.has_tape()) {
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) {
is_running_at_zero_cost_ = true;
set_clock_is_unlimited(true);
}
if(
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
tape_.get_tape()->is_at_end()
)
{
) {
is_running_at_zero_cost_ = false;
set_clock_is_unlimited(false);
}
@ -203,31 +182,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
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)
{
void Machine::set_region(Commodore::Vic20::Region region) {
region_ = region;
switch(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);
}
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);
}
@ -235,8 +209,7 @@ void Machine::set_region(Commodore::Vic20::Region region)
}
}
void Machine::setup_output(float aspect_ratio)
{
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_);
@ -248,17 +221,14 @@ void Machine::setup_output(float aspect_ratio)
mos6560_->colour_memory = colour_memory_;
}
void Machine::close_output()
{
void Machine::close_output() {
mos6560_ = nullptr;
}
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
{
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
uint8_t *target = nullptr;
size_t max_length = 0x2000;
switch(slot)
{
switch(slot) {
case Kernel: target = kernel_rom_; break;
case Characters: target = character_rom_; max_length = 0x1000; break;
case BASIC: target = basic_rom_; break;
@ -269,29 +239,23 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
return;
}
if(target)
{
if(target) {
size_t length_to_copy = std::min(max_length, length);
memcpy(target, data, length_to_copy);
}
}
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
//{
// if(length > 2)
// {
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) {
// if(length > 2) {
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
// _rom_length = (uint16_t)(length - 2);
//
// // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
// if(_rom_address == 0xa000)
// {
// if(_rom_address == 0xa000) {
// _rom = new uint8_t[0x2000];
// memcpy(_rom, &data[2], length - 2);
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
// }
// else
// {
// } else {
// set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
// }
// }
@ -299,15 +263,12 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
#pragma mar - Tape
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(target.tapes.size())
{
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
tape_.set_tape(target.tapes.front());
}
if(target.disks.size())
{
if(target.disks.size()) {
// construct the 1540
c1540_.reset(new ::Commodore::C1540::Machine);
@ -321,8 +282,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
install_disk_rom();
}
if(target.cartridges.size())
{
if(target.cartridges.size()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
rom_length_ = (uint16_t)(rom_image.size());
@ -332,15 +292,12 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
}
if(should_automatically_load_media_)
{
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
if(should_automatically_load_media_) {
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
switch(target.vic20.memory_model)
{
switch(target.vic20.memory_model) {
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
@ -354,17 +311,14 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
}
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape)
{
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
}
#pragma mark - Disc
void Machine::install_disk_rom()
{
if(drive_rom_ && c1540_)
{
void Machine::install_disk_rom() {
if(drive_rom_ && c1540_) {
c1540_->set_rom(drive_rom_.get());
c1540_->run_for_cycles(2000000);
drive_rom_.reset();
@ -373,45 +327,36 @@ void Machine::install_disk_rom()
#pragma mark - UserPortVIA
uint8_t UserPortVIA::get_port_input(Port port)
{
if(!port)
{
uint8_t UserPortVIA::get_port_input(Port port) {
if(!port) {
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
}
return 0xff;
}
void UserPortVIA::set_control_line_output(Port port, Line line, bool value)
{
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
// if(port == Port::A && line == Line::Two) {
// printf("Tape motor %s\n", value ? "on" : "off");
// }
}
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
{
switch(line)
{
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
default: 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;
}
}
void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
{
if(input != JoystickInput::Right)
{
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
if(input != JoystickInput::Right) {
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
}
}
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
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)
{
if(!port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort)
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
@ -420,38 +365,31 @@ void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
{
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
serial_port_ = serialPort;
}
#pragma mark - KeyboardVIA
KeyboardVIA::KeyboardVIA() : port_b_(0xff)
{
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
clear_all_keys();
}
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed)
{
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
if(isPressed)
columns_[key & 7] &= ~(key >> 3);
else
columns_[key & 7] |= (key >> 3);
}
void KeyboardVIA::clear_all_keys()
{
void KeyboardVIA::clear_all_keys() {
memset(columns_, 0xff, sizeof(columns_));
}
uint8_t KeyboardVIA::get_port_input(Port port)
{
if(!port)
{
uint8_t KeyboardVIA::get_port_input(Port port) {
if(!port) {
uint8_t result = 0xff;
for(int c = 0; c < 8; c++)
{
for(int c = 0; c < 8; c++) {
if(!(activation_mask_&(1 << c)))
result &= columns_[c];
}
@ -461,19 +399,15 @@ uint8_t KeyboardVIA::get_port_input(Port port)
return port_b_;
}
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
if(port)
activation_mask_ = (value & mask) | (~mask);
}
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
{
if(line == Line::Two)
{
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
if(line == Line::Two) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort)
{
if(serialPort) {
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
if(port == Port::A)
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
@ -483,28 +417,23 @@ void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
}
}
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value)
{
if(input == JoystickInput::Right)
{
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
if(input == JoystickInput::Right) {
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
}
}
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
{
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
serial_port_ = serialPort;
}
#pragma mark - SerialPort
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level)
{
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
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)
{
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
user_port_via_ = userPortVIA;
}

View File

@ -129,8 +129,7 @@ class SerialPort : public ::Commodore::Serial::Port {
class Vic6560: public MOS::MOS6560<Vic6560> {
public:
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data)
{
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = colour_memory[address & 0x03ff];
}

View File

@ -19,7 +19,7 @@ namespace ConfigurationTarget {
*/
class Machine {
public:
virtual void configure_as_target(const StaticAnalyser::Target &target) =0;
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
};
}

View File

@ -13,13 +13,12 @@ using namespace Electron;
#pragma mark - Lifecycle
Machine::Machine() :
interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_display_update_(0),
cycles_since_audio_update_(0),
use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0)
{
interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_display_update_(0),
cycles_since_audio_update_(0),
use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
@ -30,8 +29,7 @@ Machine::Machine() :
#pragma mark - Output
void Machine::setup_output(float aspect_ratio)
{
void Machine::setup_output(float aspect_ratio) {
video_output_.reset(new VideoOutput(ram_));
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
@ -41,37 +39,29 @@ void Machine::setup_output(float aspect_ratio)
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
}
void Machine::close_output()
{
void Machine::close_output() {
video_output_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
{
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
{
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return speaker_;
}
#pragma mark - The keyboard
void Machine::clear_all_keys()
{
void Machine::clear_all_keys() {
memset(key_states_, 0, sizeof(key_states_));
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void Machine::set_key_state(uint16_t key, bool isPressed)
{
if(key == KeyBreak)
{
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyBreak) {
set_reset_line(isPressed);
}
else
{
} else {
if(isPressed)
key_states_[key >> 4] |= key&0xf;
else
@ -81,23 +71,18 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
#pragma mark - Machine configuration
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(target.tapes.size())
{
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
tape_.set_tape(target.tapes.front());
}
if(target.disks.size())
{
if(target.disks.size()) {
plus3_.reset(new Plus3);
if(target.acorn.has_dfs)
{
if(target.acorn.has_dfs) {
set_rom(ROMSlot0, dfs_, true);
}
if(target.acorn.has_adfs)
{
if(target.acorn.has_adfs) {
set_rom(ROMSlot4, adfs_, true);
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true);
}
@ -106,28 +91,23 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
}
ROMSlot slot = ROMSlot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges)
{
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) {
set_rom(slot, cartridge->get_segments().front().data, false);
slot = (ROMSlot)(((int)slot + 1)&15);
}
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.acorn.should_shift_restart)
{
if(target.acorn.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
}
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable)
{
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) {
uint8_t *target = nullptr;
switch(slot)
{
switch(slot) {
case ROMSlotDFS: dfs_ = data; return;
case ROMSlotADFS: adfs_ = data; return;
@ -143,18 +123,13 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable
#pragma mark - The bus
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
unsigned int cycles = 1;
if(address < 0x8000)
{
if(isReadOperation(operation))
{
if(address < 0x8000) {
if(isReadOperation(operation)) {
*value = ram_[address];
}
else
{
} else {
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
ram_[address] = *value;
}
@ -162,30 +137,22 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1));
}
else
{
switch(address & 0xff0f)
{
} else {
switch(address & 0xff0f) {
case 0xfe00:
if(isReadOperation(operation))
{
if(isReadOperation(operation)) {
*value = interrupt_status_;
interrupt_status_ &= ~PowerOnReset;
}
else
{
} else {
interrupt_control_ = (*value) & ~1;
evaluate_interrupts();
}
break;
case 0xfe07:
if(!isReadOperation(operation))
{
if(!isReadOperation(operation)) {
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_)
{
if(new_speaker_is_enabled != speaker_is_enabled_) {
update_audio();
speaker_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
@ -202,8 +169,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
case 0xfe02: case 0xfe03:
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
if(!isReadOperation(operation))
{
if(!isReadOperation(operation)) {
update_display();
video_output_->set_register(address, *value);
video_access_range_ = video_output_->get_memory_access_range();
@ -211,23 +177,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
break;
case 0xfe04:
if(isReadOperation(operation))
{
if(isReadOperation(operation)) {
*value = tape_.get_data_register();
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
}
else
{
} else {
tape_.set_data_register(*value);
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
}
break;
case 0xfe05:
if(!isReadOperation(operation))
{
if(!isReadOperation(operation)) {
const uint8_t interruptDisable = (*value)&0xf0;
if( interruptDisable )
{
if( interruptDisable ) {
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
@ -240,15 +201,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
// apply the ULA's test
if(*value & 0x08)
{
if(*value & 0x04)
{
if(*value & 0x08) {
if(*value & 0x04) {
keyboard_is_active_ = false;
basic_is_active_ = false;
}
else
{
} else {
keyboard_is_active_ = !(*value & 0x02);
basic_is_active_ = !keyboard_is_active_;
}
@ -256,8 +213,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
break;
case 0xfe06:
if(!isReadOperation(operation))
{
if(!isReadOperation(operation)) {
update_audio();
speaker_->set_divider(*value);
tape_.set_counter(*value);
@ -265,10 +221,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
break;
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
if(plus3_ && (address&0x00f0) == 0x00c0)
{
if(is_holding_shift_ && address == 0xfcc4)
{
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(is_holding_shift_ && address == 0xfcc4) {
is_holding_shift_ = false;
set_key_state(KeyShift, false);
}
@ -279,22 +233,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
break;
case 0xfc00:
if(plus3_ && (address&0x00f0) == 0x00c0)
{
if(!isReadOperation(operation))
{
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(!isReadOperation(operation)) {
plus3_->set_control_register(*value);
}
else
*value = 1;
} else *value = 1;
}
break;
default:
if(address >= 0xc000)
{
if(isReadOperation(operation))
{
if(address >= 0xc000) {
if(isReadOperation(operation)) {
if(
use_fast_tape_hack_ &&
tape_.has_tape() &&
@ -314,21 +262,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
)
{
) {
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
if(address == 0xf0a8)
{
if(!ram_[0x247] && service_call == 14)
{
if(address == 0xf0a8) {
if(!ram_[0x247] && service_call == 14) {
tape_.set_delegate(nullptr);
// TODO: handle tape wrap around.
int cycles_left_while_plausibly_in_data = 50;
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
while(!tape_.get_tape()->is_at_end())
{
while(!tape_.get_tape()->is_at_end()) {
tape_.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
@ -345,37 +289,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
set_value_of_register(CPU6502::Register::Y, tape_.get_data_register());
*value = 0x60; // 0x60 is RTS
}
else
*value = os_[address & 16383];
else *value = os_[address & 16383];
}
else
*value = 0xea;
}
else
{
else *value = 0xea;
} else {
*value = os_[address & 16383];
}
}
}
else
{
if(isReadOperation(operation))
{
} else {
if(isReadOperation(operation)) {
*value = roms_[active_rom_][address & 16383];
if(keyboard_is_active_)
{
if(keyboard_is_active_) {
*value &= 0xf0;
for(int address_line = 0; address_line < 14; address_line++)
{
for(int address_line = 0; address_line < 14; address_line++) {
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
}
}
if(basic_is_active_)
{
if(basic_is_active_) {
*value &= roms_[ROMSlotBASIC][address & 16383];
}
} else if(rom_write_masks_[active_rom_])
{
} else if(rom_write_masks_[active_rom_]) {
roms_[active_rom_][address & 16383] = *value;
}
}
@ -389,8 +322,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
tape_.run_for_cycles(cycles);
cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0)
{
if(cycles_until_display_interrupt_ < 0) {
signal_interrupt(next_display_interrupt_);
update_display();
queue_next_display_interrupt();
@ -398,11 +330,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
if(typer_) typer_->update((int)cycles);
if(plus3_) plus3_->run_for_cycles(4*cycles);
if(shift_restart_counter_)
{
if(shift_restart_counter_) {
shift_restart_counter_ -= cycles;
if(shift_restart_counter_ <= 0)
{
if(shift_restart_counter_ <= 0) {
shift_restart_counter_ = 0;
set_power_on(true);
set_key_state(KeyShift, true);
@ -413,8 +343,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
return cycles;
}
void Machine::synchronise()
{
void Machine::synchronise() {
update_display();
update_audio();
speaker_->flush();
@ -422,26 +351,21 @@ void Machine::synchronise()
#pragma mark - Deferred scheduling
inline void Machine::update_display()
{
if(cycles_since_display_update_)
{
inline void Machine::update_display() {
if(cycles_since_display_update_) {
video_output_->run_for_cycles((int)cycles_since_display_update_);
cycles_since_display_update_ = 0;
}
}
inline void Machine::queue_next_display_interrupt()
{
inline void Machine::queue_next_display_interrupt() {
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
cycles_until_display_interrupt_ = next_interrupt.cycles;
next_display_interrupt_ = next_interrupt.interrupt;
}
inline void Machine::update_audio()
{
if(cycles_since_audio_update_)
{
inline void Machine::update_audio() {
if(cycles_since_audio_update_) {
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
speaker_->run_for_cycles(difference);
@ -450,26 +374,20 @@ inline void Machine::update_audio()
#pragma mark - Interrupts
inline void Machine::signal_interrupt(Electron::Interrupt interrupt)
{
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
interrupt_status_ |= interrupt;
evaluate_interrupts();
}
inline void Machine::clear_interrupt(Electron::Interrupt interrupt)
{
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
interrupt_status_ &= ~interrupt;
evaluate_interrupts();
}
inline void Machine::evaluate_interrupts()
{
if(interrupt_status_ & interrupt_control_)
{
inline void Machine::evaluate_interrupts() {
if(interrupt_status_ & interrupt_control_) {
interrupt_status_ |= 1;
}
else
{
} else {
interrupt_status_ &= ~1;
}
set_irq_line(interrupt_status_ & 1);
@ -477,8 +395,7 @@ inline void Machine::evaluate_interrupts()
#pragma mark - Tape::Delegate
void Machine::tape_did_change_interrupt_status(Tape *tape)
{
void Machine::tape_did_change_interrupt_status(Tape *tape) {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}

View File

@ -10,23 +10,19 @@
using namespace Electron;
Plus3::Plus3() : WD1770(P1770), last_control_(0)
{
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
set_control_register(last_control_, 0xff);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
if(!drives_[drive])
{
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
void Plus3::set_control_register(uint8_t control)
{
void Plus3::set_control_register(uint8_t control) {
// bit 0 => enable or disable drive 1
// bit 1 => enable or disable drive 2
// bit 2 => side select
@ -37,19 +33,15 @@ void Plus3::set_control_register(uint8_t control)
set_control_register(control, changes);
}
void Plus3::set_control_register(uint8_t control, uint8_t changes)
{
if(changes&3)
{
switch(control&3)
{
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
if(changes&3) {
switch(control&3) {
case 0: selected_drive_ = -1; set_drive(nullptr); break;
default: selected_drive_ = 0; set_drive(drives_[0]); break;
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
}
}
if(changes & 0x04)
{
if(changes & 0x04) {
invalidate_track();
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);

View File

@ -10,37 +10,29 @@
using namespace Electron;
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
{
if(is_enabled_)
{
while(number_of_samples--)
{
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
{
} else {
memset(target, 0, sizeof(int16_t) * number_of_samples);
}
}
void Speaker::skip_samples(unsigned int 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)
{
void Speaker::set_divider(uint8_t divider) {
enqueue([=]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void Speaker::set_is_enabled(bool is_enabled)
{
void Speaker::set_is_enabled(bool is_enabled) {
enqueue([=]() {
is_enabled_ = is_enabled;
counter_ = 0;

View File

@ -11,25 +11,21 @@
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)
{}
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)
{
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)
{
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;
}
@ -44,66 +40,53 @@ void Tape::push_tape_bit(uint16_t bit)
evaluate_interrupts();
}
void Tape::evaluate_interrupts()
{
if(last_posted_interrupt_status_ != interrupt_status_)
{
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)
{
void Tape::clear_interrupts(uint8_t interrupts) {
interrupt_status_ &= ~interrupts;
evaluate_interrupts();
}
void Tape::set_is_in_input_mode(bool is_in_input_mode)
{
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)
{
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)
{
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()
{
uint8_t Tape::get_data_register() {
return (uint8_t)(data_register_ >> 2);
}
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
{
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)
{
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)
{
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)
{
} 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;
@ -111,23 +94,16 @@ void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
}
}
void Tape::run_for_cycles(unsigned int number_of_cycles)
{
if(is_enabled_)
{
if(is_in_input_mode_)
{
if(is_running_)
{
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
{
} 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;
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
push_tape_bit(1);
}
}

View File

@ -8,18 +8,15 @@
#include "Electron.hpp"
int Electron::Machine::get_typer_delay()
{
int Electron::Machine::get_typer_delay() {
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
}
int Electron::Machine::get_typer_frequency()
{
int Electron::Machine::get_typer_frequency() {
return 625*128*2; // accept a new character every two frames
}
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}

View File

@ -37,13 +37,12 @@ namespace {
#pragma mark - Lifecycle
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
current_pixel_line_(-1),
output_position_(0),
screen_mode_(6),
screen_map_pointer_(0),
cycles_into_draw_action_(0)
{
ram_(memory),
current_pixel_line_(-1),
output_position_(0),
screen_mode_(6),
screen_map_pointer_(0),
cycles_into_draw_action_(0) {
memset(palette_, 0xf, sizeof(palette_));
setup_screen_map();
setup_base_address();
@ -62,33 +61,26 @@ VideoOutput::VideoOutput(uint8_t *memory) :
#pragma mark - CRT getter
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
{
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
#pragma mark - Display update methods
void VideoOutput::start_pixel_line()
{
void VideoOutput::start_pixel_line() {
current_pixel_line_ = (current_pixel_line_+1)&255;
if(!current_pixel_line_)
{
if(!current_pixel_line_) {
start_line_address_ = start_screen_address_;
current_character_row_ = 0;
is_blank_line_ = false;
}
else
{
} else {
bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3);
is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249)));
if(!is_blank_line_)
{
if(!is_blank_line_) {
start_line_address_++;
if(current_character_row_ > 7)
{
if(current_character_row_ > 7) {
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
current_character_row_ = 0;
}
@ -99,52 +91,41 @@ void VideoOutput::start_pixel_line()
initial_output_target_ = current_output_target_ = nullptr;
}
void VideoOutput::end_pixel_line()
{
void VideoOutput::end_pixel_line() {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_character_row_++;
}
void VideoOutput::output_pixels(unsigned int number_of_cycles)
{
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(!number_of_cycles) return;
if(is_blank_line_)
{
if(is_blank_line_) {
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
}
else
{
} else {
unsigned int divider = 1;
switch(screen_mode_)
{
switch(screen_mode_) {
case 0: case 3: divider = 2; break;
case 1: case 4: case 6: divider = 4; break;
case 2: case 5: divider = 8; break;
}
if(!initial_output_target_ || divider != current_output_divider_)
{
if(!initial_output_target_ || divider != current_output_divider_) {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_output_divider_ = divider;
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
}
#define get_pixel() \
if(current_screen_address_&32768)\
{\
if(current_screen_address_&32768) {\
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
}\
last_pixel_byte_ = ram_[current_screen_address_];\
current_screen_address_ = current_screen_address_+8
switch(screen_mode_)
{
switch(screen_mode_) {
case 0: case 3:
if(initial_output_target_)
{
while(number_of_cycles--)
{
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
current_output_target_ += 4;
@ -154,10 +135,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
break;
case 1:
if(initial_output_target_)
{
while(number_of_cycles--)
{
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
current_output_target_ += 2;
@ -167,10 +146,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
break;
case 2:
if(initial_output_target_)
{
while(number_of_cycles--)
{
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
current_output_target_ += 1;
@ -180,10 +157,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
break;
case 4: case 6:
if(initial_output_target_)
{
if(current_pixel_column_&1)
{
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 4;
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
@ -191,8 +166,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1)
{
while(number_of_cycles > 1) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
@ -204,8 +178,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles)
{
if(number_of_cycles) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
@ -215,10 +188,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
break;
case 5:
if(initial_output_target_)
{
if(current_pixel_column_&1)
{
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 2;
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
@ -226,8 +197,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1)
{
while(number_of_cycles > 1) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
@ -239,8 +209,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles)
{
if(number_of_cycles) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
@ -254,21 +223,17 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
}
}
void VideoOutput::run_for_cycles(int number_of_cycles)
{
void VideoOutput::run_for_cycles(int number_of_cycles) {
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
while(number_of_cycles)
{
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
number_of_cycles -= time_left_in_action;
cycles_into_draw_action_ += time_left_in_action;
if(cycles_into_draw_action_ == draw_action_length)
{
switch(screen_map_[screen_map_pointer_].type)
{
if(cycles_into_draw_action_ == draw_action_length) {
switch(screen_map_[screen_map_pointer_].type) {
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
@ -283,10 +248,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
#pragma mark - Register hub
void VideoOutput::set_register(int address, uint8_t value)
{
switch(address & 0xf)
{
void VideoOutput::set_register(int address, uint8_t value) {
switch(address & 0xf) {
case 0x02:
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
@ -295,21 +258,18 @@ void VideoOutput::set_register(int address, uint8_t value)
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
break;
case 0x07:
{
case 0x07: {
// update screen mode
uint8_t new_screen_mode = (value >> 3)&7;
if(new_screen_mode == 7) new_screen_mode = 4;
if(new_screen_mode != screen_mode_)
{
if(new_screen_mode != screen_mode_) {
screen_mode_ = new_screen_mode;
setup_base_address();
}
}
break;
case 0x08: case 0x09: case 0x0a: case 0x0b:
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
{
case 0x0c: case 0x0d: case 0x0e: case 0x0f: {
static const int registers[4][4] = {
{10, 8, 2, 0},
{14, 12, 6, 4},
@ -318,8 +278,7 @@ void VideoOutput::set_register(int address, uint8_t value)
};
const int index = (address >> 1)&3;
const uint8_t colour = ~value;
if(address&1)
{
if(address&1) {
palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4);
palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4);
palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4);
@ -327,9 +286,7 @@ void VideoOutput::set_register(int address, uint8_t value)
palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2);
palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2);
}
else
{
} else {
palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1);
palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1);
palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1);
@ -341,8 +298,7 @@ void VideoOutput::set_register(int address, uint8_t value)
// regenerate all palette tables for now
#define pack(a, b) (uint8_t)((a << 4) | (b))
for(int byte = 0; byte < 256; byte++)
{
for(int byte = 0; byte < 256; byte++) {
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
@ -367,10 +323,8 @@ void VideoOutput::set_register(int address, uint8_t value)
}
}
void VideoOutput::setup_base_address()
{
switch(screen_mode_)
{
void VideoOutput::setup_base_address() {
switch(screen_mode_) {
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
case 3: screen_mode_base_address_ = 0x4000; break;
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
@ -380,33 +334,28 @@ void VideoOutput::setup_base_address()
#pragma mark - Interrupts
VideoOutput::Interrupt VideoOutput::get_next_interrupt()
{
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
VideoOutput::Interrupt interrupt;
if(output_position_ < real_time_clock_interrupt_1)
{
if(output_position_ < real_time_clock_interrupt_1) {
interrupt.cycles = real_time_clock_interrupt_1 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
}
if(output_position_ < display_end_interrupt_1)
{
if(output_position_ < display_end_interrupt_1) {
interrupt.cycles = display_end_interrupt_1 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
}
if(output_position_ < real_time_clock_interrupt_2)
{
if(output_position_ < real_time_clock_interrupt_2) {
interrupt.cycles = real_time_clock_interrupt_2 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
}
if(output_position_ < display_end_interrupt_2)
{
if(output_position_ < display_end_interrupt_2) {
interrupt.cycles = display_end_interrupt_2 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
@ -419,34 +368,28 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt()
#pragma mark - RAM timing and access information
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
{
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
unsigned int result = 0;
int position = output_position_ + from_time;
result += 1 + (position&1);
if(screen_mode_ < 4)
{
if(screen_mode_ < 4) {
const int current_column = graphics_column(position + (position&1));
int current_line = graphics_line(position);
if(current_column < 80 && current_line < 256)
{
if(screen_mode_ == 3)
{
if(current_column < 80 && current_line < 256) {
if(screen_mode_ == 3) {
int output_position_line = graphics_line(output_position_);
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
if(implied_row < 8)
result += (unsigned int)(80 - current_column);
}
else
result += (unsigned int)(80 - current_column);
else result += (unsigned int)(80 - current_column);
}
}
return result;
}
VideoOutput::Range VideoOutput::get_memory_access_range()
{
VideoOutput::Range VideoOutput::get_memory_access_range() {
// This can't be more specific than this without applying a lot more thought because of mixed modes:
// suppose a program runs half the screen in an 80-column mode then switches to 40 columns. Then the
// real end address will be at 128*80 + 128*40 after the original base, subject to wrapping that depends
@ -460,8 +403,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range()
#pragma mark - The screen map
void VideoOutput::setup_screen_map()
{
void VideoOutput::setup_screen_map() {
/*
Odd field: Even field:
@ -475,15 +417,11 @@ void VideoOutput::setup_screen_map()
|-B-
*/
for(int c = 0; c < 2; c++)
{
if(c&1)
{
for(int c = 0; c < 2; c++) {
if(c&1) {
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
}
else
{
} else {
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
}
@ -494,15 +432,13 @@ void VideoOutput::setup_screen_map()
}
}
void VideoOutput::emplace_blank_line()
{
void VideoOutput::emplace_blank_line() {
screen_map_.emplace_back(DrawAction::Sync, 9);
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
}
void VideoOutput::emplace_pixel_line()
{
void VideoOutput::emplace_pixel_line() {
// output format is:
// 9 cycles: sync
// ... to 24 cycles: colour burst

View File

@ -10,18 +10,15 @@
#include <cstdlib>
void Memory::Fuzz(uint8_t *buffer, size_t size)
{
void Memory::Fuzz(uint8_t *buffer, size_t size) {
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
unsigned int shift = 1, value = 1;
while(value < divider)
{
while(value < divider) {
value <<= 1;
shift++;
}
for(size_t c = 0; c < size; c++)
{
buffer[c] = (uint8_t)(rand() >> shift);
for(size_t c = 0; c < size; c++) {
buffer[c] = (uint8_t)(std::rand() >> shift);
}
}

View File

@ -19,121 +19,99 @@ namespace {
}
Microdisc::Microdisc() :
irq_enable_(false),
delegate_(nullptr),
paging_flags_(BASICDisable),
head_load_request_counter_(-1),
WD1770(P1793),
last_control_(0)
{
irq_enable_(false),
delegate_(nullptr),
paging_flags_(BASICDisable),
head_load_request_counter_(-1),
WD1770(P1793),
last_control_(0) {
set_control_register(last_control_, 0xff);
}
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
if(!drives_[drive])
{
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
void Microdisc::set_control_register(uint8_t control)
{
void Microdisc::set_control_register(uint8_t control) {
uint8_t changes = last_control_ ^ control;
last_control_ = control;
set_control_register(control, changes);
}
void Microdisc::set_control_register(uint8_t control, uint8_t changes)
{
void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b2: data separator clock rate select (1 = double) [TODO]
// b65: drive select
if((changes >> 5)&3)
{
if((changes >> 5)&3) {
selected_drive_ = (control >> 5)&3;
set_drive(drives_[selected_drive_]);
}
// b4: side select
if(changes & 0x10)
{
if(changes & 0x10) {
unsigned int head = (control & 0x10) ? 1 : 0;
for(int c = 0; c < 4; c++)
{
for(int c = 0; c < 4; c++) {
if(drives_[c]) drives_[c]->set_head(head);
}
}
// b3: double density select (0 = double)
if(changes & 0x08)
{
if(changes & 0x08) {
set_is_double_density(!(control & 0x08));
}
// b0: IRQ enable
if(changes & 0x01)
{
if(changes & 0x01) {
bool had_irq = get_interrupt_request_line();
irq_enable_ = !!(control & 0x01);
bool has_irq = get_interrupt_request_line();
if(has_irq != had_irq && delegate_)
{
if(has_irq != had_irq && delegate_) {
delegate_->wd1770_did_change_output(this);
}
}
// b7: EPROM select (0 = select)
// b1: ROM disable (0 = disable)
if(changes & 0x82)
{
if(changes & 0x82) {
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
}
}
bool Microdisc::get_interrupt_request_line()
{
bool Microdisc::get_interrupt_request_line() {
return irq_enable_ && WD1770::get_interrupt_request_line();
}
uint8_t Microdisc::get_interrupt_request_register()
{
uint8_t Microdisc::get_interrupt_request_register() {
return 0x7f | (WD1770::get_interrupt_request_line() ? 0x00 : 0x80);
}
uint8_t Microdisc::get_data_request_register()
{
uint8_t Microdisc::get_data_request_register() {
return 0x7f | (get_data_request_line() ? 0x00 : 0x80);
}
void Microdisc::set_head_load_request(bool head_load)
{
void Microdisc::set_head_load_request(bool head_load) {
set_motor_on(head_load);
if(head_load)
{
if(head_load) {
head_load_request_counter_ = 0;
}
else
{
} else {
head_load_request_counter_ = head_load_request_counter_target;
set_head_loaded(head_load);
}
}
void Microdisc::run_for_cycles(unsigned int number_of_cycles)
{
if(head_load_request_counter_ < head_load_request_counter_target)
{
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) {
head_load_request_counter_ += number_of_cycles;
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
}
WD::WD1770::run_for_cycles(number_of_cycles);
}
bool Microdisc::get_drive_is_ready()
{
bool Microdisc::get_drive_is_ready() {
return true;
}

View File

@ -12,15 +12,14 @@
using namespace Oric;
Machine::Machine() :
cycles_since_video_update_(0),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false)
{
cycles_since_video_update_(0),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
set_clock_rate(1000000);
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
@ -29,43 +28,35 @@ Machine::Machine() :
Memory::Fuzz(ram_, sizeof(ram_));
}
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(target.tapes.size())
{
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
via_.tape->set_tape(target.tapes.front());
}
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.has_microdisc)
{
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
int drive_index = 0;
for(auto disk : target.disks)
{
for(auto disk : target.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
if(target.oric.use_atmos_rom)
{
if(target.oric.use_atmos_rom) {
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
}
else
{
} else {
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
@ -75,10 +66,8 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
}
}
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data)
{
switch(rom)
{
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
@ -89,30 +78,22 @@ void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data)
}
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
if(address > ram_top_)
{
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end())
{
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
set_value_of_register(CPU6502::A, next_byte);
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
*value = 0x60; // i.e. RTS
}
}
else
{
if((address & 0xff00) == 0x0300)
{
if(microdisc_is_enabled_ && address >= 0x0310)
{
switch(address)
{
} else {
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
@ -125,32 +106,25 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
}
else
{
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
}
}
else
{
} else {
if(isReadOperation(operation))
*value = ram_[address];
else
{
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
ram_[address] = *value;
}
}
}
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();
}
@ -162,45 +136,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
return 1;
}
void Machine::synchronise()
{
void Machine::synchronise() {
update_video();
via_.synchronise();
}
void Machine::update_video()
{
void Machine::update_video() {
video_output_->run_for_cycles(cycles_since_video_update_);
cycles_since_video_update_ = 0;
}
void Machine::setup_output(float aspect_ratio)
{
void Machine::setup_output(float aspect_ratio) {
via_.ay8910.reset(new GI::AY38910());
via_.ay8910->set_clock_rate(1000000);
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void Machine::close_output()
{
void Machine::close_output() {
video_output_.reset();
via_.ay8910.reset();
}
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
set_interrupt_line();
}
void Machine::set_key_state(uint16_t key, bool isPressed)
{
if(key == KeyNMI)
{
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyNMI) {
set_nmi_line(isPressed);
}
else
{
} else {
if(isPressed)
keyboard_->rows[key >> 8] |= (key & 0xff);
else
@ -208,100 +173,80 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
}
}
void Machine::clear_all_keys()
{
void Machine::clear_all_keys() {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
}
void Machine::set_use_fast_tape_hack(bool activate)
{
void Machine::set_use_fast_tape_hack(bool activate) {
use_fast_tape_hack_ = activate;
}
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device)
{
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player)
{
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
// set CB1
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
{
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
{
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return via_.ay8910;
}
void Machine::run_for_cycles(int number_of_cycles)
{
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
}
#pragma mark - The 6522
Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(),
cycles_since_ay_update_(0),
tape(new TapePlayer) {}
MOS::MOS6522<Machine::VIA>(),
cycles_since_ay_update_(0),
tape(new TapePlayer) {}
void Machine::VIA::set_control_line_output(Port port, Line line, bool value)
{
if(line)
{
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
{
if(port)
{
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
}
else
{
} else {
ay8910->set_data_input(value);
}
}
uint8_t Machine::VIA::get_port_input(Port port)
{
if(port)
{
uint8_t Machine::VIA::get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
}
else
{
} else {
return ay8910->get_data_output();
}
}
void Machine::VIA::synchronise()
{
void Machine::VIA::synchronise() {
ay8910->run_for_cycles(cycles_since_ay_update_);
ay8910->flush();
cycles_since_ay_update_ = 0;
}
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles)
{
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
cycles_since_ay_update_ += number_of_cycles;
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
tape->run_for_cycles((int)number_of_cycles);
}
void Machine::VIA::update_ay()
{
void Machine::VIA::update_ay() {
ay8910->run_for_cycles(cycles_since_ay_update_);
cycles_since_ay_update_ = 0;
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
@ -310,45 +255,34 @@ void Machine::VIA::update_ay()
#pragma mark - TapePlayer
Machine::TapePlayer::TapePlayer() :
Storage::Tape::BinaryTapePlayer(1000000)
{}
Storage::Tape::BinaryTapePlayer(1000000) {}
uint8_t Machine::TapePlayer::get_next_byte(bool fast)
{
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
#pragma mark - Microdisc
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc)
{
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable))
{
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
}
else
{
if(flags&Microdisc::PagingFlags::MicrodscDisable)
{
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
}
else
{
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770)
{
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
set_interrupt_line();
}
void Machine::set_interrupt_line()
{
void Machine::set_interrupt_line() {
set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));

View File

@ -1,7 +1,6 @@
#include "Oric.hpp"
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
#define X {NotMapped}

View File

@ -20,14 +20,13 @@ namespace {
}
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
frame_counter_(0), counter_(0),
is_graphics_mode_(false),
character_set_base_address_(0xb400),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2))
{
ram_(memory),
frame_counter_(0), counter_(0),
is_graphics_mode_(false),
character_set_base_address_(0xb400),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
@ -48,16 +47,13 @@ VideoOutput::VideoOutput(uint8_t *memory) :
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device)
{
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
output_device_ = output_device;
crt_->set_output_device(output_device);
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom)
{
for(size_t c = 0; c < 8; c++)
{
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
for(size_t c = 0; c < 8; c++) {
size_t index = (c << 2);
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
@ -66,52 +62,42 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom)
// check for big endianness and byte swap if required
uint16_t test_value = 0x0001;
if(*(uint8_t *)&test_value != 0x01)
{
for(size_t c = 0; c < 8; c++)
{
if(*(uint8_t *)&test_value != 0x01) {
for(size_t c = 0; c < 8; c++) {
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
}
}
}
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
{
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
void VideoOutput::run_for_cycles(int number_of_cycles)
{
void VideoOutput::run_for_cycles(int number_of_cycles) {
// Vertical: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst
// Horizontal: 0223: pixels; otherwise blank; 256259 sync
#define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
while(number_of_cycles)
{
while(number_of_cycles) {
int h_counter = counter_ & 63;
int cycles_run_for = 0;
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_)
{
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
// this is a sync line
cycles_run_for = v_sync_end_position_ - counter_;
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
}
else if(counter_ < 224*64 && h_counter < 40)
{
} else if(counter_ < 224*64 && h_counter < 40) {
// this is a pixel line
if(!h_counter)
{
if(!h_counter) {
ink_ = 0x7;
paper_ = 0x0;
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
set_character_set_base_address();
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
if(!counter_)
{
if(!counter_) {
frame_counter_++;
v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
@ -126,16 +112,12 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
int character_base_address = 0xbb80 + (counter_ >> 9) * 40;
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
while(columns--)
{
while(columns--) {
uint8_t pixels, control_byte;
if(is_graphics_mode_ && counter_ < 200*64)
{
if(is_graphics_mode_ && counter_ < 200*64) {
control_byte = pixels = ram_[pixel_base_address + h_counter];
}
else
{
} else {
int address = character_base_address + h_counter;
control_byte = ram_[address];
int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7);
@ -145,18 +127,13 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0;
pixels &= blink_mask;
if(control_byte & 0x60)
{
if(pixel_target_)
{
if(control_byte & 0x60) {
if(pixel_target_) {
uint16_t colours[2];
if(output_device_ == Outputs::CRT::Monitor)
{
if(output_device_ == Outputs::CRT::Monitor) {
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
}
else
{
} else {
colours[0] = colour_forms_[paper_ ^ inverse_mask];
colours[1] = colour_forms_[ink_ ^ inverse_mask];
}
@ -167,11 +144,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
pixel_target_[4] = colours[(pixels >> 1)&1];
pixel_target_[5] = colours[(pixels >> 0)&1];
}
}
else
{
switch(control_byte & 0x1f)
{
} else {
switch(control_byte & 0x1f) {
case 0x00: ink_ = 0x0; break;
case 0x01: ink_ = 0x4; break;
case 0x02: ink_ = 0x2; break;
@ -206,8 +180,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
default: break;
}
if(pixel_target_)
{
if(pixel_target_) {
pixel_target_[0] = pixel_target_[1] =
pixel_target_[2] = pixel_target_[3] =
pixel_target_[4] = pixel_target_[5] =
@ -218,34 +191,24 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
h_counter++;
}
if(h_counter == 40)
{
if(h_counter == 40) {
crt_->output_data(40 * 6, 1);
}
}
else
{
} else {
// this is a blank line (or the equivalent part of a pixel line)
if(h_counter < 48)
{
if(h_counter < 48) {
cycles_run_for = 48 - h_counter;
clamp(
int period = (counter_ < 224*64) ? 8 : 48;
crt_->output_blank((unsigned int)period * 6);
);
}
else if(h_counter < 54)
{
} else if(h_counter < 54) {
cycles_run_for = 54 - h_counter;
clamp(crt_->output_sync(6 * 6));
}
else if(h_counter < 56)
{
} else if(h_counter < 56) {
cycles_run_for = 56 - h_counter;
clamp(crt_->output_default_colour_burst(2 * 6));
}
else
{
} else {
cycles_run_for = 64 - h_counter;
clamp(crt_->output_blank(8 * 6));
}
@ -256,8 +219,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
}
}
void VideoOutput::set_character_set_base_address()
{
void VideoOutput::set_character_set_base_address() {
if(is_graphics_mode_) character_set_base_address_ = use_alternative_character_set_ ? 0x9c00 : 0x9800;
else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400;
}

View File

@ -12,76 +12,61 @@
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);
}
void Typer::update(int duration)
{
if(string_)
{
if(counter_ < 0 && counter_ + duration >= 0)
{
if(!type_next_character())
{
void Typer::update(int duration) {
if(string_) {
if(counter_ < 0 && counter_ + duration >= 0) {
if(!type_next_character()) {
delegate_->typer_reset(this);
}
}
counter_ += duration;
while(string_ && counter_ > frequency_)
{
while(string_ && counter_ > frequency_) {
counter_ -= frequency_;
if(!type_next_character())
{
if(!type_next_character()) {
delegate_->typer_reset(this);
}
}
}
}
bool Typer::type_next_character()
{
bool Typer::type_next_character() {
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_])
{
if(!string_[string_pointer_]) {
free(string_);
string_ = nullptr;
return false;
}
string_pointer_++;
}
else
{
} else {
phase_++;
}
return true;
}
Typer::~Typer()
{
Typer::~Typer() {
free(string_);
}
#pragma mark - Delegate
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase)
{
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
uint16_t *sequence = sequence_for_character(typer, character);
if(!sequence) return true;
if(!phase) clear_all_keys();
else
{
else {
set_key_state(sequence[phase - 1], true);
return sequence[phase] == Typer::Delegate::EndSequence;
}
@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara
return false;
}
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character)
{
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
return nullptr;
}

View File

@ -45,13 +45,11 @@ class Typer {
class TypeRecipient: public Typer::Delegate {
public:
void set_typer_for_string(const char *string)
{
void set_typer_for_string(const char *string) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
}
void typer_reset(Typer *typer)
{
void typer_reset(Typer *typer) {
clear_all_keys();
typer_.reset();
}

View File

@ -1,11 +0,0 @@
//
// CRC.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRC.hpp"
using namespace NumberTheory;

View File

@ -16,13 +16,10 @@ namespace NumberTheory {
class CRC16 {
public:
CRC16(uint16_t polynomial, uint16_t reset_value) :
reset_value_(reset_value), value_(reset_value)
{
for(int c = 0; c < 256; c++)
{
reset_value_(reset_value), value_(reset_value) {
for(int c = 0; c < 256; c++) {
uint16_t shift_value = (uint16_t)(c << 8);
for(int b = 0; b < 8; b++)
{
for(int b = 0; b < 8; b++) {
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
}

View File

@ -21,7 +21,6 @@
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; };
@ -409,10 +408,8 @@
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; };
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -457,7 +454,6 @@
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
@ -965,13 +961,11 @@
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -1153,7 +1147,6 @@
isa = PBXGroup;
children = (
4B0CCC411C62D0B3001CAC5F /* CRT */,
4B2409531C45AB05004DA684 /* Speaker.cpp */,
4B2409541C45AB05004DA684 /* Speaker.hpp */,
);
name = Outputs;
@ -1669,7 +1662,6 @@
isa = PBXGroup;
children = (
4BB697C61D4B558F00248BDF /* Factors.hpp */,
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
);
name = NumberTheory;
@ -1920,7 +1912,6 @@
children = (
4BF829641D8F732B001BAE39 /* Disk.cpp */,
4BF829651D8F732B001BAE39 /* Disk.hpp */,
4BF829671D8F7361001BAE39 /* File.cpp */,
4BF829681D8F7361001BAE39 /* File.hpp */,
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
@ -2445,7 +2436,6 @@
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
@ -2468,7 +2458,6 @@
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
@ -2478,7 +2467,6 @@
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,

View File

@ -14,8 +14,7 @@
using namespace Outputs::CRT;
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate)
{
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) {
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
const unsigned int syncCapacityLineChargeThreshold = 2;
@ -51,10 +50,8 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
}
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType)
{
switch(displayType)
{
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
switch(displayType) {
case DisplayType::PAL50:
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
break;
@ -77,26 +74,22 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
is_alernate_line_(false) {}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth)
{
CRT(common_output_divisor, buffer_depth) {
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate);
}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth)
{
CRT(common_output_divisor, buffer_depth) {
set_new_display_type(cycles_per_line, displayType);
}
#pragma mark - Sync loop
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
}
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
}
@ -114,8 +107,7 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type)
{
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
number_of_cycles *= time_multiplier_;
@ -138,13 +130,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
uint8_t *next_run = nullptr;
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full())
{
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) {
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
}
if(next_run)
{
if(next_run) {
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
source_phase() = colour_burst_phase_;
@ -159,8 +149,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
if(next_run)
{
if(next_run) {
source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
@ -174,46 +163,37 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_;
if(needs_endpoint)
{
if(needs_endpoint) {
if(
!openGL_output_builder_.array_builder.is_full() &&
!openGL_output_builder_.composite_output_buffer_is_full())
{
if(!is_writing_composite_run_)
{
!openGL_output_builder_.composite_output_buffer_is_full()) {
if(!is_writing_composite_run_) {
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
}
else
{
} else {
// Get and write all those previously unwritten output ys
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
// Construct the output run
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
if(next_run)
{
if(next_run) {
output_x1() = output_run_.x1;
output_position_y() = output_run_.y;
output_tex_y() = output_y;
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
openGL_output_builder_.array_builder.flush(
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size)
{
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
openGL_output_builder_.texture_builder.flush(
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas)
{
for(size_t run = 0; run < number_of_write_areas; run++)
{
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
for(size_t run = 0; run < number_of_write_areas; run++) {
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x;
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0] = write_areas[run].x + write_areas[run].length;
}
});
for(size_t position = 0; position < input_size; position += SourceVertexSize)
{
for(size_t position = 0; position < input_size; position += SourceVertexSize) {
(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
}
});
@ -223,19 +203,15 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
}
}
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
{
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
openGL_output_builder_.increment_composite_output_y();
}
// if this is vertical retrace then adcance a field
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
{
if(delegate_)
{
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
if(delegate_) {
frames_since_last_delegate_call_++;
if(frames_since_last_delegate_call_ == 20)
{
if(frames_since_last_delegate_call_ == 20) {
output_lock.unlock();
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
output_lock.lock();
@ -263,8 +239,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
#pragma mark - stream feeding methods
void CRT::output_scan(const Scan *const scan)
{
void CRT::output_scan(const Scan *const scan) {
const bool this_is_sync = (scan->type == Scan::Type::Sync);
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
is_receiving_sync_ = this_is_sync;
@ -276,8 +251,7 @@ void CRT::output_scan(const Scan *const scan)
bool vsync_requested = false;
// If it has been at least half a line since sync ended, then it is safe to decide whether what ended
// was vertical sync.
if(cycles_since_sync_ > (cycles_per_line_ >> 1))
{
if(cycles_since_sync_ > (cycles_per_line_ >> 1)) {
// If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid
// a mistaken vertical sync due to an aggregate of horizontals.
vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_);
@ -292,10 +266,8 @@ void CRT::output_scan(const Scan *const scan)
const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
// simplified colour burst logic: if it's within the back porch we'll take it
if(scan->type == Scan::Type::ColourBurst)
{
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6)
{
if(scan->type == Scan::Type::ColourBurst) {
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
colour_burst_phase_ = (position_phase + scan->phase) & 255;
colour_burst_amplitude_ = scan->amplitude;
@ -313,8 +285,7 @@ void CRT::output_scan(const Scan *const scan)
/*
These all merely channel into advance_cycles, supplying appropriate arguments
*/
void CRT::output_sync(unsigned int number_of_cycles)
{
void CRT::output_sync(unsigned int number_of_cycles) {
Scan scan{
.type = Scan::Type::Sync,
.number_of_cycles = number_of_cycles
@ -322,8 +293,7 @@ void CRT::output_sync(unsigned int number_of_cycles)
output_scan(&scan);
}
void CRT::output_blank(unsigned int number_of_cycles)
{
void CRT::output_blank(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::Blank,
.number_of_cycles = number_of_cycles
@ -331,8 +301,7 @@ void CRT::output_blank(unsigned int number_of_cycles)
output_scan(&scan);
}
void CRT::output_level(unsigned int number_of_cycles)
{
void CRT::output_level(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::Level,
.number_of_cycles = number_of_cycles,
@ -340,8 +309,7 @@ void CRT::output_level(unsigned int number_of_cycles)
output_scan(&scan);
}
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude)
{
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) {
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
@ -351,8 +319,7 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
output_scan(&scan);
}
void CRT::output_default_colour_burst(unsigned int number_of_cycles)
{
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
@ -362,8 +329,7 @@ void CRT::output_default_colour_burst(unsigned int number_of_cycles)
output_scan(&scan);
}
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
{
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
Scan scan {
.type = Scan::Type::Data,
@ -372,8 +338,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider
output_scan(&scan);
}
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio)
{
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) {
first_cycle_after_sync *= time_multiplier_;
number_of_cycles *= time_multiplier_;
@ -409,13 +374,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
// adjust to ensure aspect ratio is correct
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
float ideal_width = height * adjusted_aspect_ratio;
if(ideal_width > width)
{
if(ideal_width > width) {
start_x -= (ideal_width - width) * 0.5f;
width = ideal_width;
}
else
{
} else {
float ideal_height = width / adjusted_aspect_ratio;
start_y -= (ideal_height - height) * 0.5f;
height = ideal_height;

View File

@ -88,8 +88,7 @@ class CRT {
// queued tasks for the OpenGL queue; performed before the next draw
std::mutex function_mutex_;
std::vector<std::function<void(void)>> enqueued_openGL_functions_;
inline void enqueue_openGL_function(const std::function<void(void)> &function)
{
inline void enqueue_openGL_function(const std::function<void(void)> &function) {
std::lock_guard<std::mutex> function_guard(function_mutex_);
enqueued_openGL_functions_.push_back(function);
}
@ -207,8 +206,7 @@ class CRT {
@param required_length The number of samples to allocate.
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
*/
inline uint8_t *allocate_write_area(size_t required_length)
{
inline uint8_t *allocate_write_area(size_t required_length) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
}
@ -216,8 +214,7 @@ class CRT {
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call.
*/
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
{
std::lock_guard<std::mutex> function_guard(function_mutex_);
for(std::function<void(void)> function : enqueued_openGL_functions_)
@ -236,8 +233,7 @@ class CRT {
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If
@c false then the references are simply marked as invalid.
*/
inline void set_openGL_context_will_change(bool should_delete_resources)
{
inline void set_openGL_context_will_change(bool should_delete_resources) {
enqueue_openGL_function([should_delete_resources, this] {
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
});
@ -250,8 +246,7 @@ class CRT {
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
carrier phase and amplitude.
*/
inline void set_composite_sampling_function(const std::string &shader)
{
inline void set_composite_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_composite_sampling_function(shader);
});
@ -270,22 +265,19 @@ class CRT {
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and
* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
*/
inline void set_rgb_sampling_function(const std::string &shader)
{
inline void set_rgb_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_rgb_sampling_function(shader);
});
}
inline void set_output_device(OutputDevice output_device)
{
inline void set_output_device(OutputDevice output_device) {
enqueue_openGL_function([output_device, this] {
openGL_output_builder_.set_output_device(output_device);
});
}
inline void set_visible_area(Rect visible_area)
{
inline void set_visible_area(Rect visible_area) {
enqueue_openGL_function([visible_area, this] {
openGL_output_builder_.set_visible_area(visible_area);
});
@ -293,8 +285,7 @@ class CRT {
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);
inline void set_delegate(Delegate *delegate)
{
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
};

View File

@ -11,36 +11,29 @@
using namespace Outputs::CRT;
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
output_(output_size, nullptr),
input_(input_size, nullptr)
{}
output_(output_size, nullptr),
input_(input_size, nullptr) {}
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
output_(output_size, submission_function),
input_(input_size, submission_function)
{}
output_(output_size, submission_function),
input_(input_size, submission_function) {}
bool ArrayBuilder::is_full()
{
bool ArrayBuilder::is_full() {
bool was_full;
was_full = is_full_;
return was_full;
}
uint8_t *ArrayBuilder::get_input_storage(size_t size)
{
uint8_t *ArrayBuilder::get_input_storage(size_t size) {
return get_storage(size, input_);
}
uint8_t *ArrayBuilder::get_output_storage(size_t size)
{
uint8_t *ArrayBuilder::get_output_storage(size_t size) {
return get_storage(size, output_);
}
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function)
{
if(!is_full_)
{
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) {
if(!is_full_) {
size_t input_size = 0, output_size = 0;
uint8_t *input = input_.get_unflushed(input_size);
uint8_t *output = output_.get_unflushed(output_size);
@ -51,24 +44,20 @@ void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_s
}
}
void ArrayBuilder::bind_input()
{
void ArrayBuilder::bind_input() {
input_.bind();
}
void ArrayBuilder::bind_output()
{
void ArrayBuilder::bind_output() {
output_.bind();
}
ArrayBuilder::Submission ArrayBuilder::submit()
{
ArrayBuilder::Submission ArrayBuilder::submit() {
ArrayBuilder::Submission submission;
submission.input_size = input_.submit(true);
submission.output_size = output_.submit(false);
if(is_full_)
{
if(is_full_) {
is_full_ = false;
input_.reset();
output_.reset();
@ -78,12 +67,10 @@ ArrayBuilder::Submission ArrayBuilder::submit()
}
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
is_full(false),
submission_function_(submission_function),
allocated_data(0), flushed_data(0), submitted_data(0)
{
if(!submission_function_)
{
is_full(false),
submission_function_(submission_function),
allocated_data(0), flushed_data(0), submitted_data(0) {
if(!submission_function_) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
@ -91,23 +78,19 @@ ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint
data.resize(size);
}
ArrayBuilder::Buffer::~Buffer()
{
ArrayBuilder::Buffer::~Buffer() {
if(!submission_function_)
glDeleteBuffers(1, &buffer);
}
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer)
{
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) {
uint8_t *pointer = buffer.get_storage(size);
if(!pointer) is_full_ = true;
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
{
if(is_full || allocated_data + size > data.size())
{
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) {
if(is_full || allocated_data + size > data.size()) {
is_full = true;
return nullptr;
}
@ -116,20 +99,16 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size)
{
if(is_full)
{
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) {
if(is_full) {
return nullptr;
}
size = allocated_data - flushed_data;
return &data[flushed_data];
}
void ArrayBuilder::Buffer::flush()
{
if(submitted_data)
{
void ArrayBuilder::Buffer::flush() {
if(submitted_data) {
memmove(data.data(), &data[submitted_data], allocated_data - submitted_data);
allocated_data -= submitted_data;
flushed_data -= submitted_data;
@ -139,13 +118,11 @@ void ArrayBuilder::Buffer::flush()
flushed_data = allocated_data;
}
size_t ArrayBuilder::Buffer::submit(bool is_input)
{
size_t ArrayBuilder::Buffer::submit(bool is_input) {
size_t length = flushed_data;
if(submission_function_)
if(submission_function_) {
submission_function_(is_input, data.data(), length);
else
{
} else {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
memcpy(destination, data.data(), length);
@ -156,13 +133,11 @@ size_t ArrayBuilder::Buffer::submit(bool is_input)
return length;
}
void ArrayBuilder::Buffer::bind()
{
void ArrayBuilder::Buffer::bind() {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
}
void ArrayBuilder::Buffer::reset()
{
void ArrayBuilder::Buffer::reset() {
is_full = false;
allocated_data = 0;
flushed_data = 0;

View File

@ -16,8 +16,7 @@
using namespace Outputs::CRT;
namespace
{
namespace {
static const GLenum source_data_texture_unit = GL_TEXTURE0;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
@ -29,14 +28,13 @@ namespace
}
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
visible_area_(Rect(0, 0, 1, 1)),
composite_src_output_y_(0),
last_output_width_(0),
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize)
{
visible_area_(Rect(0, 0, 1, 1)),
composite_src_output_y_(0),
last_output_width_(0),
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) {
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
@ -51,46 +49,37 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
GLint number_of_extensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++)
{
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) {
const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
if(!strcmp(extension_name, "GL_NV_texture_barrier"))
{
if(!strcmp(extension_name, "GL_NV_texture_barrier")) {
supports_texture_barrier = true;
}
}
#endif
// if(supports_texture_barrier)
// {
// if(supports_texture_barrier) {
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
// }
// else
{
// } else {
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
}
// }
}
OpenGLOutputBuilder::~OpenGLOutputBuilder()
{
OpenGLOutputBuilder::~OpenGLOutputBuilder() {
glDeleteVertexArrays(1, &output_vertex_array_);
}
bool OpenGLOutputBuilder::get_is_television_output()
{
bool OpenGLOutputBuilder::get_is_television_output() {
return output_device_ == Television || !rgb_input_shader_program_;
}
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
// lock down any other draw_frames
draw_mutex_.lock();
// establish essentials
if(!output_shader_program_)
{
if(!output_shader_program_) {
prepare_composite_input_shaders();
prepare_rgb_input_shaders();
prepare_source_vertex_array();
@ -102,11 +91,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
set_colour_space_uniforms();
}
if(fence_ != nullptr)
{
if(fence_ != nullptr) {
// if the GPU is still busy, don't wait; we'll catch it next time
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
{
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) {
draw_mutex_.unlock();
return;
}
@ -115,11 +102,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
}
// make sure there's a target to draw to
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
{
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) {
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
if(framebuffer_)
{
if(framebuffer_) {
new_framebuffer->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
@ -155,8 +140,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
};
// for composite video, go through four steps to get to something that can be painted to the output
RenderStage composite_render_stages[] =
{
RenderStage composite_render_stages[] = {
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
@ -164,8 +148,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
};
// for RGB video, there's only two steps
RenderStage rgb_render_stages[] =
{
RenderStage rgb_render_stages[] = {
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr}
@ -173,33 +156,28 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages;
if(array_submission.input_size || array_submission.output_size)
{
if(array_submission.input_size || array_submission.output_size) {
// all drawing will be from the source vertex array and without blending
glBindVertexArray(source_vertex_array_);
glDisable(GL_BLEND);
#ifdef GL_NV_texture_barrier
if(work_texture_)
{
if(work_texture_) {
work_texture_->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
}
#endif
while(active_pipeline->shader)
{
while(active_pipeline->shader) {
// switch to the framebuffer and shader associated with this stage
active_pipeline->shader->bind();
if(!work_texture_)
{
if(!work_texture_) {
active_pipeline->target->bind_framebuffer();
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
// those portions for which no input was provided
// if(!active_pipeline[1].shader)
// {
// if(!active_pipeline[1].shader) {
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// }
@ -222,8 +200,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
glEnable(GL_BLEND);
// update uniforms, then bind the target
if(last_output_width_ != output_width || last_output_height_ != output_height)
{
if(last_output_width_ != output_width || last_output_height_ != output_height) {
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
last_output_width_ = output_width;
last_output_height_ = output_height;
@ -251,8 +228,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
draw_mutex_.unlock();
}
void OpenGLOutputBuilder::reset_all_OpenGL_state()
{
void OpenGLOutputBuilder::reset_all_OpenGL_state() {
composite_input_shader_program_ = nullptr;
composite_separation_filter_program_ = nullptr;
composite_chrominance_filter_shader_program_ = nullptr;
@ -263,22 +239,19 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state()
last_output_width_ = last_output_height_ = 0;
}
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources)
{
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) {
output_mutex_.lock();
reset_all_OpenGL_state();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader)
{
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
composite_shader_ = shader;
reset_all_OpenGL_state();
}
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader)
{
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
rgb_shader_ = shader;
reset_all_OpenGL_state();
@ -286,8 +259,7 @@ void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader)
#pragma mark - Program compilation
void OpenGLOutputBuilder::prepare_composite_input_shaders()
{
void OpenGLOutputBuilder::prepare_composite_input_shaders() {
composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_);
composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
@ -300,24 +272,19 @@ void OpenGLOutputBuilder::prepare_composite_input_shaders()
composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit);
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
if(work_texture_)
{
if(work_texture_) {
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
}
else
{
} else {
composite_input_shader_program_->set_is_double_height(false);
composite_separation_filter_program_->set_is_double_height(false);
composite_chrominance_filter_shader_program_->set_is_double_height(false);
}
}
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
{
if(rgb_shader_.size())
{
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
if(rgb_shader_.size()) {
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
@ -328,10 +295,8 @@ void OpenGLOutputBuilder::prepare_rgb_input_shaders()
}
}
void OpenGLOutputBuilder::prepare_source_vertex_array()
{
if(composite_input_shader_program_)
{
void OpenGLOutputBuilder::prepare_source_vertex_array() {
if(composite_input_shader_program_) {
glBindVertexArray(source_vertex_array_);
array_builder.bind_input();
@ -342,18 +307,15 @@ void OpenGLOutputBuilder::prepare_source_vertex_array()
}
}
void OpenGLOutputBuilder::prepare_output_shader()
{
void OpenGLOutputBuilder::prepare_output_shader() {
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit);
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
output_shader_program_->set_origin_is_double_height(!!work_texture_);
}
void OpenGLOutputBuilder::prepare_output_vertex_array()
{
if(output_shader_program_)
{
void OpenGLOutputBuilder::prepare_output_vertex_array() {
if(output_shader_program_) {
glBindVertexArray(output_vertex_array_);
array_builder.bind_output();
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
@ -363,10 +325,8 @@ void OpenGLOutputBuilder::prepare_output_vertex_array()
#pragma mark - Public Configuration
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
{
if(output_device_ != output_device)
{
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) {
if(output_device_ != output_device) {
output_device_ = output_device;
composite_src_output_y_ = 0;
last_output_width_ = 0;
@ -375,8 +335,7 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
}
}
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
output_mutex_.lock();
input_frequency_ = input_frequency;
cycles_per_line_ = cycles_per_line;
@ -391,8 +350,7 @@ void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int
#pragma mark - Internal Configuration
void OpenGLOutputBuilder::set_colour_space_uniforms()
{
void OpenGLOutputBuilder::set_colour_space_uniforms() {
GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
@ -401,8 +359,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
GLfloat *fromRGB, *toRGB;
switch(colour_space_)
{
switch(colour_space_) {
case ColourSpace::YIQ:
fromRGB = rgbToYIQ;
toRGB = yiqToRGB;
@ -419,54 +376,44 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
}
float OpenGLOutputBuilder::get_composite_output_width() const
{
float OpenGLOutputBuilder::get_composite_output_width() const {
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
}
void OpenGLOutputBuilder::set_output_shader_width()
{
if(output_shader_program_)
{
void OpenGLOutputBuilder::set_output_shader_width() {
if(output_shader_program_) {
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
output_shader_program_->set_input_width_scaler(width);
}
}
void OpenGLOutputBuilder::set_timing_uniforms()
{
void OpenGLOutputBuilder::set_timing_uniforms() {
const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
const float output_width = get_composite_output_width();
const float sample_cycles_per_line = cycles_per_line_ / output_width;
if(composite_separation_filter_program_)
{
if(composite_separation_filter_program_) {
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
composite_separation_filter_program_->set_extension(6.0f);
}
if(composite_chrominance_filter_shader_program_)
{
if(composite_chrominance_filter_shader_program_) {
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
composite_chrominance_filter_shader_program_->set_extension(5.0f);
}
if(rgb_filter_shader_program_)
{
if(rgb_filter_shader_program_) {
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f);
}
if(output_shader_program_)
{
if(output_shader_program_) {
set_output_shader_width();
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
}
if(composite_input_shader_program_)
{
if(composite_input_shader_program_) {
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
composite_input_shader_program_->set_extension(0.0f);
}
if(rgb_input_shader_program_)
{
if(rgb_input_shader_program_) {
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
}
}

View File

@ -106,8 +106,7 @@ class OpenGLOutputBuilder {
OpenGLOutputBuilder(size_t bytes_per_pixel);
~OpenGLOutputBuilder();
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
{
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) {
std::lock_guard<std::mutex> output_guard(output_mutex_);
colour_space_ = colour_space;
colour_cycle_numerator_ = colour_cycle_numerator;
@ -115,33 +114,27 @@ class OpenGLOutputBuilder {
set_colour_space_uniforms();
}
inline void set_visible_area(Rect visible_area)
{
inline void set_visible_area(Rect visible_area) {
visible_area_ = visible_area;
}
inline std::unique_lock<std::mutex> get_output_lock()
{
inline std::unique_lock<std::mutex> get_output_lock() {
return std::unique_lock<std::mutex>(output_mutex_);
}
inline OutputDevice get_output_device()
{
inline OutputDevice get_output_device() {
return output_device_;
}
inline uint16_t get_composite_output_y()
{
inline uint16_t get_composite_output_y() {
return (uint16_t)composite_src_output_y_;
}
inline bool composite_output_buffer_is_full()
{
inline bool composite_output_buffer_is_full() {
return composite_src_output_y_ == IntermediateBufferHeight;
}
inline void increment_composite_output_y()
{
inline void increment_composite_output_y() {
if(!composite_output_buffer_is_full())
composite_src_output_y_++;
}

View File

@ -21,8 +21,7 @@ namespace CRT {
The @c Flywheel will attempt to converge with timing implied by synchronisation pulses.
*/
struct Flywheel
{
struct Flywheel {
/*!
Constructs an instance of @c Flywheel.
@ -61,26 +60,18 @@ struct Flywheel
@returns The next synchronisation event.
*/
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
// do we recognise this hsync, thereby adjusting future time expectations?
if(sync_is_requested)
{
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_)
{
if(sync_is_requested) {
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) {
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
}
else
{
} else {
number_of_surprises_++;
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1))
{
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) {
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2;
}
else
{
} else {
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2;
}
}
@ -90,15 +81,13 @@ struct Flywheel
unsigned int proposed_sync_time = cycles_to_run_for;
// will we end an ongoing retrace?
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_)
{
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) {
proposed_sync_time = retrace_time_ - counter_;
proposed_event = SyncEvent::EndRetrace;
}
// will we start a retrace?
if(counter_ + proposed_sync_time >= expected_next_sync_)
{
if(counter_ + proposed_sync_time >= expected_next_sync_) {
proposed_sync_time = expected_next_sync_ - counter_;
proposed_event = SyncEvent::StartRetrace;
}
@ -115,12 +104,10 @@ struct Flywheel
@param event The synchronisation event to apply after that period.
*/
inline void apply_event(unsigned int cycles_advanced, SyncEvent event)
{
inline void apply_event(unsigned int cycles_advanced, SyncEvent event) {
counter_ += cycles_advanced;
switch(event)
{
switch(event) {
default: return;
case StartRetrace:
counter_before_retrace_ = counter_ - retrace_time_;
@ -135,10 +122,8 @@ struct Flywheel
@returns The current output position.
*/
inline unsigned int get_current_output_position()
{
if(counter_ < retrace_time_)
{
inline unsigned int get_current_output_position() {
if(counter_ < retrace_time_) {
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
if(retrace_distance > counter_before_retrace_) return 0;
return counter_before_retrace_ - retrace_distance;
@ -150,32 +135,28 @@ struct Flywheel
/*!
@returns the amount of time since retrace last began. Time then counts monotonically up from zero.
*/
inline unsigned int get_current_time()
{
inline unsigned int get_current_time() {
return counter_;
}
/*!
@returns whether the output is currently retracing.
*/
inline bool is_in_retrace()
{
inline bool is_in_retrace() {
return counter_ < retrace_time_;
}
/*!
@returns the expected length of the scan period (excluding retrace).
*/
inline unsigned int get_scan_period()
{
inline unsigned int get_scan_period() {
return standard_period_ - retrace_time_;
}
/*!
@returns the expected length of a complete scan and retrace cycle.
*/
inline unsigned int get_standard_period()
{
inline unsigned int get_standard_period() {
return standard_period_;
}
@ -183,8 +164,7 @@ struct Flywheel
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
a low number indicates good synchronisation.
*/
inline unsigned int get_and_reset_number_of_surprises()
{
inline unsigned int get_and_reset_number_of_surprises() {
unsigned int result = number_of_surprises_;
number_of_surprises_ = 0;
return result;
@ -193,8 +173,7 @@ struct Flywheel
/*!
@returns `true` if a sync is expected soon or the time at which it was expected was recent.
*/
inline bool is_near_expected_sync()
{
inline bool is_near_expected_sync() {
return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50;
}

View File

@ -16,8 +16,7 @@
using namespace OpenGL;
namespace {
const OpenGL::Shader::AttributeBinding bindings[] =
{
const OpenGL::Shader::AttributeBinding bindings[] = {
{"inputPosition", 0},
{"outputPosition", 1},
{"phaseAndAmplitude", 2},
@ -26,8 +25,7 @@ namespace {
};
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition)
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition) {
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition";
@ -111,12 +109,10 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::s
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader)
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) {
char *derived_composite_sample = nullptr;
const char *composite_sample = composite_shader.c_str();
if(!composite_shader.size())
{
if(!composite_shader.size()) {
asprintf(&derived_composite_sample,
"%s\n"
"uniform mat3 rgbToLumaChroma;"
@ -158,8 +154,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader)
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader) {
char *fragment_shader;
asprintf(&fragment_shader,
"#version 150\n"
@ -186,8 +181,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader()
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader() {
return make_shader(
"#version 150\n"
@ -219,8 +213,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
"}",false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader()
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() {
return make_shader(
"#version 150\n"
@ -254,8 +247,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
"}", false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
{
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() {
return make_shader(
"#version 150\n"
@ -318,18 +310,15 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
"}", false, false);
}
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height)
{
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) {
set_uniform("outputTextureSize", (GLint)output_width, (GLint)output_height);
}
void IntermediateShader::set_source_texture_unit(GLenum unit)
{
void IntermediateShader::set_source_texture_unit(GLenum unit) {
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
}
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency)
{
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) {
// The process below: the source texture will have bilinear filtering enabled; so by
// sampling at non-integral offsets from the centre the shader can get a weighted sum
// of two source pixels, then scale that once, to do two taps per sample. However
@ -340,8 +329,7 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
GLfloat offsets[5];
unsigned int taps = 11;
// unsigned int taps = 21;
while(1)
{
while(1) {
float coefficients[21];
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
luminance_filter.get_coefficients(coefficients);
@ -352,35 +340,28 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
memset(offsets, 0, sizeof(float)*5);
int halfSize = (taps >> 1);
for(int c = 0; c < taps; c++)
{
for(int c = 0; c < taps; c++) {
if(c < 5) offsets[c] = (halfSize - c);
weights[c] = coefficients[c];
}
break;
// int halfSize = (taps >> 1);
// while(c < halfSize && sample < 5)
// {
// while(c < halfSize && sample < 5) {
// offsets[sample] = (float)(halfSize - c);
// if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
// {
// if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) {
// weights[sample] = coefficients[c] + coefficients[c+1];
// offsets[sample] -= (coefficients[c+1] / weights[sample]);
// c += 2;
// }
// else
// {
// } else {
// weights[sample] = coefficients[c];
// c++;
// }
// sample ++;
// }
// if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
// {
// if(c == halfSize) { // i.e. we finished combining inputs before we ran out of space
// weights[sample] = coefficients[c];
// for(int c = 0; c < sample; c++)
// {
// for(int c = 0; c < sample; c++) {
// weights[sample+c+1] = weights[sample-c-1];
// }
// break;
@ -392,29 +373,24 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
set_uniform("offsets", 1, 5, offsets);
}
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency)
{
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) {
set_filter_coefficients(sampling_rate, colour_burst_frequency);
}
void IntermediateShader::set_extension(float extension)
{
void IntermediateShader::set_extension(float extension) {
set_uniform("extension", extension);
}
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
{
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) {
set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB);
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
}
void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler)
{
void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler) {
set_uniform("widthScalers", input_scaler, output_scaler);
}
void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset)
{
void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset) {
set_uniform("textureHeightDivisor", is_double_height ? 2.0f : 1.0f);
set_uniform("inputVerticalOffset", input_offset);
set_uniform("outputVerticalOffset", output_offset);

View File

@ -14,16 +14,14 @@
using namespace OpenGL;
namespace {
const OpenGL::Shader::AttributeBinding bindings[] =
{
const OpenGL::Shader::AttributeBinding bindings[] = {
{"position", 0},
{"srcCoordinates", 1},
{nullptr}
};
}
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler)
{
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) {
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
char *vertex_shader;
@ -92,8 +90,7 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
return result;
}
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area)
{
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) {
GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
@ -104,13 +101,11 @@ void OutputShader::set_output_size(unsigned int output_width, unsigned int outpu
set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
}
void OutputShader::set_source_texture_unit(GLenum unit)
{
void OutputShader::set_source_texture_unit(GLenum unit) {
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
}
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
GLfloat scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
@ -121,12 +116,10 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
}
void OutputShader::set_input_width_scaler(float input_scaler)
{
void OutputShader::set_input_width_scaler(float input_scaler) {
set_uniform("inputScaler", input_scaler);
}
void OutputShader::set_origin_is_double_height(bool is_double_height)
{
void OutputShader::set_origin_is_double_height(bool is_double_height) {
set_uniform("textureHeightDivisor", is_double_height ? 2 : 1);
}

View File

@ -12,13 +12,11 @@
using namespace OpenGL;
namespace
{
namespace {
Shader *bound_shader = nullptr;
}
GLuint Shader::compile_shader(const std::string &source, GLenum type)
{
GLuint Shader::compile_shader(const std::string &source, GLenum type) {
GLuint shader = glCreateShader(type);
const char *c_str = source.c_str();
glShaderSource(shader, 1, &c_str, NULL);
@ -27,8 +25,7 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type)
#ifdef DEBUG
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if(isCompiled == GL_FALSE)
{
if(isCompiled == GL_FALSE) {
GLint logLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0) {
@ -45,8 +42,7 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type)
return shader;
}
Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings)
{
Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings) {
shader_program_ = glCreateProgram();
GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER);
GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER);
@ -54,10 +50,8 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha
glAttachShader(shader_program_, vertex);
glAttachShader(shader_program_, fragment);
if(attribute_bindings)
{
while(attribute_bindings->name)
{
if(attribute_bindings) {
while(attribute_bindings->name) {
glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name);
attribute_bindings++;
}
@ -68,8 +62,7 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha
#ifdef DEBUG
GLint didLink = 0;
glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink);
if(didLink == GL_FALSE)
{
if(didLink == GL_FALSE) {
GLint logLength;
glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0) {
@ -83,40 +76,33 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha
#endif
}
Shader::~Shader()
{
Shader::~Shader() {
if(bound_shader == this) Shader::unbind();
glDeleteProgram(shader_program_);
}
void Shader::bind()
{
if(bound_shader != this)
{
void Shader::bind() {
if(bound_shader != this) {
glUseProgram(shader_program_);
bound_shader = this;
}
flush_functions();
}
void Shader::unbind()
{
void Shader::unbind() {
bound_shader = nullptr;
glUseProgram(0);
}
GLint Shader::get_attrib_location(const GLchar *name)
{
GLint Shader::get_attrib_location(const GLchar *name) {
return glGetAttribLocation(shader_program_, name);
}
GLint Shader::get_uniform_location(const GLchar *name)
{
GLint Shader::get_uniform_location(const GLchar *name) {
return glGetUniformLocation(shader_program_, name);
}
void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor)
{
void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) {
GLint location = get_attrib_location(name);
glEnableVertexAttribArray((GLuint)location);
glVertexAttribPointer((GLuint)location, size, type, normalised, stride, pointer);
@ -125,104 +111,87 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size,
// The various set_uniforms...
#define location() glGetUniformLocation(shader_program_, name.c_str())
void Shader::set_uniform(const std::string &name, GLint value)
{
void Shader::set_uniform(const std::string &name, GLint value) {
enqueue_function([name, value, this] {
glUniform1i(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLuint value)
{
void Shader::set_uniform(const std::string &name, GLuint value) {
enqueue_function([name, value, this] {
glUniform1ui(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value)
{
void Shader::set_uniform(const std::string &name, GLfloat value) {
enqueue_function([name, value, this] {
glUniform1f(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2)
{
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2) {
enqueue_function([name, value1, value2, this] {
glUniform2i(location(), value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2)
{
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2) {
enqueue_function([name, value1, value2, this] {
GLint location = location();
glUniform2f(location, value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2)
{
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2) {
enqueue_function([name, value1, value2, this] {
glUniform2ui(location(), value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3)
{
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3) {
enqueue_function([name, value1, value2, value3, this] {
glUniform3i(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3)
{
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3) {
enqueue_function([name, value1, value2, value3, this] {
glUniform3f(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3)
{
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3) {
enqueue_function([name, value1, value2, value3, this] {
glUniform3ui(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4)
{
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4) {
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4i(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4)
{
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4) {
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4f(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4)
{
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4) {
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4ui(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values)
{
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values) {
size_t number_of_values = (size_t)count * (size_t)size;
GLint *values_copy = new GLint[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
switch(size) {
case 1: glUniform1iv(location(), count, values_copy); break;
case 2: glUniform2iv(location(), count, values_copy); break;
case 3: glUniform3iv(location(), count, values_copy); break;
@ -232,15 +201,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values)
{
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values) {
size_t number_of_values = (size_t)count * (size_t)size;
GLfloat *values_copy = new GLfloat[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
switch(size) {
case 1: glUniform1fv(location(), count, values_copy); break;
case 2: glUniform2fv(location(), count, values_copy); break;
case 3: glUniform3fv(location(), count, values_copy); break;
@ -250,15 +217,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values)
{
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values) {
size_t number_of_values = (size_t)count * (size_t)size;
GLuint *values_copy = new GLuint[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
switch(size) {
case 1: glUniform1uiv(location(), count, values_copy); break;
case 2: glUniform2uiv(location(), count, values_copy); break;
case 3: glUniform3uiv(location(), count, values_copy); break;
@ -268,21 +233,18 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
});
}
void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values)
{
void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values) {
set_uniform_matrix(name, size, 1, transpose, values);
}
void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values)
{
void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values) {
size_t number_of_values = (size_t)count * (size_t)size * (size_t)size;
GLfloat *values_copy = new GLfloat[number_of_values];
memcpy(values_copy, values, sizeof(*values) * number_of_values);
enqueue_function([name, size, count, transpose, values_copy, this] {
GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
switch(size)
{
switch(size) {
case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy); break;
case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy); break;
case 4: glUniformMatrix4fv(location(), count, glTranspose, values_copy); break;
@ -291,17 +253,14 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou
});
}
void Shader::enqueue_function(std::function<void(void)> function)
{
void Shader::enqueue_function(std::function<void(void)> function) {
std::lock_guard<std::mutex> function_guard(function_mutex_);
enqueued_functions_.push_back(function);
}
void Shader::flush_functions()
{
void Shader::flush_functions() {
std::lock_guard<std::mutex> function_guard(function_mutex_);
for(std::function<void(void)> function : enqueued_functions_)
{
for(std::function<void(void)> function : enqueued_functions_) {
function();
}
enqueued_functions_.clear();

View File

@ -13,10 +13,8 @@
using namespace Outputs::CRT;
static const GLint internalFormatForDepth(size_t depth)
{
switch(depth)
{
static const GLint internalFormatForDepth(size_t depth) {
switch(depth) {
default: return GL_FALSE;
case 1: return GL_R8UI;
case 2: return GL_RG8UI;
@ -25,10 +23,8 @@ static const GLint internalFormatForDepth(size_t depth)
}
}
static const GLenum formatForDepth(size_t depth)
{
switch(depth)
{
static const GLenum formatForDepth(size_t depth) {
switch(depth) {
default: return GL_FALSE;
case 1: return GL_RED_INTEGER;
case 2: return GL_RG_INTEGER;
@ -38,14 +34,13 @@ static const GLenum formatForDepth(size_t depth)
}
TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
bytes_per_pixel_(bytes_per_pixel),
write_areas_start_x_(0),
write_areas_start_y_(0),
is_full_(false),
did_submit_(false),
has_write_area_(false),
number_of_write_areas_(0)
{
bytes_per_pixel_(bytes_per_pixel),
write_areas_start_x_(0),
write_areas_start_y_(0),
is_full_(false),
did_submit_(false),
has_write_area_(false),
number_of_write_areas_(0) {
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
glGenTextures(1, &texture_name_);
@ -58,41 +53,33 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr);
}
TextureBuilder::~TextureBuilder()
{
TextureBuilder::~TextureBuilder() {
glDeleteTextures(1, &texture_name_);
}
inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y)
{
inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) {
return &image_[((y * InputBufferBuilderWidth) + x) * bytes_per_pixel_];
}
uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
{
uint8_t *TextureBuilder::allocate_write_area(size_t required_length) {
if(is_full_) return nullptr;
uint16_t starting_x, starting_y;
if(!number_of_write_areas_)
{
if(!number_of_write_areas_) {
starting_x = write_areas_start_x_;
starting_y = write_areas_start_y_;
}
else
{
} else {
starting_x = write_areas_[number_of_write_areas_ - 1].x + write_areas_[number_of_write_areas_ - 1].length + 1;
starting_y = write_areas_[number_of_write_areas_ - 1].y;
}
WriteArea next_write_area;
if(starting_x + required_length + 2 > InputBufferBuilderWidth)
{
if(starting_x + required_length + 2 > InputBufferBuilderWidth) {
starting_x = 0;
starting_y++;
if(starting_y == InputBufferBuilderHeight)
{
if(starting_y == InputBufferBuilderHeight) {
is_full_ = true;
return nullptr;
}
@ -111,13 +98,11 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
return pointer_to_location(next_write_area.x, next_write_area.y);
}
bool TextureBuilder::is_full()
{
bool TextureBuilder::is_full() {
return is_full_;
}
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
{
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
if(is_full_ || !has_write_area_) return;
has_write_area_ = false;
@ -136,8 +121,7 @@ void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
bytes_per_pixel_);
}
void TextureBuilder::submit()
{
void TextureBuilder::submit() {
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
did_submit_ = true;
@ -148,30 +132,23 @@ void TextureBuilder::submit()
image_.data());
}
void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function)
{
void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function) {
bool was_full = is_full_;
if(did_submit_)
{
if(did_submit_) {
write_areas_start_y_ = write_areas_start_x_ = 0;
is_full_ = false;
}
if(number_of_write_areas_ && !was_full)
{
if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_)
{
for(size_t area = 0; area < number_of_write_areas_; area++)
{
if(number_of_write_areas_ && !was_full) {
if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_) {
for(size_t area = 0; area < number_of_write_areas_; area++) {
WriteArea &write_area = write_areas_[area];
if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth)
{
if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth) {
write_areas_start_x_ = 0;
write_areas_start_y_ ++;
if(write_areas_start_y_ == InputBufferBuilderHeight)
{
if(write_areas_start_y_ == InputBufferBuilderHeight) {
is_full_ = true;
break;
}
@ -186,8 +163,7 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea>
}
}
if(!is_full_)
{
if(!is_full_) {
function(write_areas_, number_of_write_areas_);
write_areas_start_x_ = write_areas_[number_of_write_areas_-1].x + write_areas_[number_of_write_areas_-1].length + 1;

View File

@ -13,14 +13,13 @@
using namespace OpenGL;
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter) :
_width(width),
_height(height),
_pixel_shader(nullptr),
_drawing_vertex_array(0),
_drawing_array_buffer(0),
_set_aspect_ratio(0.0f),
_texture_unit(texture_unit)
{
_width(width),
_height(height),
_pixel_shader(nullptr),
_drawing_vertex_array(0),
_drawing_array_buffer(0),
_set_aspect_ratio(0.0f),
_texture_unit(texture_unit) {
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
@ -42,29 +41,24 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit,
throw ErrorFramebufferIncomplete;
}
TextureTarget::~TextureTarget()
{
TextureTarget::~TextureTarget() {
glDeleteFramebuffers(1, &_framebuffer);
glDeleteTextures(1, &_texture);
if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array);
if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer);
}
void TextureTarget::bind_framebuffer()
{
void TextureTarget::bind_framebuffer() {
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glViewport(0, 0, _width, _height);
}
void TextureTarget::bind_texture()
{
void TextureTarget::bind_texture() {
glBindTexture(GL_TEXTURE_2D, _texture);
}
void TextureTarget::draw(float aspect_ratio)
{
if(!_pixel_shader)
{
void TextureTarget::draw(float aspect_ratio) {
if(!_pixel_shader) {
const char *vertex_shader =
"#version 150\n"
@ -112,8 +106,7 @@ void TextureTarget::draw(float aspect_ratio)
glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0));
}
if(_set_aspect_ratio != aspect_ratio)
{
if(_set_aspect_ratio != aspect_ratio) {
_set_aspect_ratio = aspect_ratio;
float buffer[4*4];

View File

@ -46,16 +46,14 @@ class TextureTarget {
/*!
@returns the width of the texture target.
*/
GLsizei get_width()
{
GLsizei get_width() {
return _width;
}
/*!
@returns the height of the texture target.
*/
GLsizei get_height()
{
GLsizei get_height() {
return _height;
}

View File

@ -1,12 +0,0 @@
//
// Speaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/01/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Speaker.hpp"
using namespace Outputs;

View File

@ -37,8 +37,7 @@ class Speaker {
virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0;
};
float get_ideal_clock_rate_in_range(float minimum, float maximum)
{
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;
@ -52,30 +51,25 @@ class Speaker {
return maximum;
}
void set_output_rate(float cycles_per_second, int buffer_size)
{
void set_output_rate(float cycles_per_second, int buffer_size) {
output_cycles_per_second_ = cycles_per_second;
if(buffer_size_ != buffer_size)
{
if(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)
{
void set_output_quality(int number_of_taps) {
requested_number_of_taps_ = number_of_taps;
set_needs_updated_filter_coefficients();
}
void set_delegate(Delegate *delegate)
{
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void set_input_rate(float cycles_per_second)
{
void set_input_rate(float cycles_per_second) {
input_cycles_per_second_ = cycles_per_second;
set_needs_updated_filter_coefficients();
}
@ -83,8 +77,7 @@ class Speaker {
/*!
Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional.
*/
void set_high_frequency_cut_off(float high_frequency)
{
void set_high_frequency_cut_off(float high_frequency) {
high_frequency_cut_off_ = high_frequency;
set_needs_updated_filter_coefficients();
}
@ -94,21 +87,18 @@ class Speaker {
/*!
Ensures any deferred processing occurs now.
*/
void flush()
{
void flush() {
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)
{
for(auto function : *queued_functions) {
function();
}
});
}
protected:
void enqueue(std::function<void(void)> function)
{
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);
}
@ -124,14 +114,12 @@ class Speaker {
float input_cycles_per_second_, output_cycles_per_second_;
void set_needs_updated_filter_coefficients()
{
void set_needs_updated_filter_coefficients() {
coefficients_are_dirty_ = true;
}
void get_samples(unsigned int quantity, int16_t *target) {}
void skip_samples(unsigned int quantity)
{
void skip_samples(unsigned int quantity) {
int16_t throwaway_samples[quantity];
get_samples(quantity, throwaway_samples);
}
@ -151,22 +139,18 @@ class Speaker {
*/
template <class T> class Filter: public Speaker {
public:
~Filter()
{
~Filter() {
_queue->flush();
}
void run_for_cycles(unsigned int input_cycles)
{
void run_for_cycles(unsigned int input_cycles) {
enqueue([=]() {
unsigned int cycles_remaining = input_cycles;
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)
{
while(cycles_remaining)
{
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_);
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
@ -174,11 +158,9 @@ template <class T> class Filter: public Speaker {
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_)
{
if(delegate_) {
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
}
}
@ -190,26 +172,21 @@ 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_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0))
{
while(cycles_remaining)
{
if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) {
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_]);
cycles_remaining -= 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_++;
// 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_)
{
if(delegate_) {
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
}
}
@ -218,14 +195,11 @@ template <class T> class Filter: public Speaker {
// 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_)
{
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;
}
else
{
} else {
if(steps > number_of_taps_)
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
input_buffer_depth_ = 0;
@ -247,15 +221,11 @@ template <class T> class Filter: public Speaker {
std::unique_ptr<int16_t> input_buffer_;
int input_buffer_depth_;
void update_filter_coefficients()
{
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_;
}
else
{
} 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;
@ -267,12 +237,9 @@ template <class T> class Filter: public Speaker {
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_);
}
else
{
} else {
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));

View File

@ -176,8 +176,7 @@ template <class T> class Processor {
@param program The program to schedule.
*/
inline void schedule_program(const MicroOp *program)
{
inline void schedule_program(const MicroOp *program) {
scheduled_programs_[schedule_programs_write_pointer_] = program;
schedule_programs_write_pointer_ = (schedule_programs_write_pointer_+1)&3;
}
@ -189,8 +188,7 @@ template <class T> class Processor {
@returns The current value of the flags register.
*/
uint8_t get_flags()
{
uint8_t get_flags() {
return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_;
}
@ -201,8 +199,7 @@ template <class T> class Processor {
@param flags The new value of the flags register.
*/
void set_flags(uint8_t flags)
{
void set_flags(uint8_t flags) {
carry_flag_ = flags & Flag::Carry;
negative_result_ = flags & Flag::Sign;
zero_result_ = (~flags) & Flag::Zero;
@ -216,8 +213,7 @@ template <class T> class Processor {
@param operation The operation code for which to schedule a program.
*/
inline void decode_operation(uint8_t operation)
{
inline void decode_operation(uint8_t operation) {
#define Program(...) {__VA_ARGS__, OperationMoveToNextProgram}
#define Absolute CycleLoadAddressAbsolute
@ -539,23 +535,22 @@ template <class T> class Processor {
protected:
Processor() :
schedule_programs_read_pointer_(0),
schedule_programs_write_pointer_(0),
is_jammed_(false),
jam_handler_(nullptr),
cycles_left_to_run_(0),
ready_line_is_enabled_(false),
ready_is_active_(false),
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
inverse_interrupt_flag_(0),
irq_request_history_(0),
s_(0),
next_bus_operation_(BusOperation::None),
interrupt_requests_(InterruptRequestFlags::PowerOn),
irq_line_(0),
nmi_line_is_enabled_(false),
set_overflow_line_is_enabled_(false)
{
schedule_programs_read_pointer_(0),
schedule_programs_write_pointer_(0),
is_jammed_(false),
jam_handler_(nullptr),
cycles_left_to_run_(0),
ready_line_is_enabled_(false),
ready_is_active_(false),
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
inverse_interrupt_flag_(0),
irq_request_history_(0),
s_(0),
next_bus_operation_(BusOperation::None),
interrupt_requests_(InterruptRequestFlags::PowerOn),
irq_line_(0),
nmi_line_is_enabled_(false),
set_overflow_line_is_enabled_(false) {
// only the interrupt flag is defined upon reset but get_flags isn't going to
// mask the other flags so we need to do that, at least
carry_flag_ &= Flag::Carry;
@ -573,16 +568,14 @@ template <class T> class Processor {
@param number_of_cycles The number of cycles to run the 6502 for.
*/
void run_for_cycles(int number_of_cycles)
{
void run_for_cycles(int number_of_cycles) {
static const MicroOp doBranch[] = {
CycleReadFromPC,
CycleAddSignedOperandToPC,
OperationMoveToNextProgram
};
static uint8_t throwaway_target;
static const MicroOp fetch_decode_execute[] =
{
static const MicroOp fetch_decode_execute[] = {
CycleFetchOperation,
CycleFetchOperand,
OperationDecodeOperation,
@ -635,10 +628,8 @@ template <class T> class Processor {
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue);
}
if(!ready_is_active_)
{
if(nextBusOperation != BusOperation::None)
{
if(!ready_is_active_) {
if(nextBusOperation != BusOperation::None) {
bus_access();
}
@ -691,8 +682,7 @@ template <class T> class Processor {
program = scheduled_programs_[scheduleProgramsReadPointer];
continue;
#define push(v) \
{\
#define push(v) {\
uint16_t targetAddress = s_ | 0x100; s_--;\
write_mem(v, targetAddress);\
}
@ -702,8 +692,7 @@ template <class T> class Processor {
case CyclePushPCL: push(pc_.bytes.low); break;
case CyclePushOperand: push(operand_); break;
case CyclePushA: push(a_); break;
case CycleNoWritePush:
{
case CycleNoWritePush: {
uint16_t targetAddress = s_ | 0x100; s_--;
read_mem(operand_, targetAddress);
}
@ -1146,8 +1135,7 @@ template <class T> class Processor {
@param r The register to set.
@returns The value of the register. 8-bit registers will be returned as unsigned.
*/
uint16_t get_value_of_register(Register r)
{
uint16_t get_value_of_register(Register r) {
switch (r) {
case Register::ProgramCounter: return pc_.full;
case Register::LastOperationAddress: return last_operation_pc_.full;
@ -1169,8 +1157,7 @@ template <class T> class Processor {
@param r The register to set.
@param value The value to set. If the register is only 8 bit, the value will be truncated.
*/
void set_value_of_register(Register r, uint16_t value)
{
void set_value_of_register(Register r, uint16_t value) {
switch (r) {
case Register::ProgramCounter: pc_.full = value; break;
case Register::StackPointer: s_ = (uint8_t)value; break;
@ -1187,8 +1174,7 @@ template <class T> class Processor {
Interrupts current execution flow to perform an RTS and, if the 6502 is currently jammed,
to unjam it.
*/
void return_from_subroutine()
{
void return_from_subroutine() {
s_++;
static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.low); s_++;
static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.high);
@ -1204,8 +1190,7 @@ template <class T> class Processor {
@param active @c true if the line is logically active; @c false otherwise.
*/
inline void set_ready_line(bool active)
{
inline void set_ready_line(bool active) {
if(active) {
ready_line_is_enabled_ = true;
} else {
@ -1219,8 +1204,7 @@ template <class T> class Processor {
@param active @c true if the line is logically active; @c false otherwise.
*/
inline void set_reset_line(bool active)
{
inline void set_reset_line(bool active) {
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0);
}
@ -1229,8 +1213,7 @@ template <class T> class Processor {
@returns @c true if the line is logically active; @c false otherwise.
*/
inline bool get_is_resetting()
{
inline bool get_is_resetting() {
return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn));
}
@ -1238,8 +1221,7 @@ template <class T> class Processor {
This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a
reset at the first opportunity. Use @c set_power_on to disable that behaviour.
*/
inline void set_power_on(bool active)
{
inline void set_power_on(bool active) {
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::PowerOn) | (active ? InterruptRequestFlags::PowerOn : 0);
}
@ -1248,8 +1230,7 @@ template <class T> class Processor {
@param active @c true if the line is logically active; @c false otherwise.
*/
inline void set_irq_line(bool active)
{
inline void set_irq_line(bool active) {
irq_line_ = active ? Flag::Interrupt : 0;
}
@ -1258,8 +1239,7 @@ template <class T> class Processor {
@param active @c true if the line is logically active; @c false otherwise.
*/
inline void set_overflow_line(bool active)
{
inline void set_overflow_line(bool active) {
// a leading edge will set the overflow flag
if(active && !set_overflow_line_is_enabled_)
overflow_flag_ = Flag::Overflow;
@ -1271,8 +1251,7 @@ template <class T> class Processor {
@param active `true` if the line is logically active; `false` otherwise.
*/
inline void set_nmi_line(bool active)
{
inline void set_nmi_line(bool active) {
// NMI is edge triggered, not level
if(active && !nmi_line_is_enabled_)
interrupt_requests_ |= InterruptRequestFlags::NMI;
@ -1285,8 +1264,7 @@ template <class T> class Processor {
@returns @c true if the 6502 is jammed; @c false otherwise.
*/
inline bool is_jammed()
{
inline bool is_jammed() {
return is_jammed_;
}
@ -1295,8 +1273,7 @@ template <class T> class Processor {
@param handler The class instance that will be this 6502's jam handler from now on.
*/
inline void set_jam_handler(JamHandler *handler)
{
inline void set_jam_handler(JamHandler *handler) {
jam_handler_ = handler;
}
};

View File

@ -12,13 +12,11 @@
using namespace CPU6502;
AllRAMProcessor::AllRAMProcessor() : _timestamp(0)
{
AllRAMProcessor::AllRAMProcessor() : _timestamp(0) {
set_power_on(false);
}
int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
_timestamp++;
if(isReadOperation(operation)) {
@ -30,13 +28,11 @@ int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint
return 1;
}
void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data)
{
void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data) {
size_t endAddress = std::min(startAddress + length, (size_t)65536);
memcpy(&_memory[startAddress], data, endAddress - startAddress);
}
uint32_t AllRAMProcessor::get_timestamp()
{
uint32_t AllRAMProcessor::get_timestamp() {
return _timestamp;
}

View File

@ -36,34 +36,28 @@ using namespace SignalProcessing;
#define kCSKaiserBesselFilterFixedMultiplier 32767.0f
#define kCSKaiserBesselFilterFixedShift 15
/* ino evaluates the 0th order Bessel function at a */
float FIRFilter::ino(float a)
{
/*! Evaluates the 0th order Bessel function at @c a. */
float FIRFilter::ino(float a) {
float d = 0.0f;
float ds = 1.0f;
float s = 1.0f;
do
{
do {
d += 2.0f;
ds *= (a * a) / (d * d);
s += ds;
}
while(ds > s*1e-6f);
} while(ds > s*1e-6f);
return s;
}
//static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps)
void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps)
{
void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) {
/* calculate alpha, which is the Kaiser-Bessel window shape factor */
float a; // to take the place of alpha in the normal derivation
if(attenuation < 21.0f)
if(attenuation < 21.0f) {
a = 0.0f;
else
{
} else {
if(attenuation > 50.0f)
a = 0.1102f * (attenuation - 8.7f);
else
@ -76,8 +70,7 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici
unsigned int Np = (numberOfTaps - 1) / 2;
float I0 = ino(a);
float NpSquared = (float)(Np * Np);
for(unsigned int i = 0; i <= Np; i++)
{
for(unsigned int i = 0; i <= Np; i++) {
filterCoefficientsFloat[Np + i] =
A[i] *
ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) /
@ -85,38 +78,32 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici
}
/* coefficients are symmetrical, so copy from right hand side to left side */
for(unsigned int i = 0; i < Np; i++)
{
for(unsigned int i = 0; i < Np; i++) {
filterCoefficientsFloat[i] = filterCoefficientsFloat[numberOfTaps - 1 - i];
}
/* scale back up so that we retain 100% of input volume */
float coefficientTotal = 0.0f;
for(unsigned int i = 0; i < numberOfTaps; i++)
{
for(unsigned int i = 0; i < numberOfTaps; i++) {
coefficientTotal += filterCoefficientsFloat[i];
}
/* we'll also need integer versions, potentially */
float coefficientMultiplier = 1.0f / coefficientTotal;
for(unsigned int i = 0; i < numberOfTaps; i++)
{
for(unsigned int i = 0; i < numberOfTaps; i++) {
filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier);
}
delete[] filterCoefficientsFloat;
}
void FIRFilter::get_coefficients(float *coefficients)
{
for(unsigned int i = 0; i < number_of_taps_; i++)
{
void FIRFilter::get_coefficients(float *coefficients) {
for(unsigned int i = 0; i < number_of_taps_; i++) {
coefficients[i] = (float)filter_coefficients_[i] / kCSKaiserBesselFilterFixedMultiplier;
}
}
FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation)
{
FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation) {
// we must be asked to filter based on an odd number of
// taps, and at least three
if(number_of_taps < 3) number_of_taps = 3;
@ -135,8 +122,7 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float
float *A = new float[Np+1];
A[0] = 2.0f * (high_frequency - low_frequency) / input_sample_rate;
for(unsigned int i = 1; i <= Np; i++)
{
for(unsigned int i = 1; i <= Np; i++) {
float iPi = (float)i * (float)M_PI;
A[i] =
(
@ -151,7 +137,6 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float
delete[] A;
}
FIRFilter::~FIRFilter()
{
FIRFilter::~FIRFilter() {
delete[] filter_coefficients_;
}

View File

@ -57,24 +57,21 @@ class FIRFilter {
@param src The source buffer to apply the filter to.
@returns The result of applying the filter.
*/
inline short apply(const short *src)
{
inline short apply(const short *src) {
#ifdef __APPLE__
short result;
vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_);
return result;
#else
int outputValue = 0;
for(unsigned int c = 0; c < number_of_taps_; c++)
{
for(unsigned int c = 0; c < number_of_taps_; c++) {
outputValue += filter_coefficients_[c] * src[c];
}
return (short)(outputValue >> kCSKaiserBesselFilterFixedShift);
#endif
}
inline unsigned int get_number_of_taps()
{
inline unsigned int get_number_of_taps() {
return number_of_taps_;
}

View File

@ -23,8 +23,7 @@ namespace SignalProcessing {
Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
*/
class Stepper
{
class Stepper {
public:
/*!
Establishes a stepper with a one-to-one conversion rate.
@ -48,12 +47,10 @@ class Stepper
@returns the number of output steps.
*/
inline uint64_t step()
{
inline uint64_t step() {
uint64_t update = whole_step_;
accumulated_error_ += adjustment_up_;
if(accumulated_error_ > 0)
{
if(accumulated_error_ > 0) {
update++;
accumulated_error_ -= adjustment_down_;
}
@ -65,12 +62,10 @@ class Stepper
@returns the number of output steps.
*/
inline uint64_t step(uint64_t number_of_steps)
{
inline uint64_t step(uint64_t number_of_steps) {
uint64_t update = whole_step_ * number_of_steps;
accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps;
if(accumulated_error_ > 0)
{
if(accumulated_error_ > 0) {
update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_);
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
}
@ -80,16 +75,14 @@ class Stepper
/*!
@returns the output rate.
*/
inline uint64_t get_output_rate()
{
inline uint64_t get_output_rate() {
return output_rate_;
}
/*!
@returns the input rate.
*/
inline uint64_t get_input_rate()
{
inline uint64_t get_input_rate() {
return input_rate_;
}

View File

@ -14,8 +14,7 @@
using namespace StaticAnalyser::Acorn;
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(false, disk);
@ -34,8 +33,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
catalogue->name = disk_name;
switch((details->data[6] >> 4)&3)
{
switch((details->data[6] >> 4)&3) {
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
@ -44,8 +42,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
// So iterating backwards implies the least amount of seeking.
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8)
{
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
@ -59,8 +56,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
new_file.data.reserve((size_t)data_length);
if(start_sector < 2) continue;
while(data_length > 0)
{
while(data_length > 0) {
uint8_t sector = (uint8_t)(start_sector % 10);
uint8_t track = (uint8_t)(start_sector / 10);
start_sector++;
@ -77,8 +73,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
return catalogue;
}
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(true, disk);
@ -87,8 +82,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++)
{
for(uint8_t c = 2; c < 7; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
@ -99,8 +93,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
switch(free_space_map_second_half->data[0xfd])
{
switch(free_space_map_second_half->data[0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;

View File

@ -1,9 +0,0 @@
//
// File.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "File.hpp"

View File

@ -14,12 +14,10 @@
using namespace StaticAnalyser::Acorn;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges)
{
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges)
{
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
// only one mapped item is allowed
@ -59,11 +57,10 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
}
void StaticAnalyser::Acorn::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination) {
Target target;
target.machine = Target::Electron;
target.probability = 1.0; // TODO: a proper estimation
@ -75,16 +72,14 @@ void StaticAnalyser::Acorn::AddTargets(
target.cartridges = AcornCartridgesFrom(cartridges);
// if there are any tapes, attempt to get data from the first
if(tapes.size() > 0)
{
if(tapes.size() > 0) {
std::shared_ptr<Storage::Tape::Tape> tape = tapes.front();
tape->reset();
std::list<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
if(files.size())
{
if(files.size()) {
bool is_basic = true;
// protected files are always for *RUNning only
@ -95,10 +90,8 @@ void StaticAnalyser::Acorn::AddTargets(
size_t pointer = 0;
uint8_t *data = &files.front().data[0];
size_t data_size = files.front().data.size();
while(1)
{
if(pointer >= data_size-1 || data[pointer] != 13)
{
while(1) {
if(pointer >= data_size-1 || data[pointer] != 13) {
is_basic = false;
break;
}
@ -114,14 +107,12 @@ void StaticAnalyser::Acorn::AddTargets(
}
}
if(disks.size() > 0)
{
if(disks.size() > 0) {
std::shared_ptr<Storage::Disk::Disk> disk = disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue)
{
if(dfs_catalogue || adfs_catalogue) {
target.disks = disks;
target.acorn.has_dfs = !!dfs_catalogue;
target.acorn.has_adfs = !!adfs_catalogue;

View File

@ -14,8 +14,7 @@
using namespace StaticAnalyser::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser)
{
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
int shift_register = 0;
@ -23,14 +22,12 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
// find next area of high tone
while(!tape->is_at_end() && (shift_register != 0x3ff))
{
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
shift();
}
// find next 0x2a (swallowing stop bit)
while(!tape->is_at_end() && (shift_register != 0x254))
{
while(!tape->is_at_end() && (shift_register != 0x254)) {
shift();
}
@ -42,8 +39,7 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
// read out name
char name[11];
int name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name))
{
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = (char)parser.get_next_byte(tape);
if(!name[name_ptr]) break;
name_ptr++;
@ -66,31 +62,25 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++)
{
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back((uint8_t)parser.get_next_byte(tape));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40))
{
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = (uint16_t)parser.get_next_short(tape);
stored_data_crc = (uint16_t)((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
}
else
{
} else {
new_chunk->data_crc_matched = true;
}
return parser.get_error_flag() ? nullptr : std::move(new_chunk);
}
std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
{
std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
// find next chunk with a block number of 0
while(chunks.size() && chunks.front().block_number)
{
while(chunks.size() && chunks.front().block_number) {
chunks.pop_front();
}
@ -101,8 +91,7 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
uint16_t block_number = 0;
while(chunks.size())
{
while(chunks.size()) {
if(chunks.front().block_number != block_number) return nullptr;
bool was_last = chunks.front().block_flag & 0x80;
@ -120,25 +109,21 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// copy all data into a single big block
for(File::Chunk chunk : file->chunks)
{
for(File::Chunk chunk : file->chunks) {
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
}
return file;
}
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Acorn::Parser parser;
// populate chunk list
std::deque<File::Chunk> chunk_list;
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
if(chunk)
{
if(chunk) {
chunk_list.push_back(*chunk);
}
}
@ -146,11 +131,9 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T
// decompose into file list
std::list<File> file_list;
while(chunk_list.size())
{
while(chunk_list.size()) {
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
if(next_file)
{
if(next_file) {
file_list.push_back(*next_file);
}
}

View File

@ -12,8 +12,7 @@
using namespace StaticAnalyser::Atari;
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
{
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
@ -33,10 +32,8 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory
bool has_wide_area_store = false;
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address)
{
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA)
{
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) {
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
@ -52,8 +49,7 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@ -61,8 +57,7 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
)
{
) {
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack;
return;
}
@ -78,14 +73,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
int atari_access_count = 0;
int parker_access_count = 0;
int tigervision_access_count = 0;
for(uint16_t address : internal_accesses)
{
for(uint16_t address : internal_accesses) {
uint16_t masked_address = address & 0x1fff;
atari_access_count += masked_address >= 0x1ff8 && masked_address < 0x1ffa;
parker_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ff8;
}
for(uint16_t address: disassembly.external_stores)
{
for(uint16_t address: disassembly.external_stores) {
uint16_t masked_address = address & 0x1fff;
tigervision_access_count += masked_address == 0x3f;
}
@ -94,8 +87,7 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k;
@ -106,8 +98,7 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const
int atari_access_count = 0;
int mnetwork_access_count = 0;
for(uint16_t address : internal_accesses)
{
for(uint16_t address : internal_accesses) {
uint16_t masked_address = address & 0x1fff;
atari_access_count += masked_address >= 0x1ff6 && masked_address < 0x1ffa;
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
@ -116,18 +107,15 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
target.atari.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
{
if(segment.data.size() == 2048)
{
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
}
@ -145,8 +133,7 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
switch(segment.data.size())
{
switch(segment.data.size()) {
case 8192:
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
@ -173,13 +160,10 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus &&
target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork)
{
target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) {
bool has_superchip = true;
for(size_t address = 0; address < 128; address++)
{
if(segment.data[address] != segment.data[address+128])
{
for(size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
has_superchip = false;
break;
}
@ -188,19 +172,17 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
}
// check for a Tigervision or Tigervision-esque scheme
if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096)
{
if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
}
}
void StaticAnalyser::Atari::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600?
Target target;
target.machine = Target::Atari2600;
@ -212,12 +194,10 @@ void StaticAnalyser::Atari::AddTargets(
target.atari.uses_superchip = false;
// try to figure out the paging scheme
if(!cartridges.empty())
{
if(!cartridges.empty()) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridges.front()->get_segments();
if(segments.size() == 1)
{
if(segments.size() == 1) {
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
DeterminePagingForCartridge(target, segment);
}

View File

@ -21,14 +21,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1)
{
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) {
drive.reset(new Storage::Disk::Drive);
set_drive(drive);
}
struct Sector
{
struct Sector {
uint8_t sector, track;
std::array<uint8_t, 256> data;
bool header_checksum_matched;
@ -40,13 +38,11 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
{
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
if(difference) {
int direction = difference < 0 ? -1 : 1;
difference *= 2 * direction;
@ -69,57 +65,48 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value, unsigned int cycles_since_index_hole)
{
void process_input_bit(int value, unsigned int cycles_since_index_hole) {
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
bit_count_++;
}
unsigned int proceed_to_next_block()
{
unsigned int proceed_to_next_block() {
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2)
{
while(shift_register_ == 0x3ff && index_count_ < 2) {
run_for_cycles(1);
}
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2)
{
while(bit_count_ < 9 && index_count_ < 2) {
run_for_cycles(1);
}
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
unsigned int get_next_byte()
{
unsigned int get_next_byte() {
bit_count_ = 0;
while(bit_count_ < 10) run_for_cycles(1);
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
void proceed_to_shift_value(unsigned int shift_value)
{
void proceed_to_shift_value(unsigned int shift_value) {
index_count_ = 0;
while(shift_register_ != shift_value && index_count_ < 2)
{
while(shift_register_ != shift_value && index_count_ < 2) {
run_for_cycles(1);
}
}
void process_index_hole()
{
void process_index_hole() {
index_count_++;
}
std::shared_ptr<Sector> get_sector(uint8_t sector)
{
std::shared_ptr<Sector> get_sector(uint8_t sector) {
uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
@ -127,24 +114,20 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
std::shared_ptr<Sector> get_next_sector()
{
std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
while(index_count_ < 2)
{
while(index_count_ < 2) {
// look for a sector header
while(1)
{
while(1) {
if(proceed_to_next_block() == 0x08) break;
if(index_count_ >= 2) return nullptr;
}
@ -159,21 +142,18 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
while(1)
{
while(1) {
if(proceed_to_next_block() == 0x07) break;
if(index_count_ >= 2) return nullptr;
}
checksum = 0;
for(size_t c = 0; c < 256; c++)
{
for(size_t c = 0; c < 256; c++) {
sector->data[c] = (uint8_t)get_next_byte();
checksum ^= sector->data[c];
}
if(checksum == get_next_byte())
{
if(checksum == get_next_byte()) {
uint16_t sector_address = (uint16_t)((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
@ -184,8 +164,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
};
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::list<File> files;
CommodoreGCRParser parser;
parser.drive->set_disk(disk);
@ -197,8 +176,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
std::vector<uint8_t> directory;
uint8_t next_track = 18;
uint8_t next_sector = 1;
while(1)
{
while(1) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
@ -210,13 +188,11 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
// parse directory
size_t header_pointer = (size_t)-32;
while(header_pointer+32+31 < directory.size())
{
while(header_pointer+32+31 < directory.size()) {
header_pointer += 32;
File new_file;
switch(directory[header_pointer + 2] & 7)
{
switch(directory[header_pointer + 2] & 7) {
case 0: // DEL files
default: continue; // Unknown file types
@ -230,8 +206,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
next_sector = directory[header_pointer + 4];
new_file.raw_name.reserve(16);
for(size_t c = 0; c < 16; c++)
{
for(size_t c = 0; c < 16; c++) {
new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
}
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
@ -240,8 +215,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
while(next_track)
{
while(next_track) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;

View File

@ -8,8 +8,7 @@
#include "File.hpp"
bool StaticAnalyser::Commodore::File::is_basic()
{
bool StaticAnalyser::Commodore::File::is_basic() {
// BASIC files are always relocatable (?)
if(type != File::RelocatableProgram) return false;
@ -23,15 +22,13 @@ bool StaticAnalyser::Commodore::File::is_basic()
// [4 bytes: this line number]
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)ß
while(1)
{
while(1) {
if(line_address - starting_address >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
if(!next_line_address)
{
if(!next_line_address) {
return true;
}
if(next_line_address < line_address + 5) break;

View File

@ -16,12 +16,10 @@
using namespace StaticAnalyser::Commodore;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges)
{
Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges)
{
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
// only one mapped item is allowed
@ -39,11 +37,10 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
}
void StaticAnalyser::Commodore::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination) {
Target target;
target.machine = Target::Vic20; // TODO: machine estimation
target.probability = 1.0; // TODO: a proper estimation
@ -56,11 +53,9 @@ void StaticAnalyser::Commodore::AddTargets(
target.cartridges = Vic20CartridgesFrom(cartridges);
// check disks
for(auto &disk : disks)
{
for(auto &disk : disks) {
std::list<File> disk_files = GetFiles(disk);
if(disk_files.size())
{
if(disk_files.size()) {
is_disk = true;
files.splice(files.end(), disk_files);
target.disks = disks;
@ -69,36 +64,29 @@ void StaticAnalyser::Commodore::AddTargets(
}
// check tapes
for(auto &tape : tapes)
{
for(auto &tape : tapes) {
std::list<File> tape_files = GetFiles(tape);
if(tape_files.size())
{
if(tape_files.size()) {
files.splice(files.end(), tape_files);
target.tapes = tapes;
if(!device) device = 1;
}
}
if(files.size())
{
if(files.size()) {
target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
if(files.front().is_basic())
{
if(files.front().is_basic()) {
char command[16];
snprintf(command, 16, "LOAD\"%s\",%d,0\nRUN\n", is_disk ? "*" : "", device);
target.loadingCommand = command;
}
else
{
} else {
char command[16];
snprintf(command, 16, "LOAD\"%s\",%d,1\nRUN\n", is_disk ? "*" : "", device);
target.loadingCommand = command;
}
// make a first guess based on loading address
switch(files.front().starting_address)
{
switch(files.front().starting_address) {
case 0x1001:
default: break;
case 0x1201:
@ -110,8 +98,7 @@ void StaticAnalyser::Commodore::AddTargets(
}
// General approach: increase memory size conservatively such that the largest file found will fit.
for(File &file : files)
{
for(File &file : files) {
size_t file_size = file.data.size();
// bool is_basic = file.is_basic();

View File

@ -12,25 +12,20 @@
using namespace StaticAnalyser::Commodore;
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Commodore::Parser parser;
std::list<File> file_list;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
while(!tape->is_at_end())
{
if(!header)
{
while(!tape->is_at_end()) {
if(!header) {
header = parser.get_next_header(tape);
continue;
}
switch(header->type)
{
case Storage::Tape::Commodore::Header::DataSequenceHeader:
{
switch(header->type) {
case Storage::Tape::Commodore::Header::DataSequenceHeader: {
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
@ -39,8 +34,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
new_file.type = File::DataSequence;
new_file.data.swap(header->data);
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
header = parser.get_next_header(tape);
if(!header) continue;
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
@ -52,11 +46,9 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
break;
case Storage::Tape::Commodore::Header::RelocatableProgram:
case Storage::Tape::Commodore::Header::NonRelocatableProgram:
{
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
if(data)
{
if(data) {
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;

View File

@ -16,12 +16,10 @@ struct PartialDisassembly {
std::vector<uint16_t> remaining_entry_points;
};
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, uint16_t entry_point)
{
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
disassembly.disassembly.internal_calls.insert(entry_point);
uint16_t address = entry_point;
while(1)
{
while(1) {
size_t local_address = address_mapper(address);
if(local_address >= memory.size()) return;
@ -33,8 +31,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
uint8_t operation = memory[local_address];
// decode addressing mode
switch(operation&0x1f)
{
switch(operation&0x1f) {
case 0x00:
if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
else if(operation == 0x20) instruction.addressing_mode = Instruction::Absolute;
@ -94,8 +91,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#define IM_INSTRUCTION(base, op) \
case base: instruction.operation = op; break;
switch(operation)
{
switch(operation) {
IM_INSTRUCTION(0x00, Instruction::BRK)
IM_INSTRUCTION(0x20, Instruction::JSR)
IM_INSTRUCTION(0x40, Instruction::RTI)
@ -222,8 +218,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#undef IM_INSTRUCTION
// get operand
switch(instruction.addressing_mode)
{
switch(instruction.addressing_mode) {
// zero-byte operands
case Instruction::Implied:
instruction.operand = 0;
@ -233,8 +228,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Immediate:
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
case Instruction::Relative:
{
case Instruction::Relative: {
size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
address++;
@ -245,8 +239,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
// two-byte operands
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
case Instruction::Indirect:
{
case Instruction::Indirect: {
size_t low_operand_address = address_mapper(address);
size_t high_operand_address = address_mapper(address + 1);
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
@ -261,13 +254,11 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// TODO: something wider-ranging than this
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage)
{
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
size_t mapped_address = address_mapper(instruction.operand);
bool is_external = mapped_address >= memory.size();
switch(instruction.operation)
{
switch(instruction.operation) {
default: break;
case Instruction::LDY: case Instruction::LDX: case Instruction::LDA:
@ -297,31 +288,26 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
// decide on overall flow control
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR)
{
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP)
{
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative)
{
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand);
disassembly.remaining_entry_points.push_back(destination);
}
}
}
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points)
{
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points) {
PartialDisassembly partialDisassembly;
partialDisassembly.remaining_entry_points = entry_points;
while(!partialDisassembly.remaining_entry_points.empty())
{
while(!partialDisassembly.remaining_entry_points.empty()) {
// pull the next entry point from the back of the vector
uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back();
partialDisassembly.remaining_entry_points.pop_back();
@ -340,8 +326,7 @@ Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &mem
return std::move(partialDisassembly.disassembly);
}
std::function<size_t(uint16_t)> StaticAnalyser::MOS6502::OffsetMapper(uint16_t start_address)
{
std::function<size_t(uint16_t)> StaticAnalyser::MOS6502::OffsetMapper(uint16_t start_address) {
return [start_address](uint16_t argument) {
return (size_t)(argument - start_address);
};

View File

@ -13,8 +13,7 @@
using namespace StaticAnalyser::Oric;
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations)
{
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@ -24,8 +23,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const
return score;
}
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@ -49,8 +47,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
return Score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@ -76,11 +73,10 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
}
void StaticAnalyser::Oric::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination) {
Target target;
target.machine = Target::Oric;
target.probability = 1.0;
@ -88,15 +84,11 @@ void StaticAnalyser::Oric::AddTargets(
int basic10_votes = 0;
int basic11_votes = 0;
for(auto tape : tapes)
{
for(auto tape : tapes) {
std::list<File> tape_files = GetFiles(tape);
if(tape_files.size())
{
for(auto file : tape_files)
{
if(file.data_type == File::MachineCode)
{
if(tape_files.size()) {
for(auto file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::MOS6502::OffsetMapper(file.starting_address), entry_points);
@ -113,13 +105,10 @@ void StaticAnalyser::Oric::AddTargets(
}
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
if(!disks.empty())
{
if(!disks.empty()) {
target.oric.has_microdisc = true;
target.disks = disks;
}
else
{
} else {
target.oric.has_microdisc = false;
}

View File

@ -11,14 +11,12 @@
using namespace StaticAnalyser::Oric;
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::list<File> files;
Storage::Tape::Oric::Parser parser;
tape->reset();
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
// sync to next lead-in, check that it's one of three 0x16s
bool is_fast = parser.sync_and_get_encoding_speed(tape);
int next_bytes[2];
@ -29,8 +27,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// get the first byte that isn't a 0x16, check it was a 0x24
int byte = 0x16;
while(!tape->is_at_end() && byte == 0x16)
{
while(!tape->is_at_end() && byte == 0x16) {
byte = parser.get_next_byte(tape, is_fast);
}
if(byte != 0x24) continue;
@ -41,14 +38,12 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// get data and launch types
File new_file;
switch(parser.get_next_byte(tape, is_fast))
{
switch(parser.get_next_byte(tape, is_fast)) {
case 0x00: new_file.data_type = File::ProgramType::BASIC; break;
case 0x80: new_file.data_type = File::ProgramType::MachineCode; break;
default: new_file.data_type = File::ProgramType::None; break;
}
switch(parser.get_next_byte(tape, is_fast))
{
switch(parser.get_next_byte(tape, is_fast)) {
case 0x80: new_file.launch_type = File::ProgramType::BASIC; break;
case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break;
default: new_file.launch_type = File::ProgramType::None; break;
@ -66,8 +61,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// read file name, up to 16 characters and null terminated
char file_name[17];
int name_pos = 0;
while(name_pos < 16)
{
while(name_pos < 16) {
file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
if(!file_name[name_pos]) break;
name_pos++;
@ -78,14 +72,12 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// read body
size_t body_length = new_file.ending_address - new_file.starting_address + 1;
new_file.data.reserve(body_length);
for(size_t c = 0; c < body_length; c++)
{
for(size_t c = 0; c < body_length; c++) {
new_file.data.push_back((uint8_t)parser.get_next_byte(tape, is_fast));
}
// only one validation check: was there enough tape?
if(!tape->is_at_end())
{
if(!tape->is_at_end()) {
files.push_back(new_file);
}
}

View File

@ -8,8 +8,7 @@
#include "CommodoreROM.hpp"
bool Storage::Cartridge::Encodings::CommodoreROM::isROM(const std::vector<uint8_t> &contents)
{
bool Storage::Cartridge::Encodings::CommodoreROM::isROM(const std::vector<uint8_t> &contents) {
return
(
contents.size() == 0x400 ||

View File

@ -13,8 +13,7 @@
using namespace Storage::Cartridge;
BinaryDump::BinaryDump(const char *file_name)
{
BinaryDump::BinaryDump(const char *file_name) {
// the file should be exactly 16 kb
struct stat file_stats;
stat(file_name, &file_stats);

View File

@ -14,8 +14,7 @@
using namespace Storage::Cartridge;
PRG::PRG(const char *file_name)
{
PRG::PRG(const char *file_name) {
struct stat file_stats;
stat(file_name, &file_stats);

View File

@ -8,12 +8,10 @@
#include "Commodore.hpp"
std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted)
{
std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted) {
std::wstring result;
wchar_t unshifted_characters[256] =
{
wchar_t unshifted_characters[256] = {
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/',
@ -32,8 +30,7 @@ std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string,
L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'π',
};
wchar_t shifted_characters[256] =
{
wchar_t shifted_characters[256] = {
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/',
@ -53,8 +50,7 @@ std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string,
};
wchar_t *table = shifted ? shifted_characters : unshifted_characters;
for(int c = 0; c < length; c++)
{
for(int c = 0; c < length; c++) {
wchar_t next_character = table[string[c]];
if(next_character) result.push_back(next_character);
}

View File

@ -13,27 +13,21 @@
using namespace Storage;
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) :
clocks_per_bit_(clocks_per_bit),
tolerance_(tolerance),
phase_(0),
window_length_(clocks_per_bit),
phase_error_pointer_(0)
{
clocks_per_bit_(clocks_per_bit),
tolerance_(tolerance),
phase_(0),
window_length_(clocks_per_bit),
phase_error_pointer_(0) {
phase_error_history_.reset(new std::vector<int>(length_of_history, 0));
}
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
{
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) {
phase_ += number_of_cycles;
if(phase_ >= window_length_)
{
if(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--;
for(int c = 0; c < windows_crossed; c++)
delegate_->digital_phase_locked_loop_output_bit(0);
@ -44,18 +38,15 @@ void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
}
}
void DigitalPhaseLockedLoop::add_pulse()
{
if(!window_was_filled_)
{
void DigitalPhaseLockedLoop::add_pulse() {
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));
}
}
void DigitalPhaseLockedLoop::post_phase_error(int error)
{
void DigitalPhaseLockedLoop::post_phase_error(int error) {
// use a simple spring mechanism as a lowpass filter for phase
phase_ -= (error + 1) >> 1;
@ -67,8 +58,7 @@ void DigitalPhaseLockedLoop::post_phase_error(int 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++)
{
for(size_t c = 0; c < phase_error_history_size; c++) {
total_error += (*phase_error_history)[c];
}
int denominator = (int)(phase_error_history_size * 4);

View File

@ -44,8 +44,7 @@ class DigitalPhaseLockedLoop {
public:
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
};
void set_delegate(Delegate *delegate)
{
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}

View File

@ -10,13 +10,11 @@
using namespace Storage::Disk;
int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position)
{
int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position) {
return (int)(position * get_head_count() + head);
}
void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track)
{
void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
if(get_is_read_only()) return;
int address = get_id_for_track_at_position(head, position);
@ -29,8 +27,7 @@ void Disk::set_track_at_position(unsigned int head, unsigned int position, const
});
}
std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned int position) {
int address = get_id_for_track_at_position(head, position);
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
if(cached_track != cached_tracks_.end()) return cached_track->second;
@ -43,7 +40,6 @@ std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned i
void Disk::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex) {}
void Disk::flush_updates()
{
void Disk::flush_updates() {
if(update_queue_) update_queue_->flush();
}

View File

@ -12,30 +12,27 @@
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),
rotational_multiplier_(60u, revolutions_per_minute),
clock_rate_(clock_rate * clock_rate_multiplier),
clock_rate_multiplier_(clock_rate_multiplier),
rotational_multiplier_(60u, revolutions_per_minute),
cycles_since_index_hole_(0),
motor_is_on_(false),
cycles_since_index_hole_(0),
motor_is_on_(false),
is_reading_(true),
is_reading_(true),
TimedEventLoop(clock_rate * clock_rate_multiplier)
{
TimedEventLoop(clock_rate * clock_rate_multiplier) {
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
Time one(1);
set_expected_bit_length(one);
}
void Controller::setup_track()
{
void Controller::setup_track() {
track_ = drive_->get_track();
Time offset;
Time track_time_now = get_time_into_track();
if(track_)
{
if(track_) {
Time time_found = track_->seek_to(track_time_now);
offset = track_time_now - time_found;
}
@ -43,21 +40,17 @@ void Controller::setup_track()
get_next_event(offset);
}
void Controller::run_for_cycles(int number_of_cycles)
{
void Controller::run_for_cycles(int number_of_cycles) {
Time zero(0);
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_;
while(number_of_cycles)
{
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);
if(!is_reading_ && cycles_until_bits_written_ > zero)
{
if(!is_reading_ && cycles_until_bits_written_ > zero) {
int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int();
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
@ -66,25 +59,18 @@ void Controller::run_for_cycles(int number_of_cycles)
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
number_of_cycles -= cycles_to_run_for;
if(is_reading_)
{
if(is_reading_) {
pll_->run_for_cycles(cycles_to_run_for);
}
else
{
if(cycles_until_bits_written_ > zero)
{
} else {
if(cycles_until_bits_written_ > zero) {
Storage::Time cycles_to_run_for_time(cycles_to_run_for);
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
{
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
process_write_completed();
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
cycles_until_bits_written_.set_zero();
else
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
else
{
} else {
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
}
@ -96,12 +82,10 @@ void Controller::run_for_cycles(int number_of_cycles)
#pragma mark - Track timed event loop
void Controller::get_next_event(const Time &duration_already_passed)
{
if(track_)
void Controller::get_next_event(const Time &duration_already_passed) {
if(track_) {
current_event_ = track_->get_next_event();
else
{
} else {
current_event_.length.length = 1;
current_event_.length.clock_rate = 1;
current_event_.type = Track::Event::IndexHole;
@ -114,8 +98,7 @@ void Controller::get_next_event(const Time &duration_already_passed)
void Controller::process_next_event()
{
switch(current_event_.type)
{
switch(current_event_.type) {
case Track::Event::FluxTransition:
if(is_reading_) pll_->add_pulse();
break;
@ -128,8 +111,7 @@ void Controller::process_next_event()
get_next_event(Time(0));
}
Storage::Time Controller::get_time_into_track()
{
Storage::Time Controller::get_time_into_track() {
// this is proportion of a second
Time result(cycles_since_index_hole_, 8000000 * clock_rate_multiplier_);
result /= rotational_multiplier_;
@ -139,8 +121,7 @@ Storage::Time Controller::get_time_into_track()
#pragma mark - Writing
void Controller::begin_writing()
{
void Controller::begin_writing() {
is_reading_ = false;
write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_;
@ -150,8 +131,7 @@ void Controller::begin_writing()
write_start_time_ = get_time_into_track();
}
void Controller::write_bit(bool value)
{
void Controller::write_bit(bool value) {
bool needs_new_byte = !(write_segment_.number_of_bits&7);
if(needs_new_byte) write_segment_.data.push_back(0);
if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7);
@ -160,16 +140,13 @@ void Controller::write_bit(bool value)
cycles_until_bits_written_ += cycles_per_bit_;
}
void Controller::end_writing()
{
void Controller::end_writing() {
is_reading_ = true;
if(!patched_track_)
{
if(!patched_track_) {
// Avoid creating a new patched track if this one is already patched
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
if(!patched_track_)
{
if(!patched_track_) {
patched_track_.reset(new PCMPatchedTrack(track_));
}
}
@ -179,8 +156,7 @@ void Controller::end_writing()
#pragma mark - PLL control and delegate
void Controller::set_expected_bit_length(Time bit_length)
{
void Controller::set_expected_bit_length(Time bit_length) {
bit_length_ = bit_length;
bit_length_.simplify();
@ -194,49 +170,41 @@ void Controller::set_expected_bit_length(Time bit_length)
pll_->set_delegate(this);
}
void Controller::digital_phase_locked_loop_output_bit(int value)
{
void Controller::digital_phase_locked_loop_output_bit(int value) {
process_input_bit(value, cycles_since_index_hole_);
}
#pragma mark - Drive actions
bool Controller::get_is_track_zero()
{
bool Controller::get_is_track_zero() {
if(!drive_) return false;
return drive_->get_is_track_zero();
}
bool Controller::get_drive_is_ready()
{
bool Controller::get_drive_is_ready() {
if(!drive_) return false;
return drive_->has_disk();
}
bool Controller::get_drive_is_read_only()
{
bool Controller::get_drive_is_read_only() {
if(!drive_) return false;
return drive_->get_is_read_only();
}
void Controller::step(int direction)
{
void Controller::step(int direction) {
invalidate_track();
if(drive_) drive_->step(direction);
}
void Controller::set_motor_on(bool motor_on)
{
void Controller::set_motor_on(bool motor_on) {
motor_is_on_ = motor_on;
}
bool Controller::get_motor_on()
{
bool Controller::get_motor_on() {
return motor_is_on_;
}
void Controller::set_drive(std::shared_ptr<Drive> drive)
{
void Controller::set_drive(std::shared_ptr<Drive> drive) {
if(drive_ != drive)
{
invalidate_track();
@ -244,16 +212,12 @@ void Controller::set_drive(std::shared_ptr<Drive> drive)
}
}
void Controller::invalidate_track()
{
void Controller::invalidate_track() {
track_ = nullptr;
if(patched_track_)
{
if(patched_track_) {
drive_->set_track(patched_track_);
patched_track_ = nullptr;
}
}
void Controller::process_write_completed()
{
}
void Controller::process_write_completed() {}

View File

@ -14,53 +14,44 @@ using namespace Storage::Disk;
Drive::Drive()
: head_position_(0), head_(0) {}
void Drive::set_disk(const std::shared_ptr<Disk> &disk)
{
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
disk_ = disk;
track_ = nullptr;
}
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track)
{
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track) {
disk_ = nullptr;
track_ = track;
}
bool Drive::has_disk()
{
bool Drive::has_disk() {
return (bool)disk_ || (bool)track_;
}
bool Drive::get_is_track_zero()
{
bool Drive::get_is_track_zero() {
return head_position_ == 0;
}
void Drive::step(int direction)
{
void Drive::step(int direction) {
head_position_ = std::max(head_position_ + direction, 0);
}
void Drive::set_head(unsigned int head)
{
void Drive::set_head(unsigned int head) {
head_ = head;
}
bool Drive::get_is_read_only()
{
bool Drive::get_is_read_only() {
if(disk_) return disk_->get_is_read_only();
if(track_) return true;
return false;
}
std::shared_ptr<Track> Drive::get_track()
{
std::shared_ptr<Track> Drive::get_track() {
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
if(track_) return track_;
return nullptr;
}
void Drive::set_track(const std::shared_ptr<Track> &track)
{
void Drive::set_track(const std::shared_ptr<Track> &track) {
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
}

View File

@ -11,8 +11,7 @@
using namespace Storage;
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone)
{
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone) {
Time duration;
// the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time)
duration.length = 16 - time_zone;
@ -20,10 +19,8 @@ Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int
return duration;
}
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibble)
{
switch(nibble & 0xf)
{
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibble) {
switch(nibble & 0xf) {
case 0x0: return 0x0a; case 0x1: return 0x0b;
case 0x2: return 0x12; case 0x3: return 0x13;
case 0x4: return 0x0e; case 0x5: return 0x0f;
@ -38,10 +35,8 @@ unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibbl
}
}
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_quintet(unsigned int quintet)
{
switch(quintet & 0x1f)
{
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_quintet(unsigned int quintet) {
switch(quintet & 0x1f) {
case 0x0a: return 0x0; case 0x0b: return 0x1;
case 0x12: return 0x2; case 0x13: return 0x3;
case 0x0e: return 0x4; case 0x0f: return 0x5;
@ -55,18 +50,15 @@ unsigned int Storage::Encodings::CommodoreGCR::decoding_from_quintet(unsigned in
}
}
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte)
{
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte) {
return encoding_for_nibble(byte) | (encoding_for_nibble(byte >> 4) << 5);
}
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_dectet(unsigned int dectet)
{
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_dectet(unsigned int dectet) {
return decoding_from_quintet(dectet) | (decoding_from_quintet(dectet >> 5) << 4);
}
void Storage::Encodings::CommodoreGCR::encode_block(uint8_t *destination, uint8_t *source)
{
void Storage::Encodings::CommodoreGCR::encode_block(uint8_t *destination, uint8_t *source) {
unsigned int encoded_bytes[4] = {
encoding_for_byte(source[0]),
encoding_for_byte(source[1]),

View File

@ -47,6 +47,7 @@ namespace CommodoreGCR {
*/
unsigned int decoding_from_dectet(unsigned int dectet);
}
}
}

View File

@ -89,58 +89,50 @@ class FMEncoder: public Encoder {
));
}
void add_index_address_mark()
{
void add_index_address_mark() {
crc_generator_.reset();
crc_generator_.add(IndexAddressByte);
output_short(FMIndexAddressMark);
}
void add_ID_address_mark()
{
void add_ID_address_mark() {
crc_generator_.reset();
crc_generator_.add(IDAddressByte);
output_short(FMIDAddressMark);
}
void add_data_address_mark()
{
void add_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DataAddressByte);
output_short(FMDataAddressMark);
}
void add_deleted_data_address_mark()
{
void add_deleted_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DeletedDataAddressByte);
output_short(FMDeletedDataAddressMark);
}
};
static uint8_t logarithmic_size_for_size(size_t size)
{
switch(size)
{
static uint8_t logarithmic_size_for_size(size_t size) {
switch(size) {
default: return 0;
case 256: return 1;
case 512: return 2;
case 1024: return 3;
case 2048: return 4; std::vector<uint8_t> get_track(uint8_t track);
case 2048: return 4;
case 4196: return 5;
}
}
template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors(
const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes, size_t post_address_mark_bytes,
size_t pre_data_mark_bytes, size_t post_data_bytes,
size_t inter_sector_gap,
size_t expected_track_bytes)
{
GetTrackWithSectors(
const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes, size_t post_address_mark_bytes,
size_t pre_data_mark_bytes, size_t post_data_bytes,
size_t inter_sector_gap,
size_t expected_track_bytes) {
Storage::Disk::PCMSegment segment;
segment.data.reserve(expected_track_bytes);
T shifter(segment.data);
@ -152,8 +144,7 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
for(int c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
// add sectors
for(const Sector &sector : sectors)
{
for(const Sector &sector : sectors) {
// gap
for(int c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
@ -191,24 +182,20 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
Encoder::Encoder(std::vector<uint8_t> &target) :
crc_generator_(0x1021, 0xffff),
target_(target)
{}
target_(target) {}
void Encoder::output_short(uint16_t value)
{
void Encoder::output_short(uint16_t value) {
target_.push_back(value >> 8);
target_.push_back(value & 0xff);
}
void Encoder::add_crc()
{
void Encoder::add_crc() {
uint16_t crc_value = crc_generator_.get_value();
add_byte(crc_value >> 8);
add_byte(crc_value & 0xff);
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors)
{
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors) {
return GetTrackWithSectors<FMEncoder>(
sectors,
16, 0x00,
@ -218,8 +205,7 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSec
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors)
{
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors) {
return GetTrackWithSectors<MFMEncoder>(
sectors,
50, 0x4e,
@ -229,23 +215,20 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSe
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
}
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<uint8_t> &target)
{
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<uint8_t> &target) {
return std::unique_ptr<Encoder>(new MFMEncoder(target));
}
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target)
{
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
return std::unique_ptr<Encoder>(new FMEncoder(target));
}
#pragma mark - Parser
Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm)
{
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm) {
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
@ -257,24 +240,20 @@ Parser::Parser(bool is_mfm) :
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser(is_mfm)
{
Parser(is_mfm) {
drive->set_disk(disk);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
Parser(is_mfm)
{
Parser(is_mfm) {
drive->set_disk_with_track(track);
}
void Parser::seek_to_track(uint8_t track)
{
void Parser::seek_to_track(uint8_t track) {
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
if(difference) {
int direction = difference < 0 ? -1 : 1;
difference *= direction;
@ -282,31 +261,26 @@ void Parser::seek_to_track(uint8_t track)
}
}
std::shared_ptr<Sector> Parser::get_sector(uint8_t track, uint8_t sector)
{
std::shared_ptr<Sector> Parser::get_sector(uint8_t track, uint8_t sector) {
seek_to_track(track);
return get_sector(sector);
}
std::vector<uint8_t> Parser::get_track(uint8_t track)
{
std::vector<uint8_t> Parser::get_track(uint8_t track) {
seek_to_track(track);
return get_track();
}
void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole) {
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
void Parser::process_index_hole()
{
void Parser::process_index_hole() {
index_count_++;
}
uint8_t Parser::get_byte_for_shift_value(uint16_t value)
{
uint8_t Parser::get_byte_for_shift_value(uint16_t value) {
return (uint8_t)(
((value&0x0001) >> 0) |
((value&0x0004) >> 1) |
@ -315,10 +289,10 @@ uint8_t Parser::get_byte_for_shift_value(uint16_t value)
((value&0x0100) >> 4) |
((value&0x0400) >> 5) |
((value&0x1000) >> 6) |
((value&0x4000) >> 7));}
((value&0x4000) >> 7));
}
uint8_t Parser::get_next_byte()
{
uint8_t Parser::get_next_byte() {
bit_count_ = 0;
while(bit_count_ < 16) run_for_cycles(1);
uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_);
@ -326,8 +300,7 @@ uint8_t Parser::get_next_byte()
return byte;
}
std::vector<uint8_t> Parser::get_track()
{
std::vector<uint8_t> Parser::get_track() {
std::vector<uint8_t> result;
int distance_until_permissible_sync = 0;
uint8_t last_id[6];
@ -340,25 +313,19 @@ std::vector<uint8_t> Parser::get_track()
// capture every other bit until the next index hole
index_count_ = 0;
while(1)
{
while(1) {
// wait until either another bit or the index hole arrives
bit_count_ = 0;
bool found_sync = false;
while(!index_count_ && !found_sync && bit_count_ < 16)
{
while(!index_count_ && !found_sync && bit_count_ < 16) {
int previous_bit_count = bit_count_;
run_for_cycles(1);
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count)
{
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) {
uint16_t low_shift_register = (shift_register_&0xffff);
if(is_mfm_)
{
if(is_mfm_) {
found_sync = (low_shift_register == MFMIndexSync) || (low_shift_register == MFMSync);
}
else
{
} else {
found_sync =
(low_shift_register == FMIndexAddressMark) ||
(low_shift_register == FMIDAddressMark) ||
@ -369,8 +336,7 @@ std::vector<uint8_t> Parser::get_track()
}
// if that was the index hole then finish
if(index_count_)
{
if(index_count_) {
if(bit_count_) result.push_back(get_byte_for_shift_value((uint16_t)(shift_register_ << (16 - bit_count_))));
break;
}
@ -382,32 +348,21 @@ std::vector<uint8_t> Parser::get_track()
// if no syncs are permissible here, decrement the waiting period and perform no further contemplation
bool found_id = false, found_data = false;
if(distance_until_permissible_sync)
{
if(distance_until_permissible_sync) {
distance_until_permissible_sync--;
}
else
{
if(found_sync)
{
if(is_mfm_)
{
} else {
if(found_sync) {
if(is_mfm_) {
next_is_type = true;
}
else
{
switch(shift_register_&0xffff)
{
} else {
switch(shift_register_&0xffff) {
case FMIDAddressMark: found_id = true; break;
case FMDataAddressMark:
case FMDeletedDataAddressMark: found_data = true; break;
}
}
}
else if(next_is_type)
{
switch(byte_value)
{
} else if(next_is_type) {
switch(byte_value) {
case IDAddressByte: found_id = true; break;
case DataAddressByte:
case DeletedDataAddressByte: found_data = true; break;
@ -415,14 +370,12 @@ std::vector<uint8_t> Parser::get_track()
}
}
if(found_id)
{
if(found_id) {
distance_until_permissible_sync = 6;
last_id_pointer = 0;
}
if(found_data)
{
if(found_data) {
distance_until_permissible_sync = 128 << last_id[3];
}
}
@ -436,30 +389,22 @@ std::shared_ptr<Sector> Parser::get_next_sector()
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
while(index_count_ < 2)
{
while(index_count_ < 2) {
// look for an ID address mark
bool id_found = false;
while(!id_found)
{
while(!id_found) {
run_for_cycles(1);
if(is_mfm_)
{
while(shift_register_ == MFMSync)
{
if(is_mfm_) {
while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte();
if(mark == IDAddressByte)
{
if(mark == IDAddressByte) {
crc_generator_.set_value(MFMPostSyncCRCValue);
id_found = true;
break;
}
}
}
else
{
if(shift_register_ == FMIDAddressMark)
{
} else {
if(shift_register_ == FMIDAddressMark) {
crc_generator_.reset();
id_found = true;
}
@ -478,27 +423,20 @@ std::shared_ptr<Sector> Parser::get_next_sector()
// look for data mark
bool data_found = false;
while(!data_found)
{
while(!data_found) {
run_for_cycles(1);
if(is_mfm_)
{
while(shift_register_ == MFMSync)
{
if(is_mfm_) {
while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte();
if(mark == DataAddressByte)
{
if(mark == DataAddressByte) {
crc_generator_.set_value(MFMPostSyncCRCValue);
data_found = true;
break;
}
if(mark == IDAddressByte) return nullptr;
}
}
else
{
if(shift_register_ == FMDataAddressMark)
{
} else {
if(shift_register_ == FMDataAddressMark) {
crc_generator_.reset();
data_found = true;
}
@ -510,8 +448,7 @@ std::shared_ptr<Sector> Parser::get_next_sector()
size_t data_size = (size_t)(128 << size);
sector->data.reserve(data_size);
for(size_t c = 0; c < data_size; c++)
{
for(size_t c = 0; c < data_size; c++) {
sector->data.push_back(get_next_byte());
}
uint16_t data_crc = crc_generator_.get_value();
@ -524,16 +461,14 @@ std::shared_ptr<Sector> Parser::get_next_sector()
return nullptr;
}
std::shared_ptr<Sector> Parser::get_sector(uint8_t sector)
{
std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
std::shared_ptr<Sector> first_sector;
index_count_ = 0;
while(!first_sector && index_count_ < 2) first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
if(!next_sector) continue;
if(next_sector->sector == first_sector->sector) return nullptr;

View File

@ -19,8 +19,7 @@ namespace {
using namespace Storage::Disk;
AcornADF::AcornADF(const char *file_name) :
Storage::FileHolder(file_name)
{
Storage::FileHolder(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats_.st_size % bytes_per_sector) throw ErrorNotAcornADF;
@ -37,33 +36,27 @@ AcornADF::AcornADF(const char *file_name) :
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
}
AcornADF::~AcornADF()
{
AcornADF::~AcornADF() {
flush_updates();
}
unsigned int AcornADF::get_head_position_count()
{
unsigned int AcornADF::get_head_position_count() {
return 80;
}
unsigned int AcornADF::get_head_count()
{
unsigned int AcornADF::get_head_count() {
return 1;
}
bool AcornADF::get_is_read_only()
{
bool AcornADF::get_is_read_only() {
return is_read_only_;
}
long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position)
{
long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position) {
return (position * 1 + head) * bytes_per_sector * sectors_per_track;
}
std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int head, unsigned int position) {
std::shared_ptr<Track> track;
if(head >= 2) return track;
@ -71,8 +64,7 @@ std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int hea
fseek(file_, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < sectors_per_track; sector++)
{
for(int sector = 0; sector < sectors_per_track; sector++) {
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = (uint8_t)head;
@ -91,19 +83,14 @@ std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int hea
return track;
}
void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex)
{
void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex) {
std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(true, track);
for(unsigned int c = 0; c < sectors_per_track; c++)
{
for(unsigned int c = 0; c < sectors_per_track; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
if(sector)
{
if(sector) {
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
}
else
{
} else {
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
parsed_track.resize(parsed_track.size() + bytes_per_sector);

View File

@ -16,8 +16,7 @@
using namespace Storage::Disk;
D64::D64(const char *file_name) :
Storage::FileHolder(file_name)
{
Storage::FileHolder(file_name) {
// in D64, this is it for validation without imposing potential false-negative tests — check that
// the file size appears to be correct. Stone-age stuff.
if(file_stats_.st_size != 174848 && file_stats_.st_size != 196608)
@ -28,21 +27,18 @@ D64::D64(const char *file_name) :
// then, ostensibly, this is a valid file. Hmmm. Pick a disk ID as a function of the file_name,
// being the most stable thing available
disk_id_ = 0;
while(*file_name)
{
while(*file_name) {
disk_id_ ^= file_name[0];
disk_id_ = (uint16_t)((disk_id_ << 2) ^ (disk_id_ >> 13));
file_name++;
}
}
unsigned int D64::get_head_position_count()
{
unsigned int D64::get_head_position_count() {
return number_of_tracks_*2;
}
std::shared_ptr<Track> D64::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> D64::get_uncached_track_at_position(unsigned int head, unsigned int position) {
// every other track is missing, as is any head above 0
if(position&1 || head)
return std::shared_ptr<Track>();
@ -54,8 +50,7 @@ std::shared_ptr<Track> D64::get_uncached_track_at_position(unsigned int head, un
int zone_sizes[] = {17, 7, 6, 10};
int sectors_by_zone[] = {21, 19, 18, 17};
int zone = 0;
for(int current_zone = 0; current_zone < 4; current_zone++)
{
for(int current_zone = 0; current_zone < 4; current_zone++) {
int tracks_in_this_zone = std::min(tracks_to_traverse, zone_sizes[current_zone]);
offset_to_track += tracks_in_this_zone * sectors_by_zone[current_zone];
tracks_to_traverse -= tracks_in_this_zone;
@ -95,8 +90,7 @@ std::shared_ptr<Track> D64::get_uncached_track_at_position(unsigned int head, un
memset(data, 0, track_bytes);
for(int sector = 0; sector < sectors_by_zone[zone]; sector++)
{
for(int sector = 0; sector < sectors_by_zone[zone]; sector++) {
uint8_t *sector_data = &data[sector * 349];
sector_data[0] = sector_data[1] = sector_data[2] = 0xff;
@ -139,8 +133,7 @@ std::shared_ptr<Track> D64::get_uncached_track_at_position(unsigned int head, un
Encodings::CommodoreGCR::encode_block(&sector_data[24], start_of_data);
int source_data_offset = 3;
int target_data_offset = 29;
while((source_data_offset+4) < 256)
{
while((source_data_offset+4) < 256) {
Encodings::CommodoreGCR::encode_block(&sector_data[target_data_offset], &source_data[source_data_offset]);
target_data_offset += 5;
source_data_offset += 4;

View File

@ -15,33 +15,26 @@
using namespace Storage::Disk;
G64::G64(const char *file_name) :
Storage::FileHolder(file_name)
{
Storage::FileHolder(file_name) {
// read and check the file signature
if(!check_signature("GCR-1541", 8))
throw ErrorNotG64;
if(!check_signature("GCR-1541", 8)) throw ErrorNotG64;
// check the version number
int version = fgetc(file_);
if(version != 0)
{
throw ErrorUnknownVersion;
}
if(version != 0) throw ErrorUnknownVersion;
// get the number of tracks and track size
number_of_tracks_ = (uint8_t)fgetc(file_);
maximum_track_size_ = fgetc16le();
}
unsigned int G64::get_head_position_count()
{
unsigned int G64::get_head_position_count() {
// give at least 84 tracks, to yield the normal geometry but,
// if there are more, shove them in
return number_of_tracks_ > 84 ? number_of_tracks_ : 84;
}
std::shared_ptr<Track> G64::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> G64::get_uncached_track_at_position(unsigned int head, unsigned int position) {
std::shared_ptr<Track> resulting_track;
// if there's definitely no track here, return the empty track
@ -78,8 +71,7 @@ std::shared_ptr<Track> G64::get_uncached_track_at_position(unsigned int head, un
speed_zone_offset = fgetc32le();
// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
if(speed_zone_offset > 3)
{
if(speed_zone_offset > 3) {
// seek to start of speed zone
fseek(file_, (int)speed_zone_offset, SEEK_SET);
@ -93,11 +85,9 @@ std::shared_ptr<Track> G64::get_uncached_track_at_position(unsigned int head, un
std::vector<PCMSegment> segments;
unsigned int current_speed = speed_zone_contents[0] >> 6;
unsigned int start_byte_in_current_speed = 0;
for(unsigned int byte = 0; byte < track_length; byte ++)
{
for(unsigned int byte = 0; byte < track_length; byte ++) {
unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
if(byte_speed != current_speed || byte == (track_length-1))
{
if(byte_speed != current_speed || byte == (track_length-1)) {
unsigned int number_of_bytes = byte - start_byte_in_current_speed;
PCMSegment segment;
@ -113,9 +103,7 @@ std::shared_ptr<Track> G64::get_uncached_track_at_position(unsigned int head, un
}
resulting_track.reset(new PCMTrack(std::move(segments)));
}
else
{
} else {
PCMSegment segment;
segment.number_of_bits = track_length * 8;
segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)speed_zone_offset);

View File

@ -13,8 +13,7 @@
using namespace Storage::Disk;
OricMFMDSK::OricMFMDSK(const char *file_name) :
Storage::FileHolder(file_name)
{
Storage::FileHolder(file_name) {
if(!check_signature("MFM_DISK", 8))
throw ErrorNotOricMFMDSK;
@ -26,31 +25,25 @@ OricMFMDSK::OricMFMDSK(const char *file_name) :
throw ErrorNotOricMFMDSK;
}
OricMFMDSK::~OricMFMDSK()
{
OricMFMDSK::~OricMFMDSK() {
flush_updates();
}
unsigned int OricMFMDSK::get_head_position_count()
{
unsigned int OricMFMDSK::get_head_position_count() {
return track_count_;
}
unsigned int OricMFMDSK::get_head_count()
{
unsigned int OricMFMDSK::get_head_count() {
return head_count_;
}
bool OricMFMDSK::get_is_read_only()
{
bool OricMFMDSK::get_is_read_only() {
return is_read_only_;
}
long OricMFMDSK::get_file_offset_for_position(unsigned int head, unsigned int position)
{
long OricMFMDSK::get_file_offset_for_position(unsigned int head, unsigned int position) {
long seek_offset = 0;
switch(geometry_type_)
{
switch(geometry_type_) {
case 1:
seek_offset = (head * track_count_) + position;
break;
@ -61,8 +54,7 @@ long OricMFMDSK::get_file_offset_for_position(unsigned int head, unsigned int po
return (seek_offset * 6400) + 256;
}
std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) {
fseek(file_, get_file_offset_for_position(head, position), SEEK_SET);
PCMSegment segment;
@ -73,25 +65,19 @@ std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int h
uint8_t last_header[6];
std::unique_ptr<Encodings::MFM::Encoder> encoder = Encodings::MFM::GetMFMEncoder(segment.data);
bool did_sync = false;
while(track_offset < 6250)
{
while(track_offset < 6250) {
uint8_t next_byte = (uint8_t)fgetc(file_);
track_offset++;
switch(next_byte)
{
default:
{
switch(next_byte) {
default: {
encoder->add_byte(next_byte);
if(did_sync)
{
switch(next_byte)
{
if(did_sync) {
switch(next_byte) {
default: break;
case 0xfe:
for(int byte = 0; byte < 6; byte++)
{
for(int byte = 0; byte < 6; byte++) {
last_header[byte] = (uint8_t)fgetc(file_);
encoder->add_byte(last_header[byte]);
track_offset++;
@ -100,8 +86,7 @@ std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int h
break;
case 0xfb:
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++)
{
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
encoder->add_byte((uint8_t)fgetc(file_));
track_offset++;
if(track_offset == 6250) break;
@ -131,8 +116,7 @@ std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int h
return track;
}
void OricMFMDSK::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex)
{
void OricMFMDSK::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex) {
Storage::Encodings::MFM::Parser parser(true, track);
std::vector<uint8_t> parsed_track = parser.get_track(0);
long file_offset = get_file_offset_for_position(head, position);

View File

@ -14,8 +14,7 @@
using namespace Storage::Disk;
SSD::SSD(const char *file_name) :
Storage::FileHolder(file_name)
{
Storage::FileHolder(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
@ -30,41 +29,34 @@ SSD::SSD(const char *file_name) :
else if(track_count_ < 80) track_count_ = 80;
}
SSD::~SSD()
{
SSD::~SSD() {
flush_updates();
}
unsigned int SSD::get_head_position_count()
{
unsigned int SSD::get_head_position_count() {
return track_count_;
}
unsigned int SSD::get_head_count()
{
unsigned int SSD::get_head_count() {
return head_count_;
}
bool SSD::get_is_read_only()
{
bool SSD::get_is_read_only() {
return is_read_only_;
}
long SSD::get_file_offset_for_position(unsigned int head, unsigned int position)
{
long SSD::get_file_offset_for_position(unsigned int head, unsigned int position) {
return (position * head_count_ + head) * 256 * 10;
}
std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, unsigned int position) {
std::shared_ptr<Track> track;
if(head >= head_count_) return track;
fseek(file_, get_file_offset_for_position(head, position), SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < 10; sector++)
{
for(int sector = 0; sector < 10; sector++) {
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = 0;
@ -86,19 +78,14 @@ std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, un
return track;
}
void SSD::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex)
{
void SSD::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex) {
std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(false, track);
for(unsigned int c = 0; c < 10; c++)
{
for(unsigned int c = 0; c < 10; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
if(sector)
{
if(sector) {
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
}
else
{
} else {
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
parsed_track.resize(parsed_track.size() + 256);

View File

@ -11,8 +11,7 @@
using namespace Storage::Disk;
PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr<Track> underlying_track) :
underlying_track_(underlying_track)
{
underlying_track_(underlying_track) {
const Time zero(0);
const Time one(1);
periods_.emplace_back(zero, one, zero, nullptr);
@ -20,20 +19,17 @@ PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr<Track> underlying_track) :
underlying_track_->seek_to(zero);
}
PCMPatchedTrack::PCMPatchedTrack(const PCMPatchedTrack &original)
{
PCMPatchedTrack::PCMPatchedTrack(const PCMPatchedTrack &original) {
underlying_track_.reset(original.underlying_track_->clone());
periods_ = original.periods_;
active_period_ = periods_.begin();
}
Track *PCMPatchedTrack::clone()
{
Track *PCMPatchedTrack::clone() {
return new PCMPatchedTrack(*this);
}
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment)
{
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment) {
std::shared_ptr<PCMSegmentEventSource> event_source(new PCMSegmentEventSource(segment));
Time zero(0);
@ -42,8 +38,7 @@ void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segm
// the new segment may wrap around, so divide it up into track-length parts if required
Time one = Time(1);
while(insertion_period.end_time > one)
{
while(insertion_period.end_time > one) {
Time next_end_time = insertion_period.end_time - one;
insertion_period.end_time = one;
insert_period(insertion_period);
@ -58,8 +53,7 @@ void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segm
insertion_error_ = current_time_ - seek_to(current_time_);
}
void PCMPatchedTrack::insert_period(const Period &period)
{
void PCMPatchedTrack::insert_period(const Period &period) {
// find the existing period that the new period starts in
std::vector<Period>::iterator start_period = periods_.begin();
while(start_period->end_time <= period.start_time) start_period++;
@ -69,34 +63,24 @@ void PCMPatchedTrack::insert_period(const Period &period)
while(end_period->end_time < period.end_time) end_period++;
// perform a division if called for
if(start_period == end_period)
{
if(start_period->start_time == period.start_time)
{
if(start_period->end_time == period.end_time)
{
if(start_period == end_period) {
if(start_period->start_time == period.start_time) {
if(start_period->end_time == period.end_time) {
// period has the same start and end time as start_period. So just replace it.
*start_period = period;
}
else
{
} else {
// period has the same start time as start_period but a different end time.
// So trim the left-hand side of start_period and insert the new period in front.
start_period->push_start_to_time(period.end_time);
periods_.insert(start_period, period);
}
}
else
{
if(start_period->end_time == period.end_time)
{
} else {
if(start_period->end_time == period.end_time) {
// period has the same end time as start_period but a different start time.
// So trim the right-hand side of start_period and insert the new period afterwards
start_period->trim_end_to_time(period.start_time);
periods_.insert(start_period + 1, period);
}
else
{
} else {
// start_period has an earlier start and a later end than period. So copy it,
// trim the right off the original and the left off the copy, then insert the
// new period and the copy after start_period
@ -111,34 +95,26 @@ void PCMPatchedTrack::insert_period(const Period &period)
periods_.insert(periods_.begin() + offset + 2, right_period);
}
}
}
else
{
} else {
bool should_insert = false;
std::vector<Period>::difference_type insertion_offset = 0;
if(start_period->start_time == period.start_time)
{
if(start_period->start_time == period.start_time) {
// start_period starts at the same place as period. Period then
// ends after start_period. So replace.
*start_period = period;
should_insert = false;
}
else
{
} else {
// start_period starts before period. So trim and plan to insert afterwards.
start_period->trim_end_to_time(period.start_time);
should_insert = true;
insertion_offset = start_period + 1 - periods_.begin();
}
if(end_period->end_time == period.end_time)
{
if(end_period->end_time == period.end_time) {
// end_period ends exactly when period does. So include it from the list to delete
end_period++;
}
else
{
} else {
end_period->push_start_to_time(period.end_time);
}
@ -158,8 +134,7 @@ Track::Event PCMPatchedTrack::get_next_event()
Time extra_time(0);
Time period_error(0);
while(1)
{
while(1) {
// get the next event from the current active period
Track::Event event;
if(active_period_->event_source) event = active_period_->event_source->get_next_event();
@ -167,8 +142,7 @@ Track::Event PCMPatchedTrack::get_next_event()
// see what time that gets us to. If it's still within the current period, return the found event
Time event_time = current_time_ + event.length - period_error - insertion_error_;
if(event_time < active_period_->end_time)
{
if(event_time < active_period_->end_time) {
current_time_ = event_time;
// TODO: this is spelt out in three steps because times don't necessarily do the sensible
// thing when 'negative' if intermediate result get simplified in the meantime. So fix Time.
@ -186,8 +160,7 @@ Track::Event PCMPatchedTrack::get_next_event()
active_period_++;
// test for having reached the end of the track
if(active_period_ == periods_.end())
{
if(active_period_ == periods_.end()) {
// if this is the end of the track then jump the active pointer back to the beginning
// of the list of periods and reset current_time_ to zero
active_period_ = periods_.begin();
@ -199,9 +172,7 @@ Track::Event PCMPatchedTrack::get_next_event()
event.type = Storage::Disk::Track::Event::IndexHole;
event.length = extra_time;
return event;
}
else
{
} else {
// if this is not the end of the track then move to the next period and note how much will need
// to be subtracted if an event is found here
if(active_period_->event_source) period_error = active_period_->segment_start_time - active_period_->event_source->seek_to(active_period_->segment_start_time);
@ -210,8 +181,7 @@ Track::Event PCMPatchedTrack::get_next_event()
}
}
Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole)
{
Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole) {
// start at the beginning and continue while segments end before reaching the time sought
active_period_ = periods_.begin();
while(active_period_->end_time < time_since_index_hole) active_period_++;
@ -225,18 +195,15 @@ Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole)
}
PCMPatchedTrack::Period::Period(const Period &original) :
start_time(original.start_time), end_time(original.end_time), segment_start_time(original.segment_start_time)
{
start_time(original.start_time), end_time(original.end_time), segment_start_time(original.segment_start_time) {
if(original.event_source) event_source.reset(new PCMSegmentEventSource(*original.event_source));
}
void PCMPatchedTrack::Period::push_start_to_time(const Storage::Time &new_start_time)
{
void PCMPatchedTrack::Period::push_start_to_time(const Storage::Time &new_start_time) {
segment_start_time += new_start_time - start_time;
start_time = new_start_time;
}
void PCMPatchedTrack::Period::trim_end_to_time(const Storage::Time &new_end_time)
{
void PCMPatchedTrack::Period::trim_end_to_time(const Storage::Time &new_end_time) {
end_time = new_end_time;
}

View File

@ -11,13 +11,11 @@
using namespace Storage::Disk;
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) :
segment_(new PCMSegment(segment))
{
segment_(new PCMSegment(segment)) {
// add an extra bit of storage at the bottom if one is going to be needed;
// events returned are going to be in integral multiples of the length of a bit
// other than the very first and very last which will include a half bit length
if(segment_->length_of_a_bit.length&1)
{
if(segment_->length_of_a_bit.length&1) {
segment_->length_of_a_bit.length <<= 1;
segment_->length_of_a_bit.clock_rate <<= 1;
}
@ -29,8 +27,7 @@ PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) :
reset();
}
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegmentEventSource &original)
{
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegmentEventSource &original) {
// share underlying data with the original
segment_ = original.segment_;
@ -39,16 +36,14 @@ PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegmentEventSource &origin
reset();
}
void PCMSegmentEventSource::reset()
{
void PCMSegmentEventSource::reset() {
// start with the first bit to be considered the zeroth, and assume that it'll be
// flux transitions for the foreseeable
bit_pointer_ = 0;
next_event_.type = Track::Event::FluxTransition;
}
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
{
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() {
// track the initial bit pointer for potentially considering whether this was an
// initial index hole or a subsequent one later on
size_t initial_bit_pointer = bit_pointer_;
@ -59,8 +54,7 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
// search for the next bit that is set, if any
const uint8_t *segment_data = segment_->data.data();
while(bit_pointer_ < segment_->number_of_bits)
{
while(bit_pointer_ < segment_->number_of_bits) {
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
bit_pointer_++; // so this always points one beyond the most recent bit returned
next_event_.length.length += segment_->length_of_a_bit.length;
@ -76,25 +70,21 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
// allow an extra half bit's length to run from the position of the potential final transition
// event to the end of the segment. Otherwise don't allow any extra time, as it's already
// been consumed
if(initial_bit_pointer <= segment_->number_of_bits)
{
if(initial_bit_pointer <= segment_->number_of_bits) {
next_event_.length.length += (segment_->length_of_a_bit.length >> 1);
bit_pointer_++;
}
return next_event_;
}
Storage::Time PCMSegmentEventSource::get_length()
{
Storage::Time PCMSegmentEventSource::get_length() {
return segment_->length_of_a_bit * segment_->number_of_bits;
}
Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
{
Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) {
// test for requested time being beyond the end
Time length = get_length();
if(time_from_start >= length)
{
if(time_from_start >= length) {
next_event_.type = Track::Event::IndexHole;
bit_pointer_ = segment_->number_of_bits+1;
return length;
@ -106,8 +96,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
// test for requested time being before the first bit
Time half_bit_length = segment_->length_of_a_bit;
half_bit_length.length >>= 1;
if(time_from_start < half_bit_length)
{
if(time_from_start < half_bit_length) {
bit_pointer_ = 0;
Storage::Time zero;
return zero;

View File

@ -11,23 +11,19 @@
using namespace Storage::Disk;
PCMTrack::PCMTrack() : segment_pointer_(0)
{}
PCMTrack::PCMTrack() : segment_pointer_(0) {}
PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack()
{
PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack() {
// sum total length of all segments
Time total_length;
for(auto segment : segments)
{
for(auto segment : segments) {
total_length += segment.length_of_a_bit * segment.number_of_bits;
}
total_length.simplify();
// each segment is then some proportion of the total; for them all to sum to 1 they'll
// need to be adjusted to be
for(auto segment : segments)
{
for(auto segment : segments) {
Time original_length_of_segment = segment.length_of_a_bit * segment.number_of_bits;
Time proportion_of_whole = original_length_of_segment / total_length;
proportion_of_whole.simplify();
@ -38,8 +34,7 @@ PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack()
}
}
PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack()
{
PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack() {
// a single segment necessarily fills the track
PCMSegment length_adjusted_segment = segment;
length_adjusted_segment.length_of_a_bit.length = 1;
@ -47,39 +42,33 @@ PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack()
segment_event_sources_.emplace_back(length_adjusted_segment);
}
PCMTrack::PCMTrack(const PCMTrack &original) : PCMTrack()
{
PCMTrack::PCMTrack(const PCMTrack &original) : PCMTrack() {
segment_event_sources_ = original.segment_event_sources_;
}
Track *PCMTrack::clone()
{
Track *PCMTrack::clone() {
return new PCMTrack(*this);
}
Track::Event PCMTrack::get_next_event()
{
Track::Event PCMTrack::get_next_event() {
// ask the current segment for a new event
Track::Event event = segment_event_sources_[segment_pointer_].get_next_event();
// if it was a flux transition, that's code for end-of-segment, so dig deeper
if(event.type == Track::Event::IndexHole)
{
if(event.type == Track::Event::IndexHole) {
// multiple segments may be crossed, so start summing lengths in case the net
// effect is an index hole
Time total_length = event.length;
// continue until somewhere no returning an index hole
while(event.type == Track::Event::IndexHole)
{
while(event.type == Track::Event::IndexHole) {
// advance to the [start of] the next segment
segment_pointer_ = (segment_pointer_ + 1) % segment_event_sources_.size();
segment_event_sources_[segment_pointer_].reset();
// if this is all the way back to the start, that's a genuine index hole,
// so set the summed length and return
if(!segment_pointer_)
{
if(!segment_pointer_) {
return event;
}
@ -94,21 +83,18 @@ Track::Event PCMTrack::get_next_event()
return event;
}
Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole)
{
Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole) {
// initial condition: no time yet accumulated, the whole thing requested yet to navigate
Storage::Time accumulated_time;
Storage::Time time_left_to_seek = time_since_index_hole;
// search from the first segment
segment_pointer_ = 0;
do
{
do {
// if this segment extends beyond the amount of time left to seek, trust it to complete
// the seek
Storage::Time segment_time = segment_event_sources_[segment_pointer_].get_length();
if(segment_time > time_left_to_seek)
{
if(segment_time > time_left_to_seek) {
return accumulated_time + segment_event_sources_[segment_pointer_].seek_to(time_left_to_seek);
}
@ -116,8 +102,7 @@ Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole)
time_left_to_seek -= segment_time;
accumulated_time += segment_time;
segment_pointer_ = (segment_pointer_ + 1) % segment_event_sources_.size();
}
while(segment_pointer_);
} while(segment_pointer_);
// if all segments have now been swallowed, the closest we can get is the very end of
// the list of segments

View File

@ -27,13 +27,11 @@ struct Time {
Time(int int_value) : Time((unsigned int)int_value) {}
Time(unsigned int length, unsigned int clock_rate) : length(length), clock_rate(clock_rate) { simplify(); }
Time(int length, int clock_rate) : Time((unsigned int)length, (unsigned int)clock_rate) {}
Time(uint64_t length, uint64_t clock_rate)
{
Time(uint64_t length, uint64_t clock_rate) {
install_result(length, clock_rate);
simplify();
}
Time(float value)
{
Time(float value) {
install_float(value);
simplify();
}
@ -42,8 +40,7 @@ struct Time {
Reduces this @c Time to its simplest form eliminates all common factors from @c length
and @c clock_rate.
*/
void simplify()
{
void simplify() {
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
length /= common_divisor;
clock_rate /= common_divisor;
@ -52,73 +49,58 @@ struct Time {
/*!
@returns the floating point conversion of this @c Time. This will often be less precise.
*/
inline float get_float() const
{
inline float get_float() const {
return (float)length / (float)clock_rate;
}
inline unsigned int get_unsigned_int() const
{
inline unsigned int get_unsigned_int() const {
return length / clock_rate;
}
inline bool operator < (const Time &other) const
{
inline bool operator < (const Time &other) const {
return (uint64_t)other.clock_rate * (uint64_t)length < (uint64_t)clock_rate * (uint64_t)other.length;
}
inline bool operator <= (const Time &other) const
{
inline bool operator <= (const Time &other) const {
return (uint64_t)other.clock_rate * (uint64_t)length <= (uint64_t)clock_rate * (uint64_t)other.length;
}
inline bool operator > (const Time &other) const
{
inline bool operator > (const Time &other) const {
return (uint64_t)other.clock_rate * (uint64_t)length > (uint64_t)clock_rate * (uint64_t)other.length;
}
inline bool operator >= (const Time &other) const
{
inline bool operator >= (const Time &other) const {
return (uint64_t)other.clock_rate * (uint64_t)length >= (uint64_t)clock_rate * (uint64_t)other.length;
}
inline bool operator == (const Time &other) const
{
inline bool operator == (const Time &other) const {
return (uint64_t)other.clock_rate * (uint64_t)length == (uint64_t)clock_rate * (uint64_t)other.length;
}
inline Time operator + (const Time &other) const
{
inline Time operator + (const Time &other) const {
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
if(clock_rate == other.clock_rate) {
result_length = (uint64_t)length + (uint64_t)other.length;
result_clock_rate = clock_rate;
}
else
{
} else {
result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
return Time(result_length, result_clock_rate);
}
inline Time &operator += (const Time &other)
{
inline Time &operator += (const Time &other) {
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
if(clock_rate == other.clock_rate) {
result_length = (uint64_t)length + (uint64_t)other.length;
result_clock_rate = (uint64_t)clock_rate;
}
else
{
} else {
result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
@ -126,38 +108,30 @@ struct Time {
return *this;
}
inline Time operator - (const Time &other) const
{
inline Time operator - (const Time &other) const {
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
if(clock_rate == other.clock_rate) {
result_length = (uint64_t)length - (uint64_t)other.length;
result_clock_rate = clock_rate;
}
else
{
} else {
result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
return Time(result_length, result_clock_rate);
}
inline Time operator -= (const Time &other)
{
inline Time operator -= (const Time &other) {
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
if(clock_rate == other.clock_rate) {
result_length = (uint64_t)length - (uint64_t)other.length;
result_clock_rate = (uint64_t)clock_rate;
}
else
{
} else {
result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
@ -165,151 +139,125 @@ struct Time {
return *this;
}
inline Time operator * (const Time &other) const
{
inline Time operator * (const Time &other) const {
uint64_t result_length = (uint64_t)length * (uint64_t)other.length;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
return Time(result_length, result_clock_rate);
}
inline Time &operator *= (const Time &other)
{
inline Time &operator *= (const Time &other) {
uint64_t result_length = (uint64_t)length * (uint64_t)other.length;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
install_result(result_length, result_clock_rate);
return *this;
}
inline Time operator * (unsigned int multiplier) const
{
inline Time operator * (unsigned int multiplier) const {
uint64_t result_length = (uint64_t)length * (uint64_t)multiplier;
uint64_t result_clock_rate = (uint64_t)clock_rate;
return Time(result_length, result_clock_rate);
}
inline Time &operator *= (unsigned int multiplier)
{
inline Time &operator *= (unsigned int multiplier) {
uint64_t result_length = (uint64_t)length * (uint64_t)multiplier;
uint64_t result_clock_rate = (uint64_t)clock_rate;
install_result(result_length, result_clock_rate);
return *this;
}
inline Time operator / (const Time &other) const
{
inline Time operator / (const Time &other) const {
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.length;
return Time(result_length, result_clock_rate);
}
inline Time &operator /= (const Time &other)
{
inline Time &operator /= (const Time &other) {
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.length;
install_result(result_length, result_clock_rate);
return *this;
}
inline Time operator / (unsigned int divisor) const
{
inline Time operator / (unsigned int divisor) const {
uint64_t result_length = (uint64_t)length;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)divisor;
return Time(result_length, result_clock_rate);
}
inline Time &operator /= (unsigned int divisor)
{
inline Time &operator /= (unsigned int divisor) {
uint64_t result_length = (uint64_t)length;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)divisor;
install_result(result_length, result_clock_rate);
return *this;
}
inline void set_zero()
{
inline void set_zero() {
length = 0;
clock_rate = 1;
}
inline void set_one()
{
inline void set_one() {
length = 1;
clock_rate = 1;
}
static Time max()
{
static Time max() {
return Time(std::numeric_limits<unsigned int>::max());
}
private:
inline void install_result(uint64_t long_length, uint64_t long_clock_rate)
{
inline void install_result(uint64_t long_length, uint64_t long_clock_rate) {
// TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy.
if(!long_length)
{
if(!long_length) {
length = 0;
clock_rate = 1;
return;
}
while(!(long_length&1) && !(long_clock_rate&1))
{
while(!(long_length&1) && !(long_clock_rate&1)) {
long_length >>= 1;
long_clock_rate >>= 1;
}
if(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max())
{
if(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max()) {
uint64_t common_divisor = NumberTheory::greatest_common_divisor(long_length, long_clock_rate);
long_length /= common_divisor;
long_clock_rate /= common_divisor;
// Okay, in desperation accept a loss of accuracy.
while(
(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max()) &&
(long_clock_rate > 1))
{
(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max()) &&
(long_clock_rate > 1)) {
long_length >>= 1;
long_clock_rate >>= 1;
}
}
if(long_length <= std::numeric_limits<unsigned int>::max() && long_clock_rate <= std::numeric_limits<unsigned int>::max())
{
if(long_length <= std::numeric_limits<unsigned int>::max() && long_clock_rate <= std::numeric_limits<unsigned int>::max()) {
length = (unsigned int)long_length;
clock_rate = (unsigned int)long_clock_rate;
}
else
{
} else {
length = std::numeric_limits<unsigned int>::max();
clock_rate = 1u;
}
}
inline void install_float(float value)
{
inline void install_float(float value) {
int exponent;
float mantissa = frexpf(value, &exponent);
float loaded_mantissa = ldexpf(mantissa, 24);
uint64_t result_length;
uint64_t result_clock_rate;
if(exponent < 0)
{
if(exponent < 0) {
int right_shift = -exponent;
result_length = (uint64_t)loaded_mantissa >> right_shift;
result_clock_rate = 1;
}
else
{
if(exponent <= 24)
{
} else {
if(exponent <= 24) {
result_length = (uint64_t)loaded_mantissa;
result_clock_rate = 1 << (24 - exponent);
}
else
{
} else {
result_length = std::numeric_limits<uint64_t>::max();
result_clock_rate = 1;
}
@ -318,7 +266,6 @@ struct Time {
}
};
}
#endif /* Storage_h */

View File

@ -15,34 +15,28 @@ using namespace Storage;
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
input_clock_rate_(input_clock_rate) {}
void TimedEventLoop::run_for_cycles(int number_of_cycles)
{
void TimedEventLoop::run_for_cycles(int number_of_cycles) {
cycles_until_event_ -= number_of_cycles;
while(cycles_until_event_ <= 0)
{
while(cycles_until_event_ <= 0) {
process_next_event();
}
}
unsigned int TimedEventLoop::get_cycles_until_next_event()
{
unsigned int TimedEventLoop::get_cycles_until_next_event() {
return (unsigned int)std::max(cycles_until_event_, 0);
}
void TimedEventLoop::reset_timer()
{
void TimedEventLoop::reset_timer() {
subcycles_until_event_.set_zero();
cycles_until_event_ = 0;
}
void TimedEventLoop::jump_to_next_event()
{
void TimedEventLoop::jump_to_next_event() {
reset_timer();
process_next_event();
}
void TimedEventLoop::set_next_event_time_interval(Time interval)
{
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 numerator =
@ -61,8 +55,7 @@ void TimedEventLoop::set_next_event_time_interval(Time interval)
subcycles_until_event_.clock_rate = (unsigned int)denominator;
}
Time TimedEventLoop::get_time_into_next_event()
{
Time TimedEventLoop::get_time_into_next_event() {
// TODO: calculate, presumably as [length of interval] - ([cycles left] + [subcycles left])
Time zero;
return zero;