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];