1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-27 00:30:26 +00:00

Merge branch 'master' into Plus4VideoOptions

This commit is contained in:
Thomas Harte 2025-01-07 20:08:54 -05:00
commit 457b28c22c
5 changed files with 159 additions and 69 deletions

View File

@ -251,13 +251,11 @@ public:
}
const auto output = io_output_ | ~io_direction_;
// tape_player_->set_motor_control(~output & 0x08);
tape_player_->set_motor_control(~output & 0x08);
serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01));
serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02));
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
}
// printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w');
} else if(address < 0xfd00 || address >= 0xff40) {
if(isReadOperation(operation)) {
*value = map_.read(address);
@ -269,8 +267,27 @@ public:
if(isReadOperation(operation)) {
// printf("TODO: read @ %04x\n", address);
if((address & 0xfff0) == 0xfd10) {
tape_player_->set_motor_control(true);
*value = 0xff ^ 0x4; // Seems to detect the play button.
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
// If play button is not currently pressed and this read is immediately followed by
// an AND 4, press it. The kernel will deal with motor control subsequently.
if(!play_button_) {
const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
const uint8_t next[] = {
map_.read(pc+0),
map_.read(pc+1),
map_.read(pc+2),
map_.read(pc+3),
};
// TODO: boil this down to a PC check. It's currently in this form as I'm unclear what
// diversity of kernels exist.
if(next[0] == 0x29 && next[1] == 0x04 && next[2] == 0xd0 && next[3] == 0xf4) {
play_button_ = true;
}
}
*value = 0xff ^ (play_button_ ? 0x4 :0x0);
} else {
*value = 0xff;
}
@ -286,33 +303,32 @@ public:
case 0xff03: *value = timers_.read<3>(); break;
case 0xff04: *value = timers_.read<4>(); break;
case 0xff05: *value = timers_.read<5>(); break;
case 0xff08: *value = keyboard_latch_; break;
case 0xff09: *value = interrupts_.status(); break;
case 0xff0a: *value = interrupts_.mask(); break;
case 0xff06: *value = video_.read<0xff06>(); break;
case 0xff07: *value = video_.read<0xff07>(); break;
case 0xff08: *value = keyboard_latch_; break;
case 0xff09: *value = interrupts_.status(); break;
case 0xff0a: *value = interrupts_.mask(); break;
case 0xff0b: *value = video_.read<0xff0b>(); break;
case 0xff1c: *value = video_.read<0xff1c>(); break;
case 0xff1d: *value = video_.read<0xff1d>(); break;
case 0xff0c: *value = video_.read<0xff0c>(); break;
case 0xff0d: *value = video_.read<0xff0d>(); break;
case 0xff0e: *value = ff0e_; break;
case 0xff0f: *value = ff0f_; break;
case 0xff10: *value = ff10_; break;
case 0xff11: *value = ff11_; break;
case 0xff12: *value = ff12_; break;
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
case 0xff0c: *value = video_.read<0xff0c>(); break;
case 0xff0d: *value = video_.read<0xff0d>(); break;
case 0xff14: *value = video_.read<0xff14>(); break;
case 0xff15: *value = video_.read<0xff15>(); break;
case 0xff16: *value = video_.read<0xff16>(); break;
case 0xff17: *value = video_.read<0xff17>(); break;
case 0xff18: *value = video_.read<0xff18>(); break;
case 0xff19: *value = video_.read<0xff19>(); break;
case 0xff1a: *value = video_.read<0xff1a>(); break;
case 0xff1b: *value = video_.read<0xff1b>(); break;
case 0xff1c: *value = video_.read<0xff1c>(); break;
case 0xff1d: *value = video_.read<0xff1d>(); break;
case 0xff1e: *value = video_.read<0xff1e>(); break;
case 0xff1f: *value = video_.read<0xff1f>(); break;
default:
printf("TODO: TED read at %04x\n", address);
@ -325,7 +341,8 @@ public:
case 0xff03: timers_.write<3>(*value); break;
case 0xff04: timers_.write<4>(*value); break;
case 0xff05: timers_.write<5>(*value); break;
case 0xff06: video_.write<0xff06>(*value); break;
case 0xff07: video_.write<0xff07>(*value); break;
case 0xff08:
// Observation here: the kernel posts a 0 to this
// address upon completing each keyboard scan cycle,
@ -348,7 +365,6 @@ public:
((*value & 0x80) ? 0x00 : key_states_[7])
);
break;
case 0xff09:
interrupts_.set_status(*value);
break;
@ -357,9 +373,6 @@ public:
video_.write<0xff0a>(*value);
break;
case 0xff0b: video_.write<0xff0b>(*value); break;
case 0xff06: video_.write<0xff06>(*value); break;
case 0xff07: video_.write<0xff07>(*value); break;
case 0xff0c: video_.write<0xff0c>(*value); break;
case 0xff0d: video_.write<0xff0d>(*value); break;
case 0xff0e:
@ -400,14 +413,17 @@ public:
video_.write<0xff13>(*value);
break;
case 0xff14: video_.write<0xff14>(*value); break;
case 0xff1a: video_.write<0xff1a>(*value); break;
case 0xff1b: video_.write<0xff1b>(*value); break;
case 0xff15: video_.write<0xff15>(*value); break;
case 0xff16: video_.write<0xff16>(*value); break;
case 0xff17: video_.write<0xff17>(*value); break;
case 0xff18: video_.write<0xff18>(*value); break;
case 0xff19: video_.write<0xff19>(*value); break;
case 0xff1a: video_.write<0xff1a>(*value); break;
case 0xff1b: video_.write<0xff1b>(*value); break;
case 0xff1c: video_.write<0xff1c>(*value); break;
case 0xff1d: video_.write<0xff1d>(*value); break;
case 0xff1e: video_.write<0xff1e>(*value); break;
case 0xff1f: video_.write<0xff1f>(*value); break;
case 0xff3e: page_cpu_rom(); break;
case 0xff3f: page_cpu_ram(); break;
@ -559,6 +575,7 @@ private:
SerialPort serial_port_;
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
bool play_button_ = false;
bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack.
void set_use_fast_tape() {}

View File

@ -49,12 +49,19 @@ public:
case 0xff0b: return uint8_t(raster_interrupt_);
case 0xff0c: return cursor_position_ >> 8;
case 0xff0d: return uint8_t(cursor_position_);
case 0xff1c: return uint8_t(vertical_counter_ >> 8);
case 0xff1d: return uint8_t(vertical_counter_);
case 0xff14: return uint8_t((video_matrix_base_ >> 8) & 0xf8);
case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19:
return raw_background_[size_t(address - 0xff15)];
case 0xff1c: return uint8_t(vertical_counter_ >> 8);
case 0xff1d: return uint8_t(vertical_counter_);
case 0xff1a:
case 0xff1b:
case 0xff1e:
case 0xff1f:
printf("TODO: TED video read at %04x\n", address);
}
return 0xff;
@ -126,6 +133,16 @@ public:
}
break;
case 0xff0a:
raster_interrupt_ = (raster_interrupt_ & 0x00ff) | ((value & 1) << 8);
break;
case 0xff0b:
raster_interrupt_ = (raster_interrupt_ & 0xff00) | value;
break;
case 0xff0c: load_high10(cursor_position_); break;
case 0xff0d: load_low8(cursor_position_); break;
case 0xff12:
bitmap_base_ = uint16_t((value & 0x38) << 10);
break;
@ -137,22 +154,19 @@ public:
video_matrix_base_ = uint16_t((value & 0xf8) << 8);
break;
case 0xff0a:
raster_interrupt_ = (raster_interrupt_ & 0x00ff) | ((value & 1) << 8);
break;
case 0xff0b:
raster_interrupt_ = (raster_interrupt_ & 0xff00) | value;
break;
case 0xff0c: load_high10(cursor_position_); break;
case 0xff0d: load_low8(cursor_position_); break;
case 0xff1a: load_high10(character_position_reload_); break;
case 0xff1b: load_low8(character_position_reload_); break;
case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19:
raw_background_[size_t(address - 0xff15)] = value;
background_[size_t(address - 0xff15)] = colour(value);
break;
case 0xff1a: load_high10(character_position_reload_); break;
case 0xff1b: load_low8(character_position_reload_); break;
case 0xff1c:
case 0xff1d:
case 0xff1e:
case 0xff1f:
printf("TODO: TED video write at %04x\n", address);
}
}
@ -329,7 +343,11 @@ public:
next_character_.advance();
next_pixels_.advance();
output_.load_pixels(next_pixels_.read(), x_scroll_);
const bool is_2bpp =
(video_mode_ == VideoMode::MulticolourBitmap) ||
(video_mode_ == VideoMode::MulticolourText && output_.attributes<0>() & 0x8);
const int adjustment = (x_scroll_ & 1) && is_2bpp;
output_.load_pixels(next_pixels_.read(), x_scroll_ + adjustment);
}
if(increment_video_counter_) {
//
@ -987,22 +1005,25 @@ private:
template <int length, bool is_leftovers>
void draw_2bpp_segment(uint16_t *const target, const uint16_t *colours) {
constexpr int leftover = is_leftovers && (length & 1);
static_assert(length + leftover <= 8);
if(target) {
const auto pixels = output_.pixels();
// Intention: skip first output if leftover is 1, but still do the correct
// length of output.
if constexpr (!leftover && length >= 1) target[0] = colours[(pixels >> 6) & 3];
if constexpr (length + leftover >= 2) target[1] = colours[(pixels >> 6) & 3];
if constexpr (length + leftover >= 3) target[2] = colours[(pixels >> 4) & 3];
if constexpr (length + leftover >= 4) target[3] = colours[(pixels >> 4) & 3];
if constexpr (length + leftover >= 5) target[4] = colours[(pixels >> 2) & 3];
if constexpr (length + leftover >= 6) target[5] = colours[(pixels >> 2) & 3];
if constexpr (length + leftover >= 7) target[6] = colours[(pixels >> 0) & 3];
if constexpr (length + leftover >= 8) target[7] = colours[(pixels >> 0) & 3];
if constexpr (length + leftover >= 2) target[1 - leftover] = colours[(pixels >> 6) & 3];
if constexpr (length + leftover >= 3) target[2 - leftover] = colours[(pixels >> 4) & 3];
if constexpr (length + leftover >= 4) target[3 - leftover] = colours[(pixels >> 4) & 3];
if constexpr (length + leftover >= 5) target[4 - leftover] = colours[(pixels >> 2) & 3];
if constexpr (length + leftover >= 6) target[5 - leftover] = colours[(pixels >> 2) & 3];
if constexpr (length + leftover >= 7) target[6 - leftover] = colours[(pixels >> 0) & 3];
if constexpr (length + leftover >= 8) target[7 - leftover] = colours[(pixels >> 0) & 3];
}
if(is_leftovers) {
output_.advance_pixels(length + leftover);
if constexpr (is_leftovers) {
constexpr int shift_distance = length + leftover;
static_assert(!(shift_distance&1));
output_.advance_pixels(shift_distance);
} else {
output_.advance_pixels(length & ~1);
}

View File

@ -185,7 +185,15 @@ static const int NumBuffers = MaximumBacklog + 1;
// Starting is a no-op if the queue is already playing, but it may not have been started
// yet, or may have been paused due to a pipeline failure if the producer is running slowly.
if(enqueuedBuffers > 1) {
OSSGuard(^{return AudioQueueStart(self->_audioQueue, NULL);});
OSSGuard(^{
const OSStatus result = AudioQueueStart(self->_audioQueue, NULL);
if(result == kAudioQueueErr_CannotStart) {
// Accept cannot-start, hoping it's ephemeral; Apple's specific advice is:
// "Sleeping briefly and retrying is recommended".
return kAudioSessionNoError;
}
return result;
});
}
[_queueLock unlock];
}

View File

@ -17,33 +17,44 @@ CommodoreTAP::CommodoreTAP(const std::string &file_name) : Tape(serialiser_), se
CommodoreTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read)
{
if(!file_.check_signature("C64-TAPE-RAW"))
const bool is_c64 = file_.check_signature("C64-TAPE-RAW");
file_.seek(0, SEEK_SET);
const bool is_c16 = file_.check_signature("C16-TAPE-RAW");
if(!is_c64 && !is_c16) {
throw ErrorNotCommodoreTAP;
}
type_ = is_c16 ? FileType::C16 : FileType::C64;
// check the file version
switch(file_.get8()) {
case 0: updated_layout_ = false; break;
case 1: updated_layout_ = true; break;
default: throw ErrorNotCommodoreTAP;
// Get and check the file version.
version_ = file_.get8();
if(version_ > 2) {
throw ErrorNotCommodoreTAP;
}
// skip reserved bytes
file_.seek(3, SEEK_CUR);
// Read clock rate-implying bytes.
platform_ = Platform(file_.get8());
video_ = VideoStandard(file_.get8());
file_.seek(1, SEEK_CUR);
// read file size
// Read file size.
file_size_ = file_.get32le();
// set up for pulse output at the PAL clock rate, with each high and
// low being half of whatever length values will be read; pretend that
// a high pulse has just been distributed to imply that the next thing
// that needs to happen is a length check
current_pulse_.length.clock_rate = 985248 * 2;
current_pulse_.type = Pulse::High;
// Pick clock rate.
current_pulse_.length.clock_rate = static_cast<unsigned int>(
[&] {
switch(platform_) {
case Platform::Vic20: // It empirically seems like Vic-20 waves are counted with C64 timings?
case Platform::C64: return video_ == VideoStandard::PAL ? 985'248 : 1'022'727;
case Platform::C16: return video_ == VideoStandard::PAL ? 886'722 : 894'886;
}
}() * (double_clock() ? 2 : 1)
);
reset();
}
void CommodoreTAP::Serialiser::reset() {
file_.seek(0x14, SEEK_SET);
current_pulse_.type = Pulse::High;
current_pulse_.type = Pulse::High; // Implies that the first posted wave will be ::Low.
is_at_end_ = false;
}
@ -56,10 +67,10 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() {
return current_pulse_;
}
if(current_pulse_.type == Pulse::High) {
const auto read_next_length = [&]() -> bool {
uint32_t next_length;
const uint8_t next_byte = file_.get8();
if(!updated_layout_ || next_byte > 0) {
if(!updated_layout() || next_byte > 0) {
next_length = uint32_t(next_byte) << 3;
} else {
next_length = file_.get24le();
@ -69,8 +80,19 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() {
is_at_end_ = true;
current_pulse_.length.length = current_pulse_.length.clock_rate;
current_pulse_.type = Pulse::Zero;
return false;
} else {
current_pulse_.length.length = next_length;
return true;
}
};
if(half_waves()) {
if(read_next_length()) {
current_pulse_.type = current_pulse_.type == Pulse::High ? Pulse::Low : Pulse::High;
}
} else if(current_pulse_.type == Pulse::High) {
if(read_next_length()) {
current_pulse_.type = Pulse::Low;
}
} else {

View File

@ -43,8 +43,30 @@ private:
Storage::FileHolder file_;
bool updated_layout_;
uint32_t file_size_;
enum class FileType {
C16, C64,
} type_;
uint8_t version_;
enum class Platform: uint8_t {
C64 = 0,
Vic20 = 1,
C16 = 2,
} platform_;
enum class VideoStandard: uint8_t {
PAL = 0,
NTSC1 = 1,
NTSC2 = 2,
} video_;
bool updated_layout() const {
return version_ >= 1;
}
bool half_waves() const {
return version_ >= 2;
}
bool double_clock() const {
return platform_ != Platform::C16 || !half_waves();
}
Pulse current_pulse_;
bool is_at_end_ = false;