mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-28 06:29:37 +00:00
Merge pull request #410 from TomHarte/VicNTSC
Corrects NTSC VIC raster register timing.
This commit is contained in:
commit
8d4d5d1f46
@ -149,6 +149,9 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
}
|
||||
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ static uint8_t noise_pattern[] = {
|
||||
// means every second cycle, etc.
|
||||
|
||||
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
@ -124,7 +124,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
|
||||
}
|
||||
|
||||
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
|
@ -100,10 +100,10 @@ template <class T> class MOS6560 {
|
||||
|
||||
// Luminances are encoded trivially: on a 0–255 scale.
|
||||
const uint8_t luminances[16] = {
|
||||
0, 255, 60, 189,
|
||||
80, 144, 40, 227,
|
||||
90, 161, 207, 227,
|
||||
200, 196, 160, 196
|
||||
0, 255, 64, 192,
|
||||
128, 128, 64, 192,
|
||||
128, 192, 128, 255,
|
||||
192, 192, 128, 255
|
||||
};
|
||||
|
||||
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
||||
@ -130,6 +130,7 @@ template <class T> class MOS6560 {
|
||||
display_type = Outputs::CRT::DisplayType::PAL50;
|
||||
timing_.cycles_per_line = 71;
|
||||
timing_.line_counter_increment_offset = 0;
|
||||
timing_.final_line_increment_position = 71;
|
||||
timing_.lines_per_progressive_field = 312;
|
||||
timing_.supports_interlacing = false;
|
||||
break;
|
||||
@ -138,7 +139,8 @@ template <class T> class MOS6560 {
|
||||
chrominances = ntsc_chrominances;
|
||||
display_type = Outputs::CRT::DisplayType::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_.line_counter_increment_offset = 40;
|
||||
timing_.final_line_increment_position = 58;
|
||||
timing_.lines_per_progressive_field = 261;
|
||||
timing_.supports_interlacing = true;
|
||||
break;
|
||||
@ -176,7 +178,6 @@ template <class T> class MOS6560 {
|
||||
|
||||
// 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_++;
|
||||
@ -198,9 +199,8 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ = false;
|
||||
|
||||
vertical_counter_ ++;
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
||||
if(vertical_counter_ == lines_this_field()) {
|
||||
vertical_counter_ = 0;
|
||||
full_frame_counter_ = 0;
|
||||
|
||||
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
||||
current_row_ = 0;
|
||||
@ -262,7 +262,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
// apply vertical sync
|
||||
if(
|
||||
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
|
||||
(vertical_counter_ < 3 && is_odd_frame()) ||
|
||||
(registers_.interlaced &&
|
||||
(
|
||||
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
||||
@ -414,11 +414,10 @@ template <class T> class MOS6560 {
|
||||
*/
|
||||
uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (raster_value() >> 1) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,7 +452,29 @@ template <class T> class MOS6560 {
|
||||
unsigned int cycles_in_state_;
|
||||
|
||||
// counters that cover an entire field
|
||||
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
|
||||
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
||||
const int lines_this_field() {
|
||||
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
|
||||
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
|
||||
}
|
||||
const int raster_value() {
|
||||
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
const int line = vertical_counter_ + bonus_line;
|
||||
const int final_line = lines_this_field();
|
||||
|
||||
if(line < final_line)
|
||||
return line;
|
||||
|
||||
if(is_odd_frame()) {
|
||||
return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1;
|
||||
} else {
|
||||
return line % final_line;
|
||||
}
|
||||
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
|
||||
}
|
||||
bool is_odd_frame() {
|
||||
return is_odd_frame_ || !registers_.interlaced;
|
||||
}
|
||||
|
||||
// latches dictating start and length of drawing
|
||||
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
||||
@ -483,6 +504,7 @@ template <class T> class MOS6560 {
|
||||
struct {
|
||||
int cycles_per_line;
|
||||
int line_counter_increment_offset;
|
||||
int final_line_increment_position;
|
||||
int lines_per_progressive_field;
|
||||
bool supports_interlacing;
|
||||
} timing_;
|
||||
|
@ -374,7 +374,7 @@ class ConcreteMachine:
|
||||
type_string(target->loading_command);
|
||||
}
|
||||
|
||||
if(target->media.disks.size()) {
|
||||
if(commodore_target_.has_c1540) {
|
||||
// construct the 1540
|
||||
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
|
||||
|
||||
@ -546,12 +546,12 @@ class ConcreteMachine:
|
||||
if(isReadOperation(operation)) {
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) {
|
||||
if(!(address&0x100)) {
|
||||
update_video();
|
||||
result &= mos6560_->get_register(address);
|
||||
}
|
||||
if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
|
||||
if(address & 0x10) result &= user_port_via_.get_register(address);
|
||||
if(address & 0x20) result &= keyboard_via_.get_register(address);
|
||||
}
|
||||
*value = result;
|
||||
|
||||
@ -630,13 +630,17 @@ class ConcreteMachine:
|
||||
update_video();
|
||||
ram[address & 0x3ff] = *value;
|
||||
}
|
||||
// Anything between 0x9000 and 0x9400 is the IO area.
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) {
|
||||
// The VIC is selected by bit 8 = 0
|
||||
if(!(address&0x100)) {
|
||||
update_video();
|
||||
mos6560_->set_register(address, *value);
|
||||
}
|
||||
if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
|
||||
// The first VIA is selected by bit 4 = 1.
|
||||
if(address & 0x10) user_port_via_.set_register(address, *value);
|
||||
// The second VIA is selected by bit 5 = 1.
|
||||
if(address & 0x20) keyboard_via_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,11 @@
|
||||
<rect key="contentRect" x="196" y="240" width="600" height="165"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="165"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
|
||||
<rect key="frame" x="13" y="51" width="574" height="140"/>
|
||||
<rect key="frame" x="13" y="51" width="574" height="100"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Acorn Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
@ -334,6 +334,9 @@
|
||||
<buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="createMachine:" target="-2" id="2wo-Zv-H4f"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user