2016-06-05 14:51:07 +00:00
|
|
|
|
//
|
|
|
|
|
// 6560.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 05/06/2016.
|
|
|
|
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#ifndef _560_hpp
|
|
|
|
|
#define _560_hpp
|
|
|
|
|
|
|
|
|
|
#include "../../Outputs/CRT/CRT.hpp"
|
2016-06-13 23:30:52 +00:00
|
|
|
|
#include "../../Outputs/Speaker.hpp"
|
2017-07-26 00:20:55 +00:00
|
|
|
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
2016-06-05 14:51:07 +00:00
|
|
|
|
|
|
|
|
|
namespace MOS {
|
|
|
|
|
|
2016-08-06 18:33:24 +00:00
|
|
|
|
// audio state
|
|
|
|
|
class Speaker: public ::Outputs::Filter<Speaker> {
|
|
|
|
|
public:
|
|
|
|
|
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:
|
2017-11-11 03:05:35 +00:00
|
|
|
|
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
|
|
|
|
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
|
|
|
|
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
|
|
|
|
uint8_t volume_ = 0;
|
2016-08-06 18:33:24 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-06-15 00:00:16 +00:00
|
|
|
|
/*!
|
2016-06-19 22:11:37 +00:00
|
|
|
|
The 6560 Video Interface Chip ('VIC') is a video and audio output chip; it therefore vends both a @c CRT and a @c Speaker.
|
2016-06-13 23:30:52 +00:00
|
|
|
|
|
2016-06-19 22:11:37 +00:00
|
|
|
|
To run the VIC for a cycle, the caller should call @c get_address, make the requested bus access
|
2016-06-15 00:00:16 +00:00
|
|
|
|
and call @c set_graphics_value with the result.
|
2016-06-13 23:30:52 +00:00
|
|
|
|
|
2016-06-15 00:00:16 +00:00
|
|
|
|
@c set_register and @c get_register provide register access.
|
|
|
|
|
*/
|
2017-07-27 11:40:02 +00:00
|
|
|
|
template <class T> class MOS6560 {
|
2016-06-05 14:51:07 +00:00
|
|
|
|
public:
|
2016-08-05 23:13:49 +00:00
|
|
|
|
MOS6560() :
|
2017-11-12 22:17:27 +00:00
|
|
|
|
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
2017-11-11 03:05:35 +00:00
|
|
|
|
speaker_(new Speaker) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
crt_->set_composite_sampling_function(
|
2016-08-05 23:13:49 +00:00
|
|
|
|
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
|
|
|
|
"{"
|
2017-05-10 01:22:01 +00:00
|
|
|
|
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
2017-05-12 01:31:58 +00:00
|
|
|
|
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
2016-08-05 23:13:49 +00:00
|
|
|
|
|
|
|
|
|
"float chroma = cos(phase + phaseOffset);"
|
2017-05-14 02:01:02 +00:00
|
|
|
|
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
|
2016-08-05 23:13:49 +00:00
|
|
|
|
"}");
|
|
|
|
|
|
|
|
|
|
// default to NTSC
|
|
|
|
|
set_output_mode(OutputMode::NTSC);
|
2016-08-19 17:35:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
void set_clock_rate(double clock_rate) {
|
2017-10-21 23:49:04 +00:00
|
|
|
|
speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0));
|
2016-08-05 23:13:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
|
|
|
|
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
2016-06-05 14:51:07 +00:00
|
|
|
|
|
2016-06-23 11:32:24 +00:00
|
|
|
|
enum OutputMode {
|
|
|
|
|
PAL, NTSC
|
|
|
|
|
};
|
2016-08-05 23:13:49 +00:00
|
|
|
|
|
2016-06-23 11:32:24 +00:00
|
|
|
|
/*!
|
|
|
|
|
Sets the output mode to either PAL or NTSC.
|
|
|
|
|
*/
|
2017-03-26 18:34:47 +00:00
|
|
|
|
void set_output_mode(OutputMode output_mode) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
output_mode_ = output_mode;
|
2017-05-13 18:29:36 +00:00
|
|
|
|
|
|
|
|
|
// Lumunances are encoded trivially: on a 0–255 scale.
|
2017-05-10 01:22:01 +00:00
|
|
|
|
const uint8_t luminances[16] = {
|
2017-05-14 01:50:09 +00:00
|
|
|
|
0, 255, 109, 189,
|
|
|
|
|
199, 144, 159, 161,
|
|
|
|
|
126, 227, 227, 207,
|
|
|
|
|
235, 173, 188, 196
|
2016-08-05 23:13:49 +00:00
|
|
|
|
};
|
2017-05-13 18:29:36 +00:00
|
|
|
|
|
|
|
|
|
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
|
|
|
|
// anything above 191 disables the colour subcarrier. Phase is relative to the
|
|
|
|
|
// colour burst, so 0 is green.
|
2017-05-10 01:22:01 +00:00
|
|
|
|
const uint8_t pal_chrominances[16] = {
|
2017-05-14 01:50:09 +00:00
|
|
|
|
255, 255, 40, 112,
|
2017-05-14 02:01:02 +00:00
|
|
|
|
8, 88, 120, 56,
|
2017-05-14 01:50:09 +00:00
|
|
|
|
40, 48, 40, 112,
|
2017-05-14 02:01:02 +00:00
|
|
|
|
8, 88, 120, 56,
|
2016-08-05 23:13:49 +00:00
|
|
|
|
};
|
2017-05-06 23:53:24 +00:00
|
|
|
|
const uint8_t ntsc_chrominances[16] = {
|
2017-08-16 13:58:34 +00:00
|
|
|
|
255, 255, 8, 72,
|
|
|
|
|
32, 88, 48, 112,
|
|
|
|
|
0, 0, 8, 72,
|
|
|
|
|
32, 88, 48, 112,
|
2016-08-05 23:13:49 +00:00
|
|
|
|
};
|
2017-05-06 23:53:24 +00:00
|
|
|
|
const uint8_t *chrominances;
|
2016-08-05 23:13:49 +00:00
|
|
|
|
Outputs::CRT::DisplayType display_type;
|
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
switch(output_mode) {
|
2017-11-12 21:41:09 +00:00
|
|
|
|
default:
|
2016-08-05 23:13:49 +00:00
|
|
|
|
chrominances = pal_chrominances;
|
2017-11-12 22:17:27 +00:00
|
|
|
|
display_type = Outputs::CRT::DisplayType::PAL50;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
timing_.cycles_per_line = 71;
|
|
|
|
|
timing_.line_counter_increment_offset = 0;
|
|
|
|
|
timing_.lines_per_progressive_field = 312;
|
|
|
|
|
timing_.supports_interlacing = false;
|
2016-08-05 23:13:49 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OutputMode::NTSC:
|
|
|
|
|
chrominances = ntsc_chrominances;
|
2017-11-12 22:17:27 +00:00
|
|
|
|
display_type = Outputs::CRT::DisplayType::NTSC60;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
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;
|
2016-08-05 23:13:49 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-21 23:49:04 +00:00
|
|
|
|
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
2017-05-14 01:50:09 +00:00
|
|
|
|
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
2016-09-11 21:09:00 +00:00
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
// switch(output_mode) {
|
2016-09-11 21:09:00 +00:00
|
|
|
|
// case OutputMode::PAL:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
2016-09-11 21:09:00 +00:00
|
|
|
|
// break;
|
|
|
|
|
// case OutputMode::NTSC:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
2016-09-11 21:09:00 +00:00
|
|
|
|
// break;
|
|
|
|
|
// }
|
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
for(int c = 0; c < 16; c++) {
|
2017-10-22 02:30:15 +00:00
|
|
|
|
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
2017-05-10 01:22:01 +00:00
|
|
|
|
colour[0] = luminances[c];
|
2017-05-13 18:29:36 +00:00
|
|
|
|
colour[1] = chrominances[c];
|
2016-08-05 23:13:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-23 11:32:24 +00:00
|
|
|
|
|
2016-08-10 00:41:05 +00:00
|
|
|
|
/*!
|
|
|
|
|
Runs for cycles. Derr.
|
|
|
|
|
*/
|
2017-07-28 02:05:29 +00:00
|
|
|
|
inline void run_for(const Cycles cycles) {
|
2016-08-10 01:10:53 +00:00
|
|
|
|
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
2017-07-25 11:15:31 +00:00
|
|
|
|
cycles_since_speaker_update_ += cycles;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
2017-07-25 11:15:31 +00:00
|
|
|
|
int number_of_cycles = cycles.as_int();
|
2017-03-26 18:34:47 +00:00
|
|
|
|
while(number_of_cycles--) {
|
2016-08-10 01:10:53 +00:00
|
|
|
|
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
|
2016-12-03 15:51:09 +00:00
|
|
|
|
int previous_vertical_counter = vertical_counter_;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
|
|
|
|
// keep track of internal time relative to this scanline
|
2016-12-03 15:51:09 +00:00
|
|
|
|
horizontal_counter_++;
|
|
|
|
|
full_frame_counter_++;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(horizontal_counter_ == timing_.cycles_per_line) {
|
|
|
|
|
if(horizontal_drawing_latch_) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
current_character_row_++;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
if(
|
2016-12-03 15:51:09 +00:00
|
|
|
|
(current_character_row_ == 16) ||
|
|
|
|
|
(current_character_row_ == 8 && !registers_.tall_characters)
|
2016-08-10 01:10:53 +00:00
|
|
|
|
) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
current_character_row_ = 0;
|
|
|
|
|
current_row_++;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_line_cycle_ = -1;
|
|
|
|
|
columns_this_line_ = -1;
|
|
|
|
|
column_counter_ = -1;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
horizontal_counter_ = 0;
|
|
|
|
|
if(output_mode_ == OutputMode::PAL) is_odd_line_ ^= true;
|
|
|
|
|
horizontal_drawing_latch_ = false;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
vertical_counter_ ++;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
vertical_counter_ = 0;
|
|
|
|
|
full_frame_counter_ = 0;
|
|
|
|
|
|
|
|
|
|
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
|
|
|
|
current_row_ = 0;
|
|
|
|
|
rows_this_field_ = -1;
|
|
|
|
|
vertical_drawing_latch_ = false;
|
|
|
|
|
base_video_matrix_address_counter_ = 0;
|
|
|
|
|
current_character_row_ = 0;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check for vertical starting events
|
2016-12-03 15:51:09 +00:00
|
|
|
|
vertical_drawing_latch_ |= registers_.first_row_location == (previous_vertical_counter >> 1);
|
|
|
|
|
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
switch(pixel_line_cycle_) {
|
2016-08-10 01:10:53 +00:00
|
|
|
|
case -1:
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(horizontal_drawing_latch_) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_line_cycle_ = 0;
|
|
|
|
|
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
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;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t fetch_address = 0x1c;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
|
|
|
|
if(column_counter_&1) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
} else {
|
2017-10-04 02:04:15 +00:00
|
|
|
|
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
2016-12-03 15:51:09 +00:00
|
|
|
|
video_matrix_address_counter_++;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
if(
|
2016-12-03 15:51:09 +00:00
|
|
|
|
(current_character_row_ == 15) ||
|
|
|
|
|
(current_character_row_ == 7 && !registers_.tall_characters)
|
2016-08-10 01:10:53 +00:00
|
|
|
|
) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
base_video_matrix_address_counter_ = video_matrix_address_counter_;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetch_address &= 0x3fff;
|
|
|
|
|
|
2016-08-10 00:41:05 +00:00
|
|
|
|
uint8_t pixel_data;
|
|
|
|
|
uint8_t colour_data;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
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
|
2016-12-03 15:51:09 +00:00
|
|
|
|
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;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
else {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// apply vertical sync
|
|
|
|
|
if(
|
2016-12-03 15:51:09 +00:00
|
|
|
|
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
|
|
|
|
|
(registers_.interlaced &&
|
2016-08-10 01:10:53 +00:00
|
|
|
|
(
|
2016-12-03 15:51:09 +00:00
|
|
|
|
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
|
|
|
|
(vertical_counter_ == 1) || (vertical_counter_ == 2) ||
|
|
|
|
|
(vertical_counter_ == 3 && horizontal_counter_ <= 32)
|
2016-08-10 01:10:53 +00:00
|
|
|
|
)
|
|
|
|
|
))
|
2016-12-03 15:51:09 +00:00
|
|
|
|
this_state_ = State::Sync;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
|
|
|
|
// update the CRT
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(this_state_ != output_state_) {
|
|
|
|
|
switch(output_state_) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
2017-07-08 03:35:14 +00:00
|
|
|
|
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
case State::Border: output_border(cycles_in_state_ * 4); break;
|
|
|
|
|
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
2016-12-03 15:51:09 +00:00
|
|
|
|
output_state_ = this_state_;
|
|
|
|
|
cycles_in_state_ = 0;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
|
|
|
|
pixel_pointer = nullptr;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(output_state_ == State::Pixels) {
|
2017-10-22 02:30:15 +00:00
|
|
|
|
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-03 15:51:09 +00:00
|
|
|
|
cycles_in_state_++;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(this_state_ == State::Pixels) {
|
|
|
|
|
if(column_counter_&1) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
character_value_ = pixel_data;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(pixel_pointer) {
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t cell_colour = colours_[character_colour_ & 0x7];
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(!(character_colour_&0x8)) {
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t colours[2];
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(registers_.invertedCells) {
|
2016-08-10 01:10:53 +00:00
|
|
|
|
colours[0] = cell_colour;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
colours[1] = registers_.backgroundColour;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
} else {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
colours[0] = registers_.backgroundColour;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
colours[1] = cell_colour;
|
|
|
|
|
}
|
2016-12-03 15:51:09 +00:00
|
|
|
|
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];
|
2017-03-26 18:34:47 +00:00
|
|
|
|
} else {
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
2016-08-10 01:10:53 +00:00
|
|
|
|
pixel_pointer[0] =
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
2016-08-10 01:10:53 +00:00
|
|
|
|
pixel_pointer[2] =
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_pointer[3] = colours[(character_value_ >> 4)&3];
|
2016-08-10 01:10:53 +00:00
|
|
|
|
pixel_pointer[4] =
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_pointer[5] = colours[(character_value_ >> 2)&3];
|
2016-08-10 01:10:53 +00:00
|
|
|
|
pixel_pointer[6] =
|
2016-12-03 15:51:09 +00:00
|
|
|
|
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
2017-05-13 18:29:36 +00:00
|
|
|
|
|
2016-08-10 01:10:53 +00:00
|
|
|
|
pixel_pointer += 8;
|
|
|
|
|
}
|
2017-03-26 18:34:47 +00:00
|
|
|
|
} else {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
character_code_ = pixel_data;
|
|
|
|
|
character_colour_ = colour_data;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
column_counter_++;
|
2016-08-10 01:10:53 +00:00
|
|
|
|
}
|
2016-08-10 00:41:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
|
|
|
|
*/
|
2017-05-15 11:38:59 +00:00
|
|
|
|
inline void flush() { update_audio(); speaker_->flush(); }
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Writes to a 6560 register.
|
|
|
|
|
*/
|
2017-03-26 18:34:47 +00:00
|
|
|
|
void set_register(int address, uint8_t value) {
|
2016-08-10 00:41:05 +00:00
|
|
|
|
address &= 0xf;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.direct_values[address] = value;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
switch(address) {
|
2016-08-10 00:41:05 +00:00
|
|
|
|
case 0x0:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
|
|
|
|
registers_.first_column_location = value & 0x7f;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x1:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.first_row_location = value;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x2:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.number_of_columns = value & 0x7f;
|
2017-10-04 02:04:15 +00:00
|
|
|
|
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x3:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.number_of_rows = (value >> 1)&0x3f;
|
|
|
|
|
registers_.tall_characters = !!(value&0x01);
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5:
|
2017-10-04 02:04:15 +00:00
|
|
|
|
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
|
|
|
|
|
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xa:
|
|
|
|
|
case 0xb:
|
|
|
|
|
case 0xc:
|
|
|
|
|
case 0xd:
|
|
|
|
|
update_audio();
|
2016-12-03 15:51:09 +00:00
|
|
|
|
speaker_->set_control(address - 0xa, value);
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xe:
|
|
|
|
|
update_audio();
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.auxiliary_colour = colours_[value >> 4];
|
|
|
|
|
speaker_->set_volume(value & 0xf);
|
2016-08-10 00:41:05 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
|
case 0xf: {
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t new_border_colour = colours_[value & 0x07];
|
2017-03-26 18:34:47 +00:00
|
|
|
|
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
output_border(cycles_in_state_ * 4);
|
|
|
|
|
cycles_in_state_ = 0;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
}
|
2016-12-03 15:51:09 +00:00
|
|
|
|
registers_.invertedCells = !((value >> 3)&1);
|
|
|
|
|
registers_.borderColour = new_border_colour;
|
|
|
|
|
registers_.backgroundColour = colours_[value >> 4];
|
2016-08-10 00:41:05 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// TODO: the lightpen, etc
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Reads from a 6560 register.
|
|
|
|
|
*/
|
2017-03-26 18:34:47 +00:00
|
|
|
|
uint8_t get_register(int address) {
|
2016-08-10 00:41:05 +00:00
|
|
|
|
address &= 0xf;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
switch(address) {
|
2016-12-03 15:51:09 +00:00
|
|
|
|
default: return registers_.direct_values[address];
|
2017-10-04 02:04:15 +00:00
|
|
|
|
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
2016-08-10 00:41:05 +00:00
|
|
|
|
case 0x04: return (current_line >> 1) & 0xff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2016-12-03 15:51:09 +00:00
|
|
|
|
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
2016-12-03 15:51:09 +00:00
|
|
|
|
std::shared_ptr<Speaker> speaker_;
|
2017-07-25 11:15:31 +00:00
|
|
|
|
Cycles cycles_since_speaker_update_;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
void update_audio() {
|
2017-07-25 11:15:31 +00:00
|
|
|
|
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
2016-08-10 00:41:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t backgroundColour, borderColour, auxiliary_colour;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
bool invertedCells;
|
|
|
|
|
|
|
|
|
|
uint8_t direct_values[16];
|
2016-12-03 15:51:09 +00:00
|
|
|
|
} registers_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// output state
|
|
|
|
|
enum State {
|
|
|
|
|
Sync, ColourBurst, Border, Pixels
|
2016-12-03 15:51:09 +00:00
|
|
|
|
} this_state_, output_state_;
|
|
|
|
|
unsigned int cycles_in_state_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// counters that cover an entire field
|
2017-11-11 03:05:35 +00:00
|
|
|
|
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// latches dictating start and length of drawing
|
2016-12-03 15:51:09 +00:00
|
|
|
|
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
|
|
|
|
int rows_this_field_, columns_this_line_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// current drawing position counter
|
2016-12-03 15:51:09 +00:00
|
|
|
|
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_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// data latched from the bus
|
2016-12-03 15:51:09 +00:00
|
|
|
|
uint8_t character_code_, character_colour_, character_value_;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
2017-11-11 03:05:35 +00:00
|
|
|
|
bool is_odd_frame_ = false, is_odd_line_ = false;
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
|
|
|
|
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t colours_[16];
|
2016-08-10 00:41:05 +00:00
|
|
|
|
|
2017-05-10 01:22:01 +00:00
|
|
|
|
uint16_t *pixel_pointer;
|
2017-03-26 18:34:47 +00:00
|
|
|
|
void output_border(unsigned int number_of_cycles) {
|
2017-10-22 02:30:15 +00:00
|
|
|
|
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
|
2016-12-03 15:51:09 +00:00
|
|
|
|
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
|
|
|
|
crt_->output_level(number_of_cycles);
|
2016-08-10 00:41:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
int cycles_per_line;
|
|
|
|
|
int line_counter_increment_offset;
|
|
|
|
|
int lines_per_progressive_field;
|
|
|
|
|
bool supports_interlacing;
|
2016-12-03 15:51:09 +00:00
|
|
|
|
} timing_;
|
|
|
|
|
OutputMode output_mode_;
|
2016-06-05 14:51:07 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* _560_hpp */
|