From c6e046253e88df8e33180ccc04908cbe3b5737d7 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Fri, 5 Aug 2016 19:13:49 -0400 Subject: [PATCH 1/6] Shunted code for the main part up into the header, in advance of turning it into a template so as to bring it inside the normal orthodoxy. --- Components/6560/6560.cpp | 358 --------------------------------------- Components/6560/6560.hpp | 355 +++++++++++++++++++++++++++++++++++++- 2 files changed, 347 insertions(+), 366 deletions(-) diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp index 4c393cdcb..d958696e9 100644 --- a/Components/6560/6560.cpp +++ b/Components/6560/6560.cpp @@ -10,364 +10,6 @@ using namespace MOS; -MOS6560::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) -{ - _crt->set_composite_sampling_function( - "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" - "{" - "uint c = texture(texID, coordinate).r;" - "float y = float(c >> 4) / 4.0;" - "uint yC = c & 15u;" - "float phaseOffset = 6.283185308 * float(yC) / 16.0;" - - "float chroma = cos(phase + phaseOffset);" - "return mix(y, step(yC, 14) * chroma, amplitude);" - "}"); - - // default to NTSC - set_output_mode(OutputMode::NTSC); - - // show only the centre - _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); - _speaker->set_input_rate(255681.75); // assuming NTSC; clock rate / 4 -} - -void MOS6560::set_output_mode(OutputMode output_mode) -{ - uint8_t luminances[16] = { // range is 0–4 - 0, 4, 1, 3, 2, 2, 1, 3, - 2, 1, 2, 1, 2, 3, 2, 3 - }; - uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance" - 15, 15, 5, 13, 2, 10, 0, 8, - 6, 7, 5, 13, 2, 10, 0, 8, - }; - uint8_t ntsc_chrominances[16] = { - 15, 15, 2, 10, 4, 12, 6, 14, - 0, 8, 2, 10, 4, 12, 6, 14, - }; - uint8_t *chrominances; - Outputs::CRT::DisplayType display_type; - - switch(output_mode) - { - case OutputMode::PAL: - chrominances = pal_chrominances; - display_type = Outputs::CRT::PAL50; - _timing.cycles_per_line = 71; - _timing.line_counter_increment_offset = 0; - _timing.lines_per_progressive_field = 312; - _timing.supports_interlacing = false; - break; - - case OutputMode::NTSC: - chrominances = ntsc_chrominances; - display_type = Outputs::CRT::NTSC60; - _timing.cycles_per_line = 65; - _timing.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting - _timing.lines_per_progressive_field = 261; - _timing.supports_interlacing = true; - break; - } - - _crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type); - for(int c = 0; c < 16; c++) - { - _colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]); - } -} - -// TODO: set clock rate - -void MOS6560::set_register(int address, uint8_t value) -{ - address &= 0xf; - _registers.direct_values[address] = value; - switch(address) - { - case 0x0: - _registers.interlaced = !!(value&0x80) && _timing.supports_interlacing; - _registers.first_column_location = value & 0x7f; - break; - - case 0x1: - _registers.first_row_location = value; - break; - - case 0x2: - _registers.number_of_columns = value & 0x7f; - _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); - break; - - case 0x3: - _registers.number_of_rows = (value >> 1)&0x3f; - _registers.tall_characters = !!(value&0x01); - break; - - case 0x5: - _registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); - _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); - break; - - case 0xa: - case 0xb: - case 0xc: - case 0xd: - update_audio(); - _speaker->set_control(address - 0xa, value); - break; - - case 0xe: - update_audio(); - _registers.auxiliary_colour = _colours[value >> 4]; - _speaker->set_volume(value & 0xf); - break; - - case 0xf: - { - uint8_t new_border_colour = _colours[value & 0x07]; - if(_this_state == State::Border && new_border_colour != _registers.borderColour) - { - output_border(_cycles_in_state * 4); - _cycles_in_state = 0; - } - _registers.invertedCells = !((value >> 3)&1); - _registers.borderColour = new_border_colour; - _registers.backgroundColour = _colours[value >> 4]; - } - break; - - // TODO: the lightpen, etc - - default: - break; - } -} - -uint8_t MOS6560::get_register(int address) -{ - address &= 0xf; - int current_line = (_full_frame_counter + _timing.line_counter_increment_offset) / _timing.cycles_per_line; - switch(address) - { - default: return _registers.direct_values[address]; - case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f); - case 0x04: return (current_line >> 1) & 0xff; - } -} - -void MOS6560::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); -// _crt->output_blank(number_of_cycles); -} - -uint16_t MOS6560::get_address() -{ - // keep track of the amount of time since the speaker was updated; lazy updates are applied - _cycles_since_speaker_update++; - - // 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) - { - _current_character_row++; - if( - (_current_character_row == 16) || - (_current_character_row == 8 && !_registers.tall_characters) - ) { - _current_character_row = 0; - _current_row++; - } - - _pixel_line_cycle = -1; - _columns_this_line = -1; - _column_counter = -1; - } - - _horizontal_counter = 0; - _horizontal_drawing_latch = false; - - _vertical_counter ++; - if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field)) - { - _vertical_counter = 0; - _full_frame_counter = 0; - - _is_odd_frame ^= true; - _current_row = 0; - _rows_this_field = -1; - _vertical_drawing_latch = false; - _base_video_matrix_address_counter = 0; - _current_character_row = 0; - } - } - - // check for vertical starting events - _vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1); - _horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location); - - if(_pixel_line_cycle >= 0) _pixel_line_cycle++; - switch(_pixel_line_cycle) - { - case -1: - if(_horizontal_drawing_latch) - { - _pixel_line_cycle = 0; - _video_matrix_address_counter = _base_video_matrix_address_counter; - } - break; - case 1: _columns_this_line = _registers.number_of_columns; break; - case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break; - case 3: if(_current_row < _rows_this_field) _column_counter = 0; break; - } - - uint16_t fetch_address = 0x1c; - 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 - { - fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter); - _video_matrix_address_counter++; - if( - (_current_character_row == 15) || - (_current_character_row == 7 && !_registers.tall_characters) - ) { - _base_video_matrix_address_counter = _video_matrix_address_counter; - } - } - } - return fetch_address & 0x3fff; -} - -void MOS6560::set_graphics_value(uint8_t value, uint8_t colour_value) -{ - // TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should - // divide the byte it is set for 3:1 and then continue as usual. - - // determine output state; colour burst and sync timing are currently a guess - if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst; - else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync; - else - { - _this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border; - } - - // apply vertical sync - if( - (_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) || - (_registers.interlaced && - ( - (_vertical_counter == 0 && _horizontal_counter > 32) || - (_vertical_counter == 1) || (_vertical_counter == 2) || - (_vertical_counter == 3 && _horizontal_counter <= 32) - ) - )) - _this_state = State::Sync; - - // update the CRT - 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 ? 128 : 0, 0); break; - case State::Border: output_border(_cycles_in_state * 4); break; - case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; - } - _output_state = _this_state; - _cycles_in_state = 0; - - pixel_pointer = nullptr; - if(_output_state == State::Pixels) - { - pixel_pointer = _crt->allocate_write_area(260); - } - } - _cycles_in_state++; - - if(_this_state == State::Pixels) - { - if(_column_counter&1) - { - _character_value = value; - - if(pixel_pointer) - { - uint8_t cell_colour = _colours[_character_colour & 0x7]; - if(!(_character_colour&0x8)) - { - uint8_t colours[2]; - if(_registers.invertedCells) - { - colours[0] = cell_colour; - colours[1] = _registers.backgroundColour; - } - else - { - colours[0] = _registers.backgroundColour; - colours[1] = cell_colour; - } - pixel_pointer[0] = colours[(_character_value >> 7)&1]; - pixel_pointer[1] = colours[(_character_value >> 6)&1]; - pixel_pointer[2] = colours[(_character_value >> 5)&1]; - pixel_pointer[3] = colours[(_character_value >> 4)&1]; - pixel_pointer[4] = colours[(_character_value >> 3)&1]; - pixel_pointer[5] = colours[(_character_value >> 2)&1]; - pixel_pointer[6] = colours[(_character_value >> 1)&1]; - pixel_pointer[7] = colours[(_character_value >> 0)&1]; - } - 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]; - pixel_pointer[2] = - pixel_pointer[3] = colours[(_character_value >> 4)&3]; - pixel_pointer[4] = - pixel_pointer[5] = colours[(_character_value >> 2)&3]; - pixel_pointer[6] = - pixel_pointer[7] = colours[(_character_value >> 0)&3]; - } - pixel_pointer += 8; - } - } - else - { - _character_code = value; - _character_colour = colour_value; - } - - _column_counter++; - } -} - -void MOS6560::update_audio() -{ - _speaker->run_for_cycles(_cycles_since_speaker_update >> 2); - _cycles_since_speaker_update &= 3; -} - -#pragma mark - Audio - MOS6560::Speaker::Speaker() : _volume(0), _control_registers{0, 0, 0, 0}, diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 14a13c06a..375437b99 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -24,28 +24,285 @@ namespace MOS { */ class MOS6560 { public: - MOS6560(); + 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) + { + _crt->set_composite_sampling_function( + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" + "{" + "uint c = texture(texID, coordinate).r;" + "float y = float(c >> 4) / 4.0;" + "uint yC = c & 15u;" + "float phaseOffset = 6.283185308 * float(yC) / 16.0;" + + "float chroma = cos(phase + phaseOffset);" + "return mix(y, step(yC, 14) * chroma, amplitude);" + "}"); + + // default to NTSC + set_output_mode(OutputMode::NTSC); + + // show only the centre + _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); + _speaker->set_input_rate(255681.75); // assuming NTSC; clock rate / 4 + } + std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; } std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; } enum OutputMode { PAL, NTSC }; + /*! Sets the output mode to either PAL or NTSC. */ - void set_output_mode(OutputMode output_mode); + void set_output_mode(OutputMode output_mode) + { + uint8_t luminances[16] = { // range is 0–4 + 0, 4, 1, 3, 2, 2, 1, 3, + 2, 1, 2, 1, 2, 3, 2, 3 + }; + uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance" + 15, 15, 5, 13, 2, 10, 0, 8, + 6, 7, 5, 13, 2, 10, 0, 8, + }; + uint8_t ntsc_chrominances[16] = { + 15, 15, 2, 10, 4, 12, 6, 14, + 0, 8, 2, 10, 4, 12, 6, 14, + }; + uint8_t *chrominances; + Outputs::CRT::DisplayType display_type; + + switch(output_mode) + { + case OutputMode::PAL: + chrominances = pal_chrominances; + display_type = Outputs::CRT::PAL50; + _timing.cycles_per_line = 71; + _timing.line_counter_increment_offset = 0; + _timing.lines_per_progressive_field = 312; + _timing.supports_interlacing = false; + break; + + case OutputMode::NTSC: + chrominances = ntsc_chrominances; + display_type = Outputs::CRT::NTSC60; + _timing.cycles_per_line = 65; + _timing.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting + _timing.lines_per_progressive_field = 261; + _timing.supports_interlacing = true; + break; + } + + _crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type); + for(int c = 0; c < 16; c++) + { + _colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]); + } + } /*! Impliedly runs the 6560 for a single cycle, returning the next address that it puts on the bus. */ - uint16_t get_address(); + uint16_t get_address() + { + // keep track of the amount of time since the speaker was updated; lazy updates are applied + _cycles_since_speaker_update++; + + // 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) + { + _current_character_row++; + if( + (_current_character_row == 16) || + (_current_character_row == 8 && !_registers.tall_characters) + ) { + _current_character_row = 0; + _current_row++; + } + + _pixel_line_cycle = -1; + _columns_this_line = -1; + _column_counter = -1; + } + + _horizontal_counter = 0; + _horizontal_drawing_latch = false; + + _vertical_counter ++; + if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field)) + { + _vertical_counter = 0; + _full_frame_counter = 0; + + _is_odd_frame ^= true; + _current_row = 0; + _rows_this_field = -1; + _vertical_drawing_latch = false; + _base_video_matrix_address_counter = 0; + _current_character_row = 0; + } + } + + // check for vertical starting events + _vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1); + _horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location); + + if(_pixel_line_cycle >= 0) _pixel_line_cycle++; + switch(_pixel_line_cycle) + { + case -1: + if(_horizontal_drawing_latch) + { + _pixel_line_cycle = 0; + _video_matrix_address_counter = _base_video_matrix_address_counter; + } + break; + case 1: _columns_this_line = _registers.number_of_columns; break; + case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break; + case 3: if(_current_row < _rows_this_field) _column_counter = 0; break; + } + + uint16_t fetch_address = 0x1c; + 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 + { + fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter); + _video_matrix_address_counter++; + if( + (_current_character_row == 15) || + (_current_character_row == 7 && !_registers.tall_characters) + ) { + _base_video_matrix_address_counter = _video_matrix_address_counter; + } + } + } + return fetch_address & 0x3fff; + } /*! An owning machine should determine the state of the data bus as a result of the access implied by @c get_address and supply it to set_graphics_value. */ - void set_graphics_value(uint8_t value, uint8_t colour_value); + void set_graphics_value(uint8_t value, uint8_t colour_value) + { + // TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should + // divide the byte it is set for 3:1 and then continue as usual. + + // determine output state; colour burst and sync timing are currently a guess + if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst; + else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync; + else + { + _this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border; + } + + // apply vertical sync + if( + (_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) || + (_registers.interlaced && + ( + (_vertical_counter == 0 && _horizontal_counter > 32) || + (_vertical_counter == 1) || (_vertical_counter == 2) || + (_vertical_counter == 3 && _horizontal_counter <= 32) + ) + )) + _this_state = State::Sync; + + // update the CRT + 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 ? 128 : 0, 0); break; + case State::Border: output_border(_cycles_in_state * 4); break; + case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; + } + _output_state = _this_state; + _cycles_in_state = 0; + + pixel_pointer = nullptr; + if(_output_state == State::Pixels) + { + pixel_pointer = _crt->allocate_write_area(260); + } + } + _cycles_in_state++; + + if(_this_state == State::Pixels) + { + if(_column_counter&1) + { + _character_value = value; + + if(pixel_pointer) + { + uint8_t cell_colour = _colours[_character_colour & 0x7]; + if(!(_character_colour&0x8)) + { + uint8_t colours[2]; + if(_registers.invertedCells) + { + colours[0] = cell_colour; + colours[1] = _registers.backgroundColour; + } + else + { + colours[0] = _registers.backgroundColour; + colours[1] = cell_colour; + } + pixel_pointer[0] = colours[(_character_value >> 7)&1]; + pixel_pointer[1] = colours[(_character_value >> 6)&1]; + pixel_pointer[2] = colours[(_character_value >> 5)&1]; + pixel_pointer[3] = colours[(_character_value >> 4)&1]; + pixel_pointer[4] = colours[(_character_value >> 3)&1]; + pixel_pointer[5] = colours[(_character_value >> 2)&1]; + pixel_pointer[6] = colours[(_character_value >> 1)&1]; + pixel_pointer[7] = colours[(_character_value >> 0)&1]; + } + 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]; + pixel_pointer[2] = + pixel_pointer[3] = colours[(_character_value >> 4)&3]; + pixel_pointer[4] = + pixel_pointer[5] = colours[(_character_value >> 2)&3]; + pixel_pointer[6] = + pixel_pointer[7] = colours[(_character_value >> 0)&3]; + } + pixel_pointer += 8; + } + } + else + { + _character_code = value; + _character_colour = colour_value; + } + + _column_counter++; + } + } /*! Causes the 6560 to flush as much pending CRT and speaker communications as possible. @@ -55,12 +312,85 @@ 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) + { + case 0x0: + _registers.interlaced = !!(value&0x80) && _timing.supports_interlacing; + _registers.first_column_location = value & 0x7f; + break; + + case 0x1: + _registers.first_row_location = value; + break; + + case 0x2: + _registers.number_of_columns = value & 0x7f; + _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); + break; + + case 0x3: + _registers.number_of_rows = (value >> 1)&0x3f; + _registers.tall_characters = !!(value&0x01); + break; + + case 0x5: + _registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); + _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); + break; + + case 0xa: + case 0xb: + case 0xc: + case 0xd: + update_audio(); + _speaker->set_control(address - 0xa, value); + break; + + case 0xe: + update_audio(); + _registers.auxiliary_colour = _colours[value >> 4]; + _speaker->set_volume(value & 0xf); + break; + + case 0xf: + { + uint8_t new_border_colour = _colours[value & 0x07]; + if(_this_state == State::Border && new_border_colour != _registers.borderColour) + { + output_border(_cycles_in_state * 4); + _cycles_in_state = 0; + } + _registers.invertedCells = !((value >> 3)&1); + _registers.borderColour = new_border_colour; + _registers.backgroundColour = _colours[value >> 4]; + } + break; + + // TODO: the lightpen, etc + + default: + break; + } + } /* 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) + { + default: return _registers.direct_values[address]; + case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f); + case 0x04: return (current_line >> 1) & 0xff; + } + } private: std::shared_ptr<Outputs::CRT::CRT> _crt; @@ -84,7 +414,11 @@ 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; + } // register state struct { @@ -126,7 +460,12 @@ 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); + } struct { int cycles_per_line; From 3e65450a54d9137cf3e2bb61598b3e771dd60e37 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 6 Aug 2016 14:33:24 -0400 Subject: [PATCH 2/6] Converted the 6560 fully into a template; worked on allowing the typer to run at a much faster rate where hardware has some trigger by which it can request the next key. --- Components/6560/6560.cpp | 10 ++++---- Components/6560/6560.hpp | 37 +++++++++++++++-------------- Machines/Commodore/Vic-20/Vic20.cpp | 33 ++++++++++++++----------- Machines/Commodore/Vic-20/Vic20.hpp | 6 +++-- Machines/Typer.cpp | 8 +++++-- Machines/Typer.hpp | 3 +-- 6 files changed, 54 insertions(+), 43 deletions(-) diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp index d958696e9..1377ea9fc 100644 --- a/Components/6560/6560.cpp +++ b/Components/6560/6560.cpp @@ -10,19 +10,19 @@ using namespace MOS; -MOS6560::Speaker::Speaker() : +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 {} -void MOS6560::Speaker::set_volume(uint8_t volume) +void Speaker::set_volume(uint8_t volume) { _volume = volume; } -void MOS6560::Speaker::set_control(int channel, uint8_t value) +void Speaker::set_control(int channel, uint8_t value) { _control_registers[channel] = value; } @@ -99,7 +99,7 @@ static uint8_t noise_pattern[] = { #define increment(r) _shift_registers[r] = (_shift_registers[r]+1)%8191; #define update(r, m, up) _counters[r]++; if((_counters[r] >> m) == 0x7f) { up(r); _counters[r] = _control_registers[r]&0x7f; } -void MOS6560::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) +void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { for(unsigned int c = 0; c < number_of_samples; c++) { @@ -119,7 +119,7 @@ void MOS6560::Speaker::get_samples(unsigned int number_of_samples, int16_t *targ } } -void MOS6560::Speaker::skip_samples(unsigned int number_of_samples) +void Speaker::skip_samples(unsigned int number_of_samples) { for(unsigned int c = 0; c < number_of_samples; c++) { diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 375437b99..1f463f305 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -14,6 +14,24 @@ namespace MOS { +// audio state +class Speaker: public ::Outputs::Filter<Speaker> { + public: + Speaker(); + + void set_volume(uint8_t volume); + void set_control(int channel, uint8_t value); + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + private: + unsigned int _counters[4]; + unsigned int _shift_registers[4]; + uint8_t _control_registers[4]; + uint8_t _volume; +}; + /*! The 6560 Video Interface Chip ('VIC') is a video and audio output chip; it therefore vends both a @c CRT and a @c Speaker. @@ -22,7 +40,7 @@ namespace MOS { @c set_register and @c get_register provide register access. */ -class MOS6560 { +template <class T> class MOS6560 { public: MOS6560() : _crt(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)), @@ -395,23 +413,6 @@ class MOS6560 { private: std::shared_ptr<Outputs::CRT::CRT> _crt; - // audio state - class Speaker: public ::Outputs::Filter<Speaker> { - public: - Speaker(); - - void set_volume(uint8_t volume); - void set_control(int channel, uint8_t value); - - void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); - - private: - unsigned int _counters[4]; - unsigned int _shift_registers[4]; - uint8_t _control_registers[4]; - uint8_t _volume; - }; std::shared_ptr<Speaker> _speaker; unsigned int _cycles_since_speaker_update; void update_audio() diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index baf359b6c..e397735e3 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -128,7 +128,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _userPortVIA->run_for_half_cycles(2); _keyboardVIA->run_for_half_cycles(2); - if(_typer) _typer->update(1); + if(_typer && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) + { + if(!_typer->type_next_character()) + _typer.reset(); + } _tape.run_for_cycles(1); if(_c1540) _c1540->run_for_cycles(1); return 1; @@ -146,7 +150,7 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522) void Machine::setup_output(float aspect_ratio) { - _mos6560.reset(new MOS::MOS6560()); + _mos6560.reset(new Vic6560()); } void Machine::close_output() @@ -164,14 +168,9 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) case Characters: target = _characterROM; max_length = 0x1000; break; case BASIC: target = _basicROM; break; case Drive: - if(_c1540) - _c1540->set_rom(data); - else - { - // if there's no 1540 now, hold onto the ROM in case one is added later - _driveROM.reset(new uint8_t[length]); - memcpy(_driveROM.get(), data, length); - } + _driveROM.reset(new uint8_t[length]); + memcpy(_driveROM.get(), data, length); + install_disk_rom(); return; } @@ -204,7 +203,7 @@ void Machine::add_prg(size_t length, const uint8_t *data) void Machine::set_tape(std::shared_ptr<Storage::Tape> tape) { _tape.set_tape(tape); - set_typer_for_string("LOAD\n"); + set_typer_for_string("LOAD\nRUN\n"); } void Machine::tape_did_change_input(Tape *tape) @@ -226,13 +225,19 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk> disk) _c1540->set_disk(disk); // install the ROM if it was previously set - if(_driveROM) + install_disk_rom(); + + set_typer_for_string("LOAD\"*\",8,1\nRUN\n"); +} + +void Machine::install_disk_rom() +{ + if(_driveROM && _c1540) { _c1540->set_rom(_driveROM.get()); + _c1540->run_for_cycles(2000000); _driveROM.reset(); } - - set_typer_for_string("LOAD\"*\",8,1\n"); } #pragma mark - Typer diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index fed2b4f11..f8fec0674 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -225,6 +225,7 @@ class Tape: public Storage::TapePlayer { bool _input_level; }; +class Vic6560: public MOS::MOS6560<Vic6560> {}; class Machine: public CPU6502::Processor<Machine>, @@ -294,7 +295,7 @@ class Machine: uint8_t *_processorWriteMemoryMap[64]; void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length); - std::unique_ptr<MOS::MOS6560> _mos6560; + std::unique_ptr<Vic6560> _mos6560; std::shared_ptr<UserPortVIA> _userPortVIA; std::shared_ptr<KeyboardVIA> _keyboardVIA; std::shared_ptr<SerialPort> _serialPort; @@ -305,8 +306,9 @@ class Machine: Tape _tape; bool _use_fast_tape_hack; - // Disc + // Disk std::shared_ptr<::Commodore::C1540::Machine> _c1540; + void install_disk_rom(); }; } diff --git a/Machines/Typer.cpp b/Machines/Typer.cpp index e22c67f21..a17935280 100644 --- a/Machines/Typer.cpp +++ b/Machines/Typer.cpp @@ -32,8 +32,10 @@ void Typer::update(int duration) } } -void 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)) { _phase = 0; @@ -41,7 +43,7 @@ void Typer::type_next_character() { free(_string); _string = nullptr; - return; + return false; } _string_pointer++; @@ -50,6 +52,8 @@ void Typer::type_next_character() { _phase++; } + + return true; } Typer::~Typer() diff --git a/Machines/Typer.hpp b/Machines/Typer.hpp index a54f270e8..b85a689e4 100644 --- a/Machines/Typer.hpp +++ b/Machines/Typer.hpp @@ -23,6 +23,7 @@ class Typer { Typer(const char *string, int delay, int frequency, Delegate *delegate); ~Typer(); void update(int duration); + bool type_next_character(); private: char *_string; @@ -31,8 +32,6 @@ class Typer { int _phase; Delegate *_delegate; size_t _string_pointer; - - void type_next_character(); }; class TypeRecipient: public Typer::Delegate { From be54d8040e40c9161cc769b4497f43ac909eba17 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 6 Aug 2016 17:39:27 -0400 Subject: [PATCH 3/6] Made a first stab at having automatic loading be optional. But things are currently arranged such that the machine options are communicated too late to have an effect. So work to do. --- Machines/Commodore/Vic-20/Vic20.cpp | 6 ++-- Machines/Commodore/Vic-20/Vic20.hpp | 3 +- .../Clock Signal/Base.lproj/Vic20Document.xib | 30 ++++++++++++++----- .../Documents/MachineDocument.swift | 5 +++- .../Documents/Vic20Document.swift | 22 ++++++++++++++ .../Clock Signal/Machine/Wrappers/CSVic20.h | 1 + .../Clock Signal/Machine/Wrappers/CSVic20.mm | 7 +++++ 7 files changed, 61 insertions(+), 13 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index e397735e3..dc05075cb 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -187,7 +187,7 @@ void Machine::add_prg(size_t length, const uint8_t *data) { _rom_address = (uint16_t)(data[0] | (data[1] << 8)); _rom_length = (uint16_t)(length - 2); - if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000) + if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000 && _should_automatically_load_media) { set_typer_for_string("RUN\n"); } @@ -203,7 +203,7 @@ void Machine::add_prg(size_t length, const uint8_t *data) void Machine::set_tape(std::shared_ptr<Storage::Tape> tape) { _tape.set_tape(tape); - set_typer_for_string("LOAD\nRUN\n"); + if(_should_automatically_load_media) set_typer_for_string("LOAD\nRUN\n"); } void Machine::tape_did_change_input(Tape *tape) @@ -227,7 +227,7 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk> disk) // install the ROM if it was previously set install_disk_rom(); - set_typer_for_string("LOAD\"*\",8,1\nRUN\n"); + if(_should_automatically_load_media) set_typer_for_string("LOAD\"*\",8,1\nRUN\n"); } void Machine::install_disk_rom() diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index f8fec0674..bb7bea522 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -251,6 +251,7 @@ class Machine: } inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } + inline void set_should_automatically_load_media(bool activate) { _should_automatically_load_media = activate; } // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); @@ -304,7 +305,7 @@ class Machine: // Tape Tape _tape; - bool _use_fast_tape_hack; + bool _use_fast_tape_hack, _should_automatically_load_media; // Disk std::shared_ptr<::Commodore::C1540::Machine> _c1540; diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib index 570460d9d..9cd3153db 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <dependencies> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/> </dependencies> @@ -7,6 +7,7 @@ <customObject id="-2" userLabel="File's Owner" customClass="Vic20Document" customModule="Clock_Signal" customModuleProvider="target"> <connections> <outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="gWf-9E-D7l"/> + <outlet property="loadAutomaticallyButton" destination="lbt-Wo-6fc" id="Xsc-dz-1a6"/> <outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/> <outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/> <outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/> @@ -43,17 +44,17 @@ <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="NSPanel"> <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" HUD="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> - <rect key="contentRect" x="83" y="102" width="200" height="83"/> + <rect key="contentRect" x="83" y="102" width="200" height="103"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> <value key="minSize" type="size" width="200" height="83"/> <value key="maxSize" type="size" width="200" height="83"/> <view key="contentView" id="7Pv-WL-2Rq"> - <rect key="frame" x="0.0" y="0.0" width="200" height="83"/> + <rect key="frame" x="0.0" y="0.0" width="200" height="103"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s"> - <rect key="frame" x="18" y="47" width="164" height="18"/> - <buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm"> + <rect key="frame" x="18" y="67" width="164" height="18"/> + <buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm"> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <font key="font" metaFont="system"/> </buttonCell> @@ -61,6 +62,16 @@ <action selector="setFastLoading:" target="-2" id="ctR-h1-CYI"/> </connections> </button> + <button translatesAutoresizingMaskIntoConstraints="NO" id="lbt-Wo-6fc"> + <rect key="frame" x="18" y="47" width="164" height="18"/> + <buttonCell key="cell" type="check" title="Load Automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="jTj-uV-at1"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="setShouldLoadAutomatically:" target="-2" id="Ixe-HN-4XK"/> + </connections> + </button> <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV"> <rect key="frame" x="18" y="17" width="165" height="26"/> <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="UIu-uz-pTu"> @@ -72,7 +83,7 @@ <menuItem title="European Machine" id="5ju-Z0-BDa"/> <menuItem title="Japanese Machine" id="YlT-9e-azY"/> <menuItem title="Swedish Machine" id="joU-Bt-XFb"/> - <menuItem title="US Machine" state="on" id="FXe-ca-cTY"/> + <menuItem title="US Machine" id="FXe-ca-cTY"/> </items> </menu> </popUpButtonCell> @@ -80,14 +91,17 @@ </subviews> <constraints> <constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/> + <constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="lbt-Wo-6fc" secondAttribute="bottom" constant="8" id="DIc-Sm-VlA"/> <constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/> - <constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="8" id="fis-Fe-CkQ"/> + <constraint firstItem="lbt-Wo-6fc" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="cID-bi-rVP"/> + <constraint firstItem="lbt-Wo-6fc" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="ciY-E8-07P"/> + <constraint firstAttribute="trailing" secondItem="lbt-Wo-6fc" secondAttribute="trailing" constant="20" id="gMU-gX-3Sg"/> <constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/> <constraint firstItem="MlB-rE-TXV" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="qb4-Lp-ZMc"/> <constraint firstAttribute="trailing" secondItem="MlB-rE-TXV" secondAttribute="trailing" constant="20" id="v18-62-uee"/> </constraints> </view> - <point key="canvasLocation" x="-2" y="6.5"/> + <point key="canvasLocation" x="-2" y="16.5"/> </window> </objects> </document> diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 82c9f2499..3c9d8b6cd 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -176,9 +176,12 @@ class MachineDocument: } // MARK: IBActions + final func prefixedUserDefaultsKey(key: String) -> String { + return "\(self.name).\(key)" + } var fastLoadingUserDefaultsKey: String { get { - return "\(self.name).fastLoading" + return prefixedUserDefaultsKey("fastLoading") } } diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 3afb2fd20..71f00230d 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -66,4 +66,26 @@ class Vic20Document: MachineDocument { override func readFromData(data: NSData, ofType typeName: String) throws { vic20.setPRG(data) } + + @IBOutlet var loadAutomaticallyButton: NSButton! + var autoloadingUserDefaultsKey: String { + get { return prefixedUserDefaultsKey("autoload") } + } + @IBAction func setShouldLoadAutomatically(sender: NSButton!) { + let loadAutomatically = sender.state == NSOnState + vic20.shouldLoadAutomatically = loadAutomatically + self.loadAutomaticallyButton.state = loadAutomatically ? NSOnState : NSOffState + } + override func establishStoredOptions() { + super.establishStoredOptions() + + let standardUserDefaults = NSUserDefaults.standardUserDefaults() + standardUserDefaults.registerDefaults([ + autoloadingUserDefaultsKey: true + ]) + + let loadAutomatically = standardUserDefaults.boolForKey(self.autoloadingUserDefaultsKey) + vic20.shouldLoadAutomatically = loadAutomatically + self.loadAutomaticallyButton.state = loadAutomatically ? NSOnState : NSOffState + } } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index 90bd349ec..daaee4c06 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -23,5 +23,6 @@ - (BOOL)openD64AtURL:(nonnull NSURL *)URL; @property (nonatomic, assign) BOOL useFastLoadingHack; +@property (nonatomic, assign) BOOL shouldLoadAutomatically; @end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index f05fa6494..1ad7165f9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -197,4 +197,11 @@ using namespace Commodore::Vic20; } } +- (void)setShouldLoadAutomatically:(BOOL)shouldLoadAutomatically { + @synchronized(self) { + _shouldLoadAutomatically = shouldLoadAutomatically; + _vic20.set_should_automatically_load_media(shouldLoadAutomatically ? true : false); + } +} + @end From 285a288c802a2346a3a21af8b742d3bb582b11a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sun, 7 Aug 2016 21:48:09 -0400 Subject: [PATCH 4/6] Switched to two cycles of options loading, meaning that they get set before files are inserted. Might need some further work? --- Machines/Commodore/Vic-20/Vic20.hpp | 2 ++ .../Mac/Clock Signal/Documents/ElectronDocument.swift | 4 ++-- .../Mac/Clock Signal/Documents/MachineDocument.swift | 4 ++-- OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift | 8 +++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index bb7bea522..e51c5b8f0 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -310,6 +310,8 @@ class Machine: // Disk std::shared_ptr<::Commodore::C1540::Machine> _c1540; void install_disk_rom(); + + // Autoload string }; } diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 2790a44e4..e2981da95 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -66,7 +66,7 @@ class ElectronDocument: MachineDocument { } // MARK: IBActions - @IBOutlet var displayTypeButton: NSPopUpButton! + @IBOutlet var displayTypeButton: NSPopUpButton? @IBAction func setDisplayType(sender: NSPopUpButton!) { electron.useTelevisionOutput = (sender.indexOfSelectedItem == 1) NSUserDefaults.standardUserDefaults().setInteger(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) @@ -82,6 +82,6 @@ class ElectronDocument: MachineDocument { let displayType = standardUserDefaults.integerForKey(self.displayTypeUserDefaultsKey) electron.useTelevisionOutput = (displayType == 1) - self.displayTypeButton.selectItemAtIndex(displayType) + self.displayTypeButton?.selectItemAtIndex(displayType) } } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 3c9d8b6cd..64969d0e5 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -185,7 +185,7 @@ class MachineDocument: } } - @IBOutlet var fastLoadingButton: NSButton! + @IBOutlet var fastLoadingButton: NSButton? @IBAction func setFastLoading(sender: NSButton!) { if let fastLoadingMachine = machine as? CSFastLoading { let useFastLoadingHack = sender.state == NSOnState @@ -203,7 +203,7 @@ class MachineDocument: if let fastLoadingMachine = machine as? CSFastLoading { let useFastLoadingHack = standardUserDefaults.boolForKey(self.fastLoadingUserDefaultsKey) fastLoadingMachine.useFastLoadingHack = useFastLoadingHack - self.fastLoadingButton.state = useFastLoadingHack ? NSOnState : NSOffState + self.fastLoadingButton?.state = useFastLoadingHack ? NSOnState : NSOffState } } } diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 71f00230d..7ad6b8c32 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -35,6 +35,8 @@ class Vic20Document: MachineDocument { if let drive = dataForResource("1540", ofType: "bin", inDirectory: "ROMImages/Commodore1540") { vic20.setDriveROM(drive) } + + establishStoredOptions() } override class func autosavesInPlace() -> Bool { @@ -67,14 +69,14 @@ class Vic20Document: MachineDocument { vic20.setPRG(data) } - @IBOutlet var loadAutomaticallyButton: NSButton! + @IBOutlet var loadAutomaticallyButton: NSButton? var autoloadingUserDefaultsKey: String { get { return prefixedUserDefaultsKey("autoload") } } @IBAction func setShouldLoadAutomatically(sender: NSButton!) { let loadAutomatically = sender.state == NSOnState vic20.shouldLoadAutomatically = loadAutomatically - self.loadAutomaticallyButton.state = loadAutomatically ? NSOnState : NSOffState + NSUserDefaults.standardUserDefaults().setBool(loadAutomatically, forKey: self.autoloadingUserDefaultsKey) } override func establishStoredOptions() { super.establishStoredOptions() @@ -86,6 +88,6 @@ class Vic20Document: MachineDocument { let loadAutomatically = standardUserDefaults.boolForKey(self.autoloadingUserDefaultsKey) vic20.shouldLoadAutomatically = loadAutomatically - self.loadAutomaticallyButton.state = loadAutomatically ? NSOnState : NSOffState + self.loadAutomaticallyButton?.state = loadAutomatically ? NSOnState : NSOffState } } From 12bad8f23faee16e88c09edeba37f65521026168 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Tue, 9 Aug 2016 20:41:05 -0400 Subject: [PATCH 5/6] Turned the 6560 into an ordinary template, similar to the rest of the project, albeit right now with a fairly shonky internal implementation. Fixed a Mac-specific interface sizing issue. --- Components/6560/6560.hpp | 321 +++++++++--------- Machines/Commodore/Vic-20/Vic20.cpp | 15 +- Machines/Commodore/Vic-20/Vic20.hpp | 13 +- .../Clock Signal/Base.lproj/Vic20Document.xib | 4 +- 4 files changed, 188 insertions(+), 165 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 1f463f305..f90a4792e 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -125,6 +125,174 @@ template <class T> class MOS6560 { } } + /*! + Runs for cycles. Derr. + */ + inline void run_for_cycles(unsigned int number_of_cycles) + { + while(number_of_cycles--) + { + uint16_t address = get_address(); + uint8_t pixel_data; + uint8_t colour_data; + static_cast<T *>(this)->perform_read(address, &pixel_data, &colour_data); + set_graphics_value(pixel_data, colour_data); + } + } + + /*! + Causes the 6560 to flush as much pending CRT and speaker communications as possible. + */ + inline void synchronise() { update_audio(); } + + /*! + Writes to a 6560 register. + */ + void set_register(int address, uint8_t value) + { + address &= 0xf; + _registers.direct_values[address] = value; + switch(address) + { + case 0x0: + _registers.interlaced = !!(value&0x80) && _timing.supports_interlacing; + _registers.first_column_location = value & 0x7f; + break; + + case 0x1: + _registers.first_row_location = value; + break; + + case 0x2: + _registers.number_of_columns = value & 0x7f; + _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); + break; + + case 0x3: + _registers.number_of_rows = (value >> 1)&0x3f; + _registers.tall_characters = !!(value&0x01); + break; + + case 0x5: + _registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); + _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); + break; + + case 0xa: + case 0xb: + case 0xc: + case 0xd: + update_audio(); + _speaker->set_control(address - 0xa, value); + break; + + case 0xe: + update_audio(); + _registers.auxiliary_colour = _colours[value >> 4]; + _speaker->set_volume(value & 0xf); + break; + + case 0xf: + { + uint8_t new_border_colour = _colours[value & 0x07]; + if(_this_state == State::Border && new_border_colour != _registers.borderColour) + { + output_border(_cycles_in_state * 4); + _cycles_in_state = 0; + } + _registers.invertedCells = !((value >> 3)&1); + _registers.borderColour = new_border_colour; + _registers.backgroundColour = _colours[value >> 4]; + } + break; + + // TODO: the lightpen, etc + + default: + break; + } + } + + /* + Reads from a 6560 register. + */ + 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) + { + default: return _registers.direct_values[address]; + case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f); + case 0x04: return (current_line >> 1) & 0xff; + } + } + + private: + std::shared_ptr<Outputs::CRT::CRT> _crt; + + std::shared_ptr<Speaker> _speaker; + unsigned int _cycles_since_speaker_update; + void update_audio() + { + _speaker->run_for_cycles(_cycles_since_speaker_update >> 2); + _cycles_since_speaker_update &= 3; + } + + // register state + struct { + bool interlaced, tall_characters; + uint8_t first_column_location, first_row_location; + uint8_t number_of_columns, number_of_rows; + uint16_t character_cell_start_address, video_matrix_start_address; + uint8_t backgroundColour, borderColour, auxiliary_colour; + bool invertedCells; + + uint8_t direct_values[16]; + } _registers; + + // output state + enum State { + Sync, ColourBurst, Border, Pixels + } _this_state, _output_state; + unsigned int _cycles_in_state; + + // counters that cover an entire field + int _horizontal_counter, _vertical_counter, _full_frame_counter; + + // latches dictating start and length of drawing + bool _vertical_drawing_latch, _horizontal_drawing_latch; + int _rows_this_field, _columns_this_line; + + // current drawing position counter + int _pixel_line_cycle, _column_counter; + int _current_row; + uint16_t _current_character_row; + uint16_t _video_matrix_address_counter, _base_video_matrix_address_counter; + + // data latched from the bus + uint8_t _character_code, _character_colour, _character_value; + + bool _is_odd_frame; + + // lookup table from 6560 colour index to appropriate PAL/NTSC value + uint8_t _colours[16]; + + uint8_t *pixel_pointer; + void output_border(unsigned int number_of_cycles) + { + uint8_t *colour_pointer = _crt->allocate_write_area(1); + if(colour_pointer) *colour_pointer = _registers.borderColour; + _crt->output_level(number_of_cycles); + } + + struct { + int cycles_per_line; + int line_counter_increment_offset; + int lines_per_progressive_field; + bool supports_interlacing; + } _timing; + /*! Impliedly runs the 6560 for a single cycle, returning the next address that it puts on the bus. */ @@ -321,159 +489,6 @@ template <class T> class MOS6560 { _column_counter++; } } - - /*! - Causes the 6560 to flush as much pending CRT and speaker communications as possible. - */ - inline void synchronise() { update_audio(); } - - /*! - Writes to a 6560 register. - */ - void set_register(int address, uint8_t value) - { - address &= 0xf; - _registers.direct_values[address] = value; - switch(address) - { - case 0x0: - _registers.interlaced = !!(value&0x80) && _timing.supports_interlacing; - _registers.first_column_location = value & 0x7f; - break; - - case 0x1: - _registers.first_row_location = value; - break; - - case 0x2: - _registers.number_of_columns = value & 0x7f; - _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); - break; - - case 0x3: - _registers.number_of_rows = (value >> 1)&0x3f; - _registers.tall_characters = !!(value&0x01); - break; - - case 0x5: - _registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); - _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); - break; - - case 0xa: - case 0xb: - case 0xc: - case 0xd: - update_audio(); - _speaker->set_control(address - 0xa, value); - break; - - case 0xe: - update_audio(); - _registers.auxiliary_colour = _colours[value >> 4]; - _speaker->set_volume(value & 0xf); - break; - - case 0xf: - { - uint8_t new_border_colour = _colours[value & 0x07]; - if(_this_state == State::Border && new_border_colour != _registers.borderColour) - { - output_border(_cycles_in_state * 4); - _cycles_in_state = 0; - } - _registers.invertedCells = !((value >> 3)&1); - _registers.borderColour = new_border_colour; - _registers.backgroundColour = _colours[value >> 4]; - } - break; - - // TODO: the lightpen, etc - - default: - break; - } - } - - /* - Reads from a 6560 register. - */ - 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) - { - default: return _registers.direct_values[address]; - case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f); - case 0x04: return (current_line >> 1) & 0xff; - } - } - - private: - std::shared_ptr<Outputs::CRT::CRT> _crt; - - std::shared_ptr<Speaker> _speaker; - unsigned int _cycles_since_speaker_update; - void update_audio() - { - _speaker->run_for_cycles(_cycles_since_speaker_update >> 2); - _cycles_since_speaker_update &= 3; - } - - // register state - struct { - bool interlaced, tall_characters; - uint8_t first_column_location, first_row_location; - uint8_t number_of_columns, number_of_rows; - uint16_t character_cell_start_address, video_matrix_start_address; - uint8_t backgroundColour, borderColour, auxiliary_colour; - bool invertedCells; - - uint8_t direct_values[16]; - } _registers; - - // output state - enum State { - Sync, ColourBurst, Border, Pixels - } _this_state, _output_state; - unsigned int _cycles_in_state; - - // counters that cover an entire field - int _horizontal_counter, _vertical_counter, _full_frame_counter; - - // latches dictating start and length of drawing - bool _vertical_drawing_latch, _horizontal_drawing_latch; - int _rows_this_field, _columns_this_line; - - // current drawing position counter - int _pixel_line_cycle, _column_counter; - int _current_row; - uint16_t _current_character_row; - uint16_t _video_matrix_address_counter, _base_video_matrix_address_counter; - - // data latched from the bus - uint8_t _character_code, _character_colour, _character_value; - - bool _is_odd_frame; - - // lookup table from 6560 colour index to appropriate PAL/NTSC value - uint8_t _colours[16]; - - uint8_t *pixel_pointer; - void output_border(unsigned int number_of_cycles) - { - uint8_t *colour_pointer = _crt->allocate_write_area(1); - if(colour_pointer) *colour_pointer = _registers.borderColour; - _crt->output_level(number_of_cycles); - } - - struct { - int cycles_per_line; - int line_counter_increment_offset; - int lines_per_progressive_field; - bool supports_interlacing; - } _timing; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index dc05075cb..097d63cfe 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -35,14 +35,9 @@ Machine::Machine() : _tape.set_delegate(this); // establish the memory maps - memset(_videoMemoryMap, 0, sizeof(_videoMemoryMap)); memset(_processorReadMemoryMap, 0, sizeof(_processorReadMemoryMap)); memset(_processorWriteMemoryMap, 0, sizeof(_processorWriteMemoryMap)); - write_to_map(_videoMemoryMap, _characterROM, 0x0000, sizeof(_characterROM)); - write_to_map(_videoMemoryMap, _userBASICMemory, 0x2000, sizeof(_userBASICMemory)); - write_to_map(_videoMemoryMap, _screenMemory, 0x3000, sizeof(_screenMemory)); - write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory)); write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory)); write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory)); @@ -86,9 +81,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // } // run the phase-1 part of this cycle, in which the VIC accesses memory - uint16_t video_address = _mos6560->get_address(); - uint8_t video_value = _videoMemoryMap[video_address >> 10] ? _videoMemoryMap[video_address >> 10][video_address & 0x3ff] : 0xff; // TODO - _mos6560->set_graphics_value(video_value, _colorMemory[video_address & 0x03ff]); + _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)) @@ -151,6 +144,12 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522) void Machine::setup_output(float aspect_ratio) { _mos6560.reset(new Vic6560()); + + memset(_mos6560->_videoMemoryMap, 0, sizeof(_mos6560->_videoMemoryMap)); + write_to_map(_mos6560->_videoMemoryMap, _characterROM, 0x0000, sizeof(_characterROM)); + write_to_map(_mos6560->_videoMemoryMap, _userBASICMemory, 0x2000, sizeof(_userBASICMemory)); + write_to_map(_mos6560->_videoMemoryMap, _screenMemory, 0x3000, sizeof(_screenMemory)); + _mos6560->_colorMemory = _colorMemory; } void Machine::close_output() diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index e51c5b8f0..292f655eb 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -225,7 +225,17 @@ class Tape: public Storage::TapePlayer { bool _input_level; }; -class Vic6560: public MOS::MOS6560<Vic6560> {}; +class Vic6560: public MOS::MOS6560<Vic6560> { + public: + void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) + { + *pixel_data = _videoMemoryMap[address >> 10] ? _videoMemoryMap[address >> 10][address & 0x3ff] : 0xff; // TODO + *colour_data = _colorMemory[address & 0x03ff]; + } + + uint8_t *_videoMemoryMap[16]; + uint8_t *_colorMemory; +}; class Machine: public CPU6502::Processor<Machine>, @@ -291,7 +301,6 @@ class Machine: uint8_t _junkMemory[0x0400]; std::unique_ptr<uint8_t> _driveROM; - uint8_t *_videoMemoryMap[16]; uint8_t *_processorReadMemoryMap[64]; uint8_t *_processorWriteMemoryMap[64]; void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length); diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib index 9cd3153db..cd45e6cb1 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib @@ -46,8 +46,8 @@ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="83" y="102" width="200" height="103"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> - <value key="minSize" type="size" width="200" height="83"/> - <value key="maxSize" type="size" width="200" height="83"/> + <value key="minSize" type="size" width="200" height="103"/> + <value key="maxSize" type="size" width="200" height="103"/> <view key="contentView" id="7Pv-WL-2Rq"> <rect key="frame" x="0.0" y="0.0" width="200" height="103"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> From 142774be37a7f61281408e23de64c67781a4400e Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Tue, 9 Aug 2016 21:10:53 -0400 Subject: [PATCH 6/6] Collapsed 6560 template to a more direct loop, albeit with quite a bit still left to fix. --- Components/6560/6560.hpp | 386 ++++++++++++++-------------- Machines/Commodore/Vic-20/Vic20.hpp | 2 +- 2 files changed, 187 insertions(+), 201 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index f90a4792e..c09518130 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -130,13 +130,196 @@ template <class T> class MOS6560 { */ inline void run_for_cycles(unsigned int number_of_cycles) { + // keep track of the amount of time since the speaker was updated; lazy updates are applied + _cycles_since_speaker_update += number_of_cycles; + while(number_of_cycles--) { - uint16_t address = get_address(); + // 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) + { + _current_character_row++; + if( + (_current_character_row == 16) || + (_current_character_row == 8 && !_registers.tall_characters) + ) { + _current_character_row = 0; + _current_row++; + } + + _pixel_line_cycle = -1; + _columns_this_line = -1; + _column_counter = -1; + } + + _horizontal_counter = 0; + _horizontal_drawing_latch = false; + + _vertical_counter ++; + if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field)) + { + _vertical_counter = 0; + _full_frame_counter = 0; + + _is_odd_frame ^= true; + _current_row = 0; + _rows_this_field = -1; + _vertical_drawing_latch = false; + _base_video_matrix_address_counter = 0; + _current_character_row = 0; + } + } + + // check for vertical starting events + _vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1); + _horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location); + + if(_pixel_line_cycle >= 0) _pixel_line_cycle++; + switch(_pixel_line_cycle) + { + case -1: + if(_horizontal_drawing_latch) + { + _pixel_line_cycle = 0; + _video_matrix_address_counter = _base_video_matrix_address_counter; + } + break; + case 1: _columns_this_line = _registers.number_of_columns; break; + case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break; + case 3: if(_current_row < _rows_this_field) _column_counter = 0; break; + } + + uint16_t fetch_address = 0x1c; + 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 + { + fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter); + _video_matrix_address_counter++; + if( + (_current_character_row == 15) || + (_current_character_row == 7 && !_registers.tall_characters) + ) { + _base_video_matrix_address_counter = _video_matrix_address_counter; + } + } + } + + fetch_address &= 0x3fff; + uint8_t pixel_data; uint8_t colour_data; - static_cast<T *>(this)->perform_read(address, &pixel_data, &colour_data); - set_graphics_value(pixel_data, colour_data); + static_cast<T *>(this)->perform_read(fetch_address, &pixel_data, &colour_data); + + // TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should + // divide the byte it is set for 3:1 and then continue as usual. + + // determine output state; colour burst and sync timing are currently a guess + if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst; + else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync; + else + { + _this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border; + } + + // apply vertical sync + if( + (_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) || + (_registers.interlaced && + ( + (_vertical_counter == 0 && _horizontal_counter > 32) || + (_vertical_counter == 1) || (_vertical_counter == 2) || + (_vertical_counter == 3 && _horizontal_counter <= 32) + ) + )) + _this_state = State::Sync; + + // update the CRT + 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 ? 128 : 0, 0); break; + case State::Border: output_border(_cycles_in_state * 4); break; + case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; + } + _output_state = _this_state; + _cycles_in_state = 0; + + pixel_pointer = nullptr; + if(_output_state == State::Pixels) + { + pixel_pointer = _crt->allocate_write_area(260); + } + } + _cycles_in_state++; + + if(_this_state == State::Pixels) + { + if(_column_counter&1) + { + _character_value = pixel_data; + + if(pixel_pointer) + { + uint8_t cell_colour = _colours[_character_colour & 0x7]; + if(!(_character_colour&0x8)) + { + uint8_t colours[2]; + if(_registers.invertedCells) + { + colours[0] = cell_colour; + colours[1] = _registers.backgroundColour; + } + else + { + colours[0] = _registers.backgroundColour; + colours[1] = cell_colour; + } + pixel_pointer[0] = colours[(_character_value >> 7)&1]; + pixel_pointer[1] = colours[(_character_value >> 6)&1]; + pixel_pointer[2] = colours[(_character_value >> 5)&1]; + pixel_pointer[3] = colours[(_character_value >> 4)&1]; + pixel_pointer[4] = colours[(_character_value >> 3)&1]; + pixel_pointer[5] = colours[(_character_value >> 2)&1]; + pixel_pointer[6] = colours[(_character_value >> 1)&1]; + pixel_pointer[7] = colours[(_character_value >> 0)&1]; + } + 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]; + pixel_pointer[2] = + pixel_pointer[3] = colours[(_character_value >> 4)&3]; + pixel_pointer[4] = + pixel_pointer[5] = colours[(_character_value >> 2)&3]; + pixel_pointer[6] = + pixel_pointer[7] = colours[(_character_value >> 0)&3]; + } + pixel_pointer += 8; + } + } + else + { + _character_code = pixel_data; + _character_colour = colour_data; + } + + _column_counter++; + } } } @@ -292,203 +475,6 @@ template <class T> class MOS6560 { int lines_per_progressive_field; bool supports_interlacing; } _timing; - - /*! - Impliedly runs the 6560 for a single cycle, returning the next address that it puts on the bus. - */ - uint16_t get_address() - { - // keep track of the amount of time since the speaker was updated; lazy updates are applied - _cycles_since_speaker_update++; - - // 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) - { - _current_character_row++; - if( - (_current_character_row == 16) || - (_current_character_row == 8 && !_registers.tall_characters) - ) { - _current_character_row = 0; - _current_row++; - } - - _pixel_line_cycle = -1; - _columns_this_line = -1; - _column_counter = -1; - } - - _horizontal_counter = 0; - _horizontal_drawing_latch = false; - - _vertical_counter ++; - if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field)) - { - _vertical_counter = 0; - _full_frame_counter = 0; - - _is_odd_frame ^= true; - _current_row = 0; - _rows_this_field = -1; - _vertical_drawing_latch = false; - _base_video_matrix_address_counter = 0; - _current_character_row = 0; - } - } - - // check for vertical starting events - _vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1); - _horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location); - - if(_pixel_line_cycle >= 0) _pixel_line_cycle++; - switch(_pixel_line_cycle) - { - case -1: - if(_horizontal_drawing_latch) - { - _pixel_line_cycle = 0; - _video_matrix_address_counter = _base_video_matrix_address_counter; - } - break; - case 1: _columns_this_line = _registers.number_of_columns; break; - case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break; - case 3: if(_current_row < _rows_this_field) _column_counter = 0; break; - } - - uint16_t fetch_address = 0x1c; - 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 - { - fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter); - _video_matrix_address_counter++; - if( - (_current_character_row == 15) || - (_current_character_row == 7 && !_registers.tall_characters) - ) { - _base_video_matrix_address_counter = _video_matrix_address_counter; - } - } - } - return fetch_address & 0x3fff; - } - - /*! - An owning machine should determine the state of the data bus as a result of the access implied - by @c get_address and supply it to set_graphics_value. - */ - void set_graphics_value(uint8_t value, uint8_t colour_value) - { - // TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should - // divide the byte it is set for 3:1 and then continue as usual. - - // determine output state; colour burst and sync timing are currently a guess - if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst; - else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync; - else - { - _this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border; - } - - // apply vertical sync - if( - (_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) || - (_registers.interlaced && - ( - (_vertical_counter == 0 && _horizontal_counter > 32) || - (_vertical_counter == 1) || (_vertical_counter == 2) || - (_vertical_counter == 3 && _horizontal_counter <= 32) - ) - )) - _this_state = State::Sync; - - // update the CRT - 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 ? 128 : 0, 0); break; - case State::Border: output_border(_cycles_in_state * 4); break; - case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; - } - _output_state = _this_state; - _cycles_in_state = 0; - - pixel_pointer = nullptr; - if(_output_state == State::Pixels) - { - pixel_pointer = _crt->allocate_write_area(260); - } - } - _cycles_in_state++; - - if(_this_state == State::Pixels) - { - if(_column_counter&1) - { - _character_value = value; - - if(pixel_pointer) - { - uint8_t cell_colour = _colours[_character_colour & 0x7]; - if(!(_character_colour&0x8)) - { - uint8_t colours[2]; - if(_registers.invertedCells) - { - colours[0] = cell_colour; - colours[1] = _registers.backgroundColour; - } - else - { - colours[0] = _registers.backgroundColour; - colours[1] = cell_colour; - } - pixel_pointer[0] = colours[(_character_value >> 7)&1]; - pixel_pointer[1] = colours[(_character_value >> 6)&1]; - pixel_pointer[2] = colours[(_character_value >> 5)&1]; - pixel_pointer[3] = colours[(_character_value >> 4)&1]; - pixel_pointer[4] = colours[(_character_value >> 3)&1]; - pixel_pointer[5] = colours[(_character_value >> 2)&1]; - pixel_pointer[6] = colours[(_character_value >> 1)&1]; - pixel_pointer[7] = colours[(_character_value >> 0)&1]; - } - 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]; - pixel_pointer[2] = - pixel_pointer[3] = colours[(_character_value >> 4)&3]; - pixel_pointer[4] = - pixel_pointer[5] = colours[(_character_value >> 2)&3]; - pixel_pointer[6] = - pixel_pointer[7] = colours[(_character_value >> 0)&3]; - } - pixel_pointer += 8; - } - } - else - { - _character_code = value; - _character_colour = colour_value; - } - - _column_counter++; - } - } }; } diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index 292f655eb..29a1fe3e5 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -227,7 +227,7 @@ class Tape: public Storage::TapePlayer { class Vic6560: public MOS::MOS6560<Vic6560> { public: - 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 = _videoMemoryMap[address >> 10] ? _videoMemoryMap[address >> 10][address & 0x3ff] : 0xff; // TODO *colour_data = _colorMemory[address & 0x03ff];