mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-26 09:29:45 +00:00
Merge branch 'master' into AYFidelity
This commit is contained in:
commit
e4f04d0977
@ -31,9 +31,18 @@ class BusHandler {
|
||||
void perform_bus_cycle(const BusState &) {}
|
||||
};
|
||||
|
||||
enum Personality {
|
||||
HD6845S, //
|
||||
UM6845R, //
|
||||
MC6845, //
|
||||
AMS40226 //
|
||||
};
|
||||
|
||||
template <class T> class CRTC6845 {
|
||||
public:
|
||||
CRTC6845(T &bus_handler) : bus_handler_(bus_handler) {}
|
||||
|
||||
CRTC6845(Personality p, T &bus_handler) :
|
||||
personality_(p), bus_handler_(bus_handler) {}
|
||||
|
||||
void run_for(Cycles cycles) {
|
||||
int cyles_remaining = cycles.as_int();
|
||||
@ -58,22 +67,18 @@ template <class T> class CRTC6845 {
|
||||
if(hsync_down_counter_) bus_state_.hsync = true;
|
||||
}
|
||||
|
||||
// update refresh address
|
||||
if(character_is_visible_) {
|
||||
bus_state_.refresh_address++;
|
||||
}
|
||||
|
||||
// check for end of visible characters
|
||||
if(character_counter_ == registers_[1]) {
|
||||
bus_state_.refresh_address++;
|
||||
character_is_visible_ = false;
|
||||
} else {
|
||||
// update refresh address
|
||||
if(character_is_visible_) {
|
||||
bus_state_.refresh_address++;
|
||||
}
|
||||
}
|
||||
|
||||
// check for end-of-line
|
||||
if(is_end_of_line) {
|
||||
character_counter_ = 0;
|
||||
character_is_visible_ = true;
|
||||
|
||||
// check for end of vertical sync
|
||||
if(vsync_down_counter_) {
|
||||
vsync_down_counter_--;
|
||||
@ -94,11 +99,12 @@ template <class T> class CRTC6845 {
|
||||
} else {
|
||||
// advance vertical counter
|
||||
if(bus_state_.row_address == registers_[9]) {
|
||||
line_address_ = bus_state_.refresh_address;
|
||||
if(!character_is_visible_)
|
||||
line_address_ = bus_state_.refresh_address;
|
||||
bus_state_.row_address = 0;
|
||||
|
||||
bool is_at_end_of_frame = line_counter_ == registers_[4];
|
||||
line_counter_++;
|
||||
line_counter_ = (line_counter_ + 1) & 0x7f;
|
||||
|
||||
// check for end of visible lines
|
||||
if(line_counter_ == registers_[6]) {
|
||||
@ -125,19 +131,23 @@ template <class T> class CRTC6845 {
|
||||
line_counter_ = 0;
|
||||
}
|
||||
} else {
|
||||
bus_state_.row_address++;
|
||||
bus_state_.refresh_address = line_address_;
|
||||
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
|
||||
}
|
||||
bus_state_.refresh_address = line_address_;
|
||||
}
|
||||
|
||||
character_counter_ = 0;
|
||||
character_is_visible_ = true;
|
||||
}
|
||||
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_state_.refresh_address &= 0x3fff;
|
||||
bus_handler_.perform_bus_cycle(bus_state_);
|
||||
}
|
||||
}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
selected_register_ = (int)r & 15;
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
uint8_t get_status() {
|
||||
@ -145,18 +155,31 @@ template <class T> class CRTC6845 {
|
||||
}
|
||||
|
||||
uint8_t get_register() {
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
|
||||
void set_register(uint8_t value) {
|
||||
registers_[selected_register_] = value;
|
||||
static uint8_t masks[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
|
||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||
};
|
||||
|
||||
if(selected_register_ < 16)
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
}
|
||||
|
||||
void trigger_light_pen() {
|
||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||
registers_[16] = bus_state_.refresh_address >> 8;
|
||||
}
|
||||
|
||||
private:
|
||||
Personality personality_;
|
||||
T &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
uint8_t registers_[16];
|
||||
uint8_t registers_[18];
|
||||
int selected_register_;
|
||||
|
||||
uint8_t character_counter_;
|
||||
|
@ -13,10 +13,10 @@
|
||||
using namespace Intel;
|
||||
|
||||
namespace {
|
||||
const uint8_t StatusRQM = 0x80; // Set: ready to send or receive from processor.
|
||||
const uint8_t StatusDIO = 0x40; // Set: data is expected to be taken from the 8272 by the processor.
|
||||
const uint8_t StatusNDM = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled.
|
||||
const uint8_t StatusCB = 0x10; // Set: the FDC is busy.
|
||||
const uint8_t StatusRequest = 0x80; // Set: ready to send or receive from processor.
|
||||
const uint8_t StatusDirection = 0x40; // Set: data is expected to be taken from the 8272 by the processor.
|
||||
const uint8_t StatusNonDMAExecuting = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled.
|
||||
const uint8_t StatusBusy = 0x10; // Set: the FDC is busy.
|
||||
//const uint8_t StatusD3B = 0x08; // Set: drive 3 is seeking.
|
||||
//const uint8_t StatusD2B = 0x04; // Set: drive 2 is seeking.
|
||||
//const uint8_t StatusD1B = 0x02; // Set: drive 1 is seeking.
|
||||
@ -25,7 +25,7 @@ const uint8_t StatusCB = 0x10; // Set: the FDC is busy.
|
||||
|
||||
i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||
main_status_(StatusRQM),
|
||||
main_status_(0),
|
||||
interesting_event_mask_((int)Event8272::CommandByte),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
@ -76,7 +76,7 @@ void i8272::set_register(int address, uint8_t value) {
|
||||
if(!address) return;
|
||||
|
||||
// if not ready for commands, do nothing
|
||||
if(!(main_status_ & StatusRQM)) return;
|
||||
if(!(main_status_ & StatusRequest)) return;
|
||||
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
@ -135,7 +135,7 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
set_data_mode(Scanning);
|
||||
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
if(!dma_mode_) main_status_ |= StatusNDM; \
|
||||
if(!dma_mode_) main_status_ |= StatusNonDMAExecuting; \
|
||||
set_drive(drives_[command_[1]&3].drive); \
|
||||
set_is_double_density(command_[0] & 0x40); \
|
||||
invalidate_track();
|
||||
@ -150,94 +150,94 @@ void i8272::posit_event(int event_type) {
|
||||
// into wait_for_complete_command_sequence.
|
||||
wait_for_command:
|
||||
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
|
||||
main_status_ &= ~(StatusCB | StatusNDM);
|
||||
main_status_ &= ~(StatusBusy | StatusNonDMAExecuting);
|
||||
command_.clear();
|
||||
|
||||
// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
|
||||
// until it has a quantity that make up an entire command, then resets the data request bit and
|
||||
// branches to that command.
|
||||
wait_for_complete_command_sequence:
|
||||
main_status_ |= StatusRQM;
|
||||
main_status_ |= StatusRequest;
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||
main_status_ |= StatusCB;
|
||||
main_status_ |= StatusBusy;
|
||||
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x06: // read data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto read_data;
|
||||
|
||||
case 0x0b: // read deleted data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto read_deleted_data;
|
||||
|
||||
case 0x05: // write data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto write_data;
|
||||
|
||||
case 0x09: // write deleted data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto write_deleted_data;
|
||||
|
||||
case 0x02: // read track
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto read_track;
|
||||
|
||||
case 0x0a: // read ID
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto read_id;
|
||||
|
||||
case 0x0d: // format track
|
||||
if(command_.size() < 6) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto format_track;
|
||||
|
||||
case 0x11: // scan low
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto scan_low;
|
||||
|
||||
case 0x19: // scan low or equal
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto scan_low_or_equal;
|
||||
|
||||
case 0x1d: // scan high or equal
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto scan_high_or_equal;
|
||||
|
||||
case 0x07: // recalibrate
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto recalibrate;
|
||||
|
||||
case 0x08: // sense interrupt status
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto sense_interrupt_status;
|
||||
|
||||
case 0x03: // specify
|
||||
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto specify;
|
||||
|
||||
case 0x04: // sense drive status
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto sense_drive_status;
|
||||
|
||||
case 0x0f: // seek
|
||||
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto seek;
|
||||
|
||||
default: // invalid
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
@ -253,6 +253,8 @@ void i8272::posit_event(int event_type) {
|
||||
sector_ = command_[4];
|
||||
size_ = command_[5];
|
||||
|
||||
read_next_data:
|
||||
|
||||
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
|
||||
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||
// values in the internal registers.
|
||||
@ -277,15 +279,21 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
main_status_ |= StatusRQM | StatusDIO;
|
||||
main_status_ |= StatusRequest | StatusDirection;
|
||||
WAIT_FOR_EVENT(Event8272::ResultEmpty);
|
||||
main_status_ &= ~StatusRQM;
|
||||
main_status_ &= ~StatusRequest;
|
||||
if(distance_into_section_ < (128 << size_)) goto get_byte;
|
||||
|
||||
// read CRC, without transferring it
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
|
||||
// check whether that's it
|
||||
if(sector_ != command_[6]) {
|
||||
sector_++;
|
||||
goto read_next_data;
|
||||
}
|
||||
|
||||
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
|
||||
goto post_st012chrn;
|
||||
|
||||
@ -416,8 +424,17 @@ void i8272::posit_event(int event_type) {
|
||||
goto wait_for_command;
|
||||
|
||||
sense_drive_status:
|
||||
printf("Sense drive status unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
result_stack_.push_back(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drives_[drive].drive->has_disk() ? 0x20 : 0x00) | // ready, approximately (TODO)
|
||||
0x40 // write protected
|
||||
);
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs any invalid command.
|
||||
invalid:
|
||||
@ -442,15 +459,15 @@ void i8272::posit_event(int event_type) {
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
main_status_ |= StatusRQM | StatusDIO;
|
||||
main_status_ &= ~StatusNDM;
|
||||
main_status_ |= StatusRequest | StatusDirection;
|
||||
main_status_ &= ~StatusNonDMAExecuting;
|
||||
|
||||
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
|
||||
// until the processor has read all result bytes.
|
||||
WAIT_FOR_EVENT(Event8272::ResultEmpty);
|
||||
|
||||
// Reset data direction and end the command.
|
||||
main_status_ &= ~StatusDIO;
|
||||
main_status_ &= ~StatusDirection;
|
||||
goto wait_for_command;
|
||||
|
||||
END_SECTION()
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "AmstradCPC.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
|
||||
#include "../../Components/6845/CRTC6845.hpp"
|
||||
@ -15,6 +17,9 @@
|
||||
#include "../../Components/8272/i8272.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
namespace AmstradCPC {
|
||||
@ -291,6 +296,7 @@ class CRTCBusHandler {
|
||||
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
|
||||
"}");
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
|
||||
crt_->set_output_device(Outputs::CRT::Monitor);
|
||||
}
|
||||
|
||||
/// Destructs the CRT.
|
||||
@ -522,19 +528,23 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
|
||||
*/
|
||||
class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
z80_(*this),
|
||||
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses
|
||||
crtc_(crtc_bus_handler_),
|
||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||
i8255_(i8255_port_handler_),
|
||||
i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_),
|
||||
tape_player_(8000000) {
|
||||
// primary clock is 4Mhz
|
||||
set_clock_rate(4000000);
|
||||
|
||||
// ensure memory starts in a random state
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
/// The entry point for performing a partial Z80 machine cycle.
|
||||
@ -556,12 +566,15 @@ class ConcreteMachine:
|
||||
// run_for as HalfCycles
|
||||
tape_player_.run_for(cycle.length.as_int());
|
||||
|
||||
// Pump the AY.
|
||||
// Pump the AY
|
||||
ay_.run_for(cycle.length);
|
||||
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||
|
||||
// Update typing activity
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
|
||||
// Stop now if no action is strictly required.
|
||||
if(!cycle.is_terminal()) return HalfCycles(0);
|
||||
|
||||
@ -579,48 +592,7 @@ class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
// Check for a gate array access.
|
||||
if((address & 0xc000) == 0x4000) {
|
||||
switch(*cycle.value >> 6) {
|
||||
case 0: crtc_bus_handler_.select_pen(*cycle.value & 0x1f); break;
|
||||
case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break;
|
||||
case 2:
|
||||
// Perform ROM paging.
|
||||
read_pointers_[0] = (*cycle.value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
|
||||
|
||||
upper_rom_is_paged_ = !(*cycle.value & 8);
|
||||
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
|
||||
|
||||
// Reset the interrupt timer if requested.
|
||||
if(*cycle.value & 0x10) interrupt_timer_.reset_count();
|
||||
|
||||
// Post the next mode.
|
||||
crtc_bus_handler_.set_next_mode(*cycle.value & 3);
|
||||
break;
|
||||
case 3:
|
||||
// Perform RAM paging, if 128kb is permitted.
|
||||
if(has_128k_) {
|
||||
bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0];
|
||||
bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3];
|
||||
#define RAM_BANK(x) &ram_[x * 16384]
|
||||
#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d);
|
||||
switch(*cycle.value & 7) {
|
||||
case 0: RAM_CONFIG(0, 1, 2, 3); break;
|
||||
case 1: RAM_CONFIG(0, 1, 2, 7); break;
|
||||
case 2: RAM_CONFIG(4, 5, 6, 7); break;
|
||||
case 3: RAM_CONFIG(0, 3, 2, 7); break;
|
||||
case 4: RAM_CONFIG(0, 4, 2, 3); break;
|
||||
case 5: RAM_CONFIG(0, 5, 2, 3); break;
|
||||
case 6: RAM_CONFIG(0, 6, 2, 3); break;
|
||||
case 7: RAM_CONFIG(0, 7, 2, 3); break;
|
||||
}
|
||||
#undef RAM_CONFIG
|
||||
#undef RAM_BANK
|
||||
if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0];
|
||||
read_pointers_[1] = write_pointers_[1];
|
||||
read_pointers_[2] = write_pointers_[2];
|
||||
if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3];
|
||||
}
|
||||
break;
|
||||
}
|
||||
write_to_gate_array(*cycle.value);
|
||||
}
|
||||
|
||||
// Check for an upper ROM selection
|
||||
@ -634,7 +606,7 @@ class ConcreteMachine:
|
||||
switch((address >> 8) & 3) {
|
||||
case 0: crtc_.select_register(*cycle.value); break;
|
||||
case 1: crtc_.set_register(*cycle.value); break;
|
||||
case 2: case 3: printf("Illegal CRTC write?\n"); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -657,23 +629,31 @@ class ConcreteMachine:
|
||||
// Default to nothing answering
|
||||
*cycle.value = 0xff;
|
||||
|
||||
// Check for a CRTC access
|
||||
if(!(address & 0x4000)) {
|
||||
switch((address >> 8) & 3) {
|
||||
case 0: case 1: printf("Illegal CRTC read?\n"); break;
|
||||
case 2: *cycle.value = crtc_.get_status(); break;
|
||||
case 3: *cycle.value = crtc_.get_register(); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a PIO access
|
||||
if(!(address & 0x800)) {
|
||||
*cycle.value = i8255_.get_register((address >> 8) & 3);
|
||||
*cycle.value &= i8255_.get_register((address >> 8) & 3);
|
||||
}
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
*cycle.value = fdc_.get_register(address & 1);
|
||||
*cycle.value &= fdc_.get_register(address & 1);
|
||||
}
|
||||
|
||||
// Check for a CRTC access; the below is not a typo — the CRTC can be selected
|
||||
// for writing via an input, and will sample whatever happens to be available
|
||||
if(!(address & 0x4000)) {
|
||||
switch((address >> 8) & 3) {
|
||||
case 0: crtc_.select_register(*cycle.value); break;
|
||||
case 1: crtc_.set_register(*cycle.value); break;
|
||||
case 2: *cycle.value &= crtc_.get_status(); break;
|
||||
case 3: *cycle.value &= crtc_.get_register(); break;
|
||||
}
|
||||
}
|
||||
|
||||
// As with the CRTC, the gate array will sample the bus if the address decoding
|
||||
// implies that it should, unaware of data direction
|
||||
if((address & 0xc000) == 0x4000) {
|
||||
write_to_gate_array(*cycle.value);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -774,6 +754,11 @@ class ConcreteMachine:
|
||||
c++;
|
||||
if(c == 4) break;
|
||||
}
|
||||
|
||||
// Type whatever is required.
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// See header; provides the system ROMs.
|
||||
@ -781,6 +766,21 @@ class ConcreteMachine:
|
||||
roms_[(int)type] = data;
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
void set_typer_for_string(const char *string) {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
||||
}
|
||||
|
||||
HalfCycles get_typer_delay() {
|
||||
return Cycles(4000000); // Wait 1 second before typing.
|
||||
}
|
||||
|
||||
HalfCycles get_typer_frequency() {
|
||||
return Cycles(80000); // Type one character per frame.
|
||||
}
|
||||
|
||||
// See header; sets a key as either pressed or released.
|
||||
void set_key_state(uint16_t key, bool isPressed) {
|
||||
int line = key >> 4;
|
||||
@ -794,6 +794,51 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
private:
|
||||
inline void write_to_gate_array(uint8_t value) {
|
||||
switch(value >> 6) {
|
||||
case 0: crtc_bus_handler_.select_pen(value & 0x1f); break;
|
||||
case 1: crtc_bus_handler_.set_colour(value & 0x1f); break;
|
||||
case 2:
|
||||
// Perform ROM paging.
|
||||
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
|
||||
|
||||
upper_rom_is_paged_ = !(value & 8);
|
||||
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
|
||||
|
||||
// Reset the interrupt timer if requested.
|
||||
if(value & 0x10) interrupt_timer_.reset_count();
|
||||
|
||||
// Post the next mode.
|
||||
crtc_bus_handler_.set_next_mode(value & 3);
|
||||
break;
|
||||
case 3:
|
||||
// Perform RAM paging, if 128kb is permitted.
|
||||
if(has_128k_) {
|
||||
bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0];
|
||||
bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3];
|
||||
#define RAM_BANK(x) &ram_[x * 16384]
|
||||
#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d);
|
||||
switch(value & 7) {
|
||||
case 0: RAM_CONFIG(0, 1, 2, 3); break;
|
||||
case 1: RAM_CONFIG(0, 1, 2, 7); break;
|
||||
case 2: RAM_CONFIG(4, 5, 6, 7); break;
|
||||
case 3: RAM_CONFIG(0, 3, 2, 7); break;
|
||||
case 4: RAM_CONFIG(0, 4, 2, 3); break;
|
||||
case 5: RAM_CONFIG(0, 5, 2, 3); break;
|
||||
case 6: RAM_CONFIG(0, 6, 2, 3); break;
|
||||
case 7: RAM_CONFIG(0, 7, 2, 3); break;
|
||||
}
|
||||
#undef RAM_CONFIG
|
||||
#undef RAM_BANK
|
||||
if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0];
|
||||
read_pointers_[1] = write_pointers_[1];
|
||||
read_pointers_[2] = write_pointers_[2];
|
||||
if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine> z80_;
|
||||
|
||||
CRTCBusHandler crtc_bus_handler_;
|
||||
@ -836,3 +881,5 @@ using namespace AmstradCPC;
|
||||
Machine *Machine::AmstradCPC() {
|
||||
return new AmstradCPC::ConcreteMachine;
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
@ -52,6 +52,8 @@ class Machine:
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates an returns an Amstrad CPC on the heap.
|
||||
static Machine *AmstradCPC();
|
||||
|
||||
|
89
Machines/AmstradCPC/CharacterMapper.cpp
Normal file
89
Machines/AmstradCPC/CharacterMapper.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "AmstradCPC.hpp"
|
||||
|
||||
using namespace AmstradCPC;
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
/* ACK */ X, /* BEL */ X,
|
||||
/* BS */ KEYS(KeyDelete), /* HT */ X,
|
||||
/* LF */ KEYS(KeyReturn), /* VT */ X,
|
||||
/* FF */ X, /* CR */ X,
|
||||
/* SO */ X, /* SI */ X,
|
||||
/* DLE */ X, /* DC1 */ X,
|
||||
/* DC2 */ X, /* DC3 */ X,
|
||||
/* DC4 */ X, /* NAK */ X,
|
||||
/* SYN */ X, /* ETB */ X,
|
||||
/* CAN */ X, /* EM */ X,
|
||||
/* SUB */ X, /* ESC */ X,
|
||||
/* FS */ X, /* GS */ X,
|
||||
/* RS */ X, /* US */ X,
|
||||
/* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
|
||||
/* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
|
||||
/* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
|
||||
/* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
|
||||
/* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
|
||||
/* * */ SHIFT(KeyColon), /* + */ SHIFT(KeySemicolon),
|
||||
/* , */ KEYS(KeyComma), /* - */ KEYS(KeyMinus),
|
||||
/* . */ KEYS(KeyFullStop), /* / */ KEYS(KeyForwardSlash),
|
||||
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
|
||||
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
|
||||
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
|
||||
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
|
||||
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
|
||||
/* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
|
||||
/* < */ SHIFT(KeyComma), /* = */ SHIFT(KeyMinus),
|
||||
/* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeyForwardSlash),
|
||||
/* @ */ SHIFT(KeyAt), /* A */ SHIFT(KeyA),
|
||||
/* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC),
|
||||
/* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE),
|
||||
/* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG),
|
||||
/* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI),
|
||||
/* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK),
|
||||
/* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM),
|
||||
/* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO),
|
||||
/* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ),
|
||||
/* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS),
|
||||
/* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU),
|
||||
/* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW),
|
||||
/* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY),
|
||||
/* Z */ SHIFT(KeyZ), /* [ */ KEYS(KeyLeftSquareBracket),
|
||||
/* \ */ KEYS(KeyBackSlash), /* ] */ KEYS(KeyRightSquareBracket),
|
||||
/* ^ */ SHIFT(KeyCaret), /* _ */ SHIFT(Key0),
|
||||
/* ` */ X, /* a */ KEYS(KeyA),
|
||||
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
|
||||
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
|
||||
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
|
||||
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
|
||||
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
|
||||
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
|
||||
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
|
||||
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
|
||||
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
|
||||
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
/* z */ KEYS(KeyZ), /* { */ X,
|
||||
/* | */ SHIFT(KeyAt), /* } */ X,
|
||||
/* ~ */ X
|
||||
};
|
||||
#undef KEYS
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
}
|
23
Machines/AmstradCPC/CharacterMapper.hpp
Normal file
23
Machines/AmstradCPC/CharacterMapper.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_AmstradCPC_CharacterMapper_hpp
|
||||
#define Machines_AmstradCPC_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CharacterMapper_hpp */
|
@ -51,7 +51,7 @@ bool Typer::try_type_next_character() {
|
||||
if(!phase_) delegate_->clear_all_keys();
|
||||
else {
|
||||
delegate_->set_key_state(sequence[phase_ - 1], true);
|
||||
return sequence[phase_] == CharacterMapper::EndSequence;
|
||||
return sequence[phase_] != CharacterMapper::EndSequence;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -6,8 +6,8 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CharacterMapper_hpp
|
||||
#define CharacterMapper_hpp
|
||||
#ifndef Machines_ZX8081_CharacterMapper_hpp
|
||||
#define Machines_ZX8081_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
|
@ -384,3 +384,5 @@ using namespace ZX8081;
|
||||
Machine *Machine::ZX8081() {
|
||||
return new ZX8081::ConcreteMachine;
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
@ -39,6 +39,7 @@ class Machine:
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
static Machine *ZX8081();
|
||||
virtual ~Machine();
|
||||
|
||||
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
|
||||
|
||||
|
@ -58,6 +58,7 @@
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; };
|
||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; };
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
|
||||
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
|
||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; };
|
||||
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
|
||||
@ -119,6 +120,7 @@
|
||||
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; };
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; };
|
||||
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; };
|
||||
4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; };
|
||||
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
|
||||
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
|
||||
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
|
||||
@ -562,6 +564,8 @@
|
||||
4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; };
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
|
||||
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; };
|
||||
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
|
||||
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
|
||||
@ -667,6 +671,8 @@
|
||||
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; };
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; };
|
||||
4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = AmstradCPC/CharacterMapper.cpp; sourceTree = "<group>"; };
|
||||
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = "<group>"; };
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; };
|
||||
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
|
||||
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
|
||||
@ -1294,6 +1300,8 @@
|
||||
children = (
|
||||
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */,
|
||||
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */,
|
||||
4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */,
|
||||
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */,
|
||||
);
|
||||
name = AmstradCPC;
|
||||
sourceTree = "<group>";
|
||||
@ -1330,6 +1338,15 @@
|
||||
path = Bridges;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B3FE75F1F3CF6BA00448EE4 /* Parsers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */,
|
||||
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */,
|
||||
);
|
||||
name = Parsers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B4A762D1DB1A35C007AAE2E /* AY38910 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1446,12 +1463,12 @@
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */,
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
|
||||
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */,
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
|
||||
4B69FB411C4D941400B5F0AA /* Formats */,
|
||||
4B8805F11DCFC9A2003085B1 /* Parsers */,
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */,
|
||||
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */,
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
|
||||
);
|
||||
path = Tape;
|
||||
sourceTree = "<group>";
|
||||
@ -1545,6 +1562,7 @@
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */,
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
4B3FE75F1F3CF6BA00448EE4 /* Parsers */,
|
||||
);
|
||||
path = Disk;
|
||||
sourceTree = "<group>";
|
||||
@ -2700,6 +2718,7 @@
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
@ -2710,6 +2729,7 @@
|
||||
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */,
|
||||
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
|
||||
4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */,
|
||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */,
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
|
||||
|
@ -116,6 +116,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
|
||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
||||
self.machine->setup_output(aspectRatio);
|
||||
|
||||
// Since OS X v10.6, Macs have had a gamma of 2.2.
|
||||
self.machine->get_crt()->set_output_gamma(2.2f);
|
||||
}
|
||||
|
||||
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty {
|
||||
|
@ -56,10 +56,12 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display
|
||||
switch(displayType) {
|
||||
case DisplayType::PAL50:
|
||||
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync
|
||||
set_input_gamma(2.8f);
|
||||
break;
|
||||
|
||||
case DisplayType::NTSC60:
|
||||
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync
|
||||
set_input_gamma(2.2f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -72,6 +74,21 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_
|
||||
}
|
||||
}
|
||||
|
||||
void CRT::set_input_gamma(float gamma) {
|
||||
input_gamma_ = gamma;
|
||||
update_gamma();
|
||||
}
|
||||
|
||||
void CRT::set_output_gamma(float gamma) {
|
||||
output_gamma_ = gamma;
|
||||
update_gamma();
|
||||
}
|
||||
|
||||
void CRT::update_gamma() {
|
||||
float gamma_ratio = input_gamma_ / output_gamma_;
|
||||
openGL_output_builder_.set_gamma(gamma_ratio);
|
||||
}
|
||||
|
||||
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
|
||||
is_receiving_sync_(false),
|
||||
common_output_divisor_(common_output_divisor),
|
||||
|
@ -97,6 +97,9 @@ class CRT {
|
||||
|
||||
unsigned int cycles_per_line_;
|
||||
|
||||
float input_gamma_, output_gamma_;
|
||||
void update_gamma();
|
||||
|
||||
public:
|
||||
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
|
||||
The requested number of buffers, each with the requested number of bytes per pixel,
|
||||
@ -238,6 +241,12 @@ class CRT {
|
||||
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
|
||||
}
|
||||
|
||||
/*! Sets the gamma exponent for the simulated screen. */
|
||||
void set_input_gamma(float gamma);
|
||||
|
||||
/*! Sets the gamma exponent for the real, tangible screen on which content will be drawn. */
|
||||
void set_output_gamma(float gamma);
|
||||
|
||||
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
|
||||
the previous.
|
||||
|
||||
|
@ -89,6 +89,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
|
||||
set_timing_uniforms();
|
||||
set_colour_space_uniforms();
|
||||
set_gamma();
|
||||
}
|
||||
|
||||
if(fence_ != nullptr) {
|
||||
@ -375,6 +376,10 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() {
|
||||
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_gamma() {
|
||||
if(output_shader_program_) output_shader_program_->set_gamma_ratio(gamma_);
|
||||
}
|
||||
|
||||
float OpenGLOutputBuilder::get_composite_output_width() const {
|
||||
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class OpenGLOutputBuilder {
|
||||
unsigned int colour_cycle_numerator_;
|
||||
unsigned int colour_cycle_denominator_;
|
||||
OutputDevice output_device_;
|
||||
float gamma_;
|
||||
|
||||
// timing information to allow reasoning about input information
|
||||
unsigned int input_frequency_;
|
||||
@ -89,6 +90,7 @@ class OpenGLOutputBuilder {
|
||||
|
||||
void set_timing_uniforms();
|
||||
void set_colour_space_uniforms();
|
||||
void set_gamma();
|
||||
|
||||
void establish_OpenGL_state();
|
||||
void reset_all_OpenGL_state();
|
||||
@ -118,6 +120,11 @@ class OpenGLOutputBuilder {
|
||||
visible_area_ = visible_area;
|
||||
}
|
||||
|
||||
inline void set_gamma(float gamma) {
|
||||
gamma_ = gamma;
|
||||
set_gamma();
|
||||
}
|
||||
|
||||
inline std::unique_lock<std::mutex> get_output_lock() {
|
||||
return std::unique_lock<std::mutex>(output_mutex_);
|
||||
}
|
||||
|
@ -74,12 +74,13 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
"out vec4 fragColour;"
|
||||
|
||||
"uniform %s texID;"
|
||||
"uniform float gamma;"
|
||||
|
||||
"\n%s\n"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"fragColour = vec4(%s, 0.5);"//*cos(lateralVarying)
|
||||
"fragColour = vec4(pow(%s, vec3(gamma)), 0.5);"//*cos(lateralVarying)
|
||||
"}",
|
||||
sampler_type, fragment_methods, colour_expression);
|
||||
|
||||
@ -116,6 +117,10 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
|
||||
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
|
||||
}
|
||||
|
||||
void OutputShader::set_gamma_ratio(float ratio) {
|
||||
set_uniform("gamma", ratio);
|
||||
}
|
||||
|
||||
void OutputShader::set_input_width_scaler(float input_scaler) {
|
||||
set_uniform("inputScaler", input_scaler);
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ public:
|
||||
*/
|
||||
void set_origin_is_double_height(bool is_double_height);
|
||||
|
||||
void set_gamma_ratio(float ratio);
|
||||
|
||||
/*!
|
||||
Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available
|
||||
space, 0.5 means use half, etc.
|
||||
|
@ -9,9 +9,10 @@ So its aims are:
|
||||
|
||||
It currently contains emulations of the:
|
||||
* Acorn Electron;
|
||||
* Amstrad CPC;
|
||||
* Atari 2600;
|
||||
* Oric 1/Atmos;
|
||||
* Commodore Vic-20 (and Commodore 1540/1); and
|
||||
* Commodore Vic-20 (and Commodore 1540/1);
|
||||
* Oric 1/Atmos; and
|
||||
* Sinclair ZX80/81.
|
||||
|
||||
## Single-click Loading
|
||||
|
@ -19,8 +19,8 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 1);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0, 0);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 0, 1);
|
||||
|
||||
if(!names || !details) return nullptr;
|
||||
if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
|
||||
@ -61,7 +61,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
||||
uint8_t track = (uint8_t)(start_sector / 10);
|
||||
start_sector++;
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(track, sector);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
@ -77,13 +77,13 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
|
||||
}
|
||||
|
@ -7,6 +7,50 @@
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "../../Storage/Disk/Parsers/CPM.hpp"
|
||||
|
||||
static void InspectDataCatalogue(
|
||||
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &data_catalogue,
|
||||
StaticAnalyser::Target &target) {
|
||||
// If there's just one file, run that.
|
||||
if(data_catalogue->files.size() == 1) {
|
||||
target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one file is [potentially] BASIC, run that one; otherwise if only one has no suffix,
|
||||
// pick that one.
|
||||
int basic_files = 0;
|
||||
int nonsuffixed_files = 0;
|
||||
size_t last_basic_file = 0;
|
||||
size_t last_nonsuffixed_file = 0;
|
||||
for(size_t c = 0; c < data_catalogue->files.size(); c++) {
|
||||
// Check for whether this is [potentially] BASIC.
|
||||
if(!((data_catalogue->files[c].data[18] >> 1) & 7)) {
|
||||
basic_files++;
|
||||
last_basic_file = c;
|
||||
}
|
||||
|
||||
// Check suffix for emptiness.
|
||||
if(data_catalogue->files[c].type == " ") {
|
||||
nonsuffixed_files++;
|
||||
last_nonsuffixed_file = c;
|
||||
}
|
||||
}
|
||||
if(basic_files == 1 || nonsuffixed_files == 1) {
|
||||
size_t selected_file = (basic_files == 1) ? last_basic_file : last_nonsuffixed_file;
|
||||
target.loadingCommand = "run\"" + data_catalogue->files[selected_file].name + "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Desperation.
|
||||
target.loadingCommand = "cat\n";
|
||||
}
|
||||
|
||||
static void InspectSystemCatalogue(
|
||||
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &data_catalogue,
|
||||
StaticAnalyser::Target &target) {
|
||||
}
|
||||
|
||||
void StaticAnalyser::AmstradCPC::AddTargets(
|
||||
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
|
||||
@ -20,7 +64,42 @@ void StaticAnalyser::AmstradCPC::AddTargets(
|
||||
target.tapes = tapes;
|
||||
target.cartridges = cartridges;
|
||||
|
||||
target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC6128;
|
||||
target.amstradcpc.model = AmstradCPCModel::CPC6128;
|
||||
|
||||
if(!target.tapes.empty()) {
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target.loadingCommand = "|tape\nrun\"\n1234567890";
|
||||
}
|
||||
|
||||
if(!target.disks.empty()) {
|
||||
Storage::Disk::CPM::ParameterBlock data_format;
|
||||
data_format.sectors_per_track = 9;
|
||||
data_format.tracks = 40;
|
||||
data_format.block_size = 1024;
|
||||
data_format.first_sector = 0xc1;
|
||||
data_format.catalogue_allocation_bitmap = 0xc000;
|
||||
data_format.reserved_tracks = 0;
|
||||
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), data_format);
|
||||
if(data_catalogue) {
|
||||
InspectDataCatalogue(data_catalogue, target);
|
||||
} else {
|
||||
Storage::Disk::CPM::ParameterBlock system_format;
|
||||
data_format.sectors_per_track = 9;
|
||||
data_format.tracks = 40;
|
||||
data_format.block_size = 1024;
|
||||
data_format.first_sector = 0x41;
|
||||
data_format.catalogue_allocation_bitmap = 0xc000;
|
||||
data_format.reserved_tracks = 2;
|
||||
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), system_format);
|
||||
if(system_catalogue) {
|
||||
InspectSystemCatalogue(data_catalogue, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destination.push_back(target);
|
||||
}
|
||||
|
@ -46,6 +46,11 @@ bool Drive::get_is_read_only() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Drive::get_is_ready() {
|
||||
// TODO: a real test for this.
|
||||
return disk_ != nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Drive::get_track() {
|
||||
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
|
||||
if(track_) return track_;
|
||||
|
@ -65,6 +65,11 @@ class Drive {
|
||||
*/
|
||||
void set_track(const std::shared_ptr<Track> &track);
|
||||
|
||||
/*!
|
||||
@returns @c true if the drive is ready; @c false otherwise.
|
||||
*/
|
||||
bool get_is_ready();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Track> track_;
|
||||
std::shared_ptr<Disk> disk_;
|
||||
|
@ -114,22 +114,12 @@ class FMEncoder: public Encoder {
|
||||
}
|
||||
};
|
||||
|
||||
static uint8_t logarithmic_size_for_size(size_t size) {
|
||||
switch(size) {
|
||||
default: return 0;
|
||||
case 256: return 1;
|
||||
case 512: return 2;
|
||||
case 1024: return 3;
|
||||
case 2048: return 4;
|
||||
case 4196: return 5;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
GetTrackWithSectors(
|
||||
const std::vector<Sector> §ors,
|
||||
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
|
||||
size_t pre_address_mark_bytes, size_t post_address_mark_bytes,
|
||||
size_t pre_address_mark_bytes,
|
||||
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
|
||||
size_t pre_data_mark_bytes, size_t post_data_bytes,
|
||||
size_t inter_sector_gap,
|
||||
size_t expected_track_bytes) {
|
||||
@ -153,21 +143,30 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
shifter.add_byte(sector.track);
|
||||
shifter.add_byte(sector.side);
|
||||
shifter.add_byte(sector.sector);
|
||||
uint8_t size = logarithmic_size_for_size(sector.data.size());
|
||||
shifter.add_byte(size);
|
||||
shifter.add_crc();
|
||||
shifter.add_byte(sector.size);
|
||||
shifter.add_crc(sector.has_header_crc_error);
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e);
|
||||
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
|
||||
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// data
|
||||
shifter.add_data_address_mark();
|
||||
for(size_t c = 0; c < sector.data.size(); c++)
|
||||
{
|
||||
shifter.add_byte(sector.data[c]);
|
||||
// data, if attached
|
||||
if(!sector.data.empty()) {
|
||||
if(sector.is_deleted)
|
||||
shifter.add_deleted_data_address_mark();
|
||||
else
|
||||
shifter.add_data_address_mark();
|
||||
|
||||
size_t c = 0;
|
||||
size_t declared_length = (size_t)(128 << sector.size);
|
||||
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
|
||||
shifter.add_byte(sector.data[c]);
|
||||
}
|
||||
for(; c < declared_length; c++) {
|
||||
shifter.add_byte(0x00);
|
||||
}
|
||||
shifter.add_crc(sector.has_data_crc_error);
|
||||
}
|
||||
shifter.add_crc();
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00);
|
||||
@ -189,28 +188,32 @@ void Encoder::output_short(uint16_t value) {
|
||||
target_.push_back(value & 0xff);
|
||||
}
|
||||
|
||||
void Encoder::add_crc() {
|
||||
void Encoder::add_crc(bool incorrectly) {
|
||||
uint16_t crc_value = crc_generator_.get_value();
|
||||
add_byte(crc_value >> 8);
|
||||
add_byte(crc_value & 0xff);
|
||||
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors) {
|
||||
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<FMEncoder>(
|
||||
sectors,
|
||||
16, 0x00,
|
||||
6, 0,
|
||||
17, 14,
|
||||
6,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 0, sector_gap_filler_byte,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? 0 : 17, 14,
|
||||
0,
|
||||
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> §ors) {
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<MFMEncoder>(
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
12, 22,
|
||||
12, 18,
|
||||
12,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 22, sector_gap_filler_byte,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? 0 : 12, 18,
|
||||
32,
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
|
||||
}
|
||||
@ -228,25 +231,26 @@ std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8
|
||||
Parser::Parser(bool is_mfm) :
|
||||
Storage::Disk::Controller(4000000, 1, 300),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
shift_register_(0), track_(0), is_mfm_(is_mfm) {
|
||||
shift_register_(0), is_mfm_(is_mfm),
|
||||
track_(0), head_(0) {
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
drive.reset(new Storage::Disk::Drive);
|
||||
set_drive(drive);
|
||||
drive_.reset(new Storage::Disk::Drive);
|
||||
set_drive(drive_);
|
||||
set_motor_on(true);
|
||||
}
|
||||
|
||||
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
|
||||
Parser(is_mfm) {
|
||||
drive->set_disk(disk);
|
||||
drive_->set_disk(disk);
|
||||
}
|
||||
|
||||
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
|
||||
Parser(is_mfm) {
|
||||
drive->set_disk_with_track(track);
|
||||
drive_->set_disk_with_track(track);
|
||||
}
|
||||
|
||||
void Parser::seek_to_track(uint8_t track) {
|
||||
@ -261,7 +265,20 @@ void Parser::seek_to_track(uint8_t track) {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> Parser::get_sector(uint8_t track, uint8_t sector) {
|
||||
std::shared_ptr<Sector> Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) {
|
||||
// Check cache for sector.
|
||||
int index = get_index(head, track, sector);
|
||||
auto cached_sector = sectors_by_index_.find(index);
|
||||
if(cached_sector != sectors_by_index_.end()) {
|
||||
return cached_sector->second;
|
||||
}
|
||||
|
||||
// Failing that, set the proper head and track, and search for the sector. get_sector automatically
|
||||
// inserts everything found into sectors_by_index_.
|
||||
if(head_ != head) {
|
||||
drive_->set_head(head);
|
||||
invalidate_track();
|
||||
}
|
||||
seek_to_track(track);
|
||||
return get_sector(sector);
|
||||
}
|
||||
@ -384,8 +401,7 @@ std::vector<uint8_t> Parser::get_track() {
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<Sector> Parser::get_next_sector()
|
||||
{
|
||||
std::shared_ptr<Sector> Parser::get_next_sector() {
|
||||
std::shared_ptr<Sector> sector(new Sector);
|
||||
index_count_ = 0;
|
||||
|
||||
@ -455,6 +471,10 @@ std::shared_ptr<Sector> Parser::get_next_sector()
|
||||
if((data_crc >> 8) != get_next_byte()) continue;
|
||||
if((data_crc & 0xff) != get_next_byte()) continue;
|
||||
|
||||
// Put this sector into the cache.
|
||||
int index = get_index(head_, track_, sector->sector);
|
||||
sectors_by_index_[index] = sector;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
@ -465,7 +485,7 @@ std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
|
||||
std::shared_ptr<Sector> first_sector;
|
||||
index_count_ = 0;
|
||||
while(!first_sector && index_count_ < 2) first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(!first_sector) return nullptr;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1) {
|
||||
@ -475,3 +495,7 @@ std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) {
|
||||
return head | (track << 8) | (sector << 16);
|
||||
}
|
||||
|
@ -36,13 +36,39 @@ const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator sho
|
||||
const uint8_t MFMIndexSyncByteValue = 0xc2;
|
||||
const uint8_t MFMSyncByteValue = 0xa1;
|
||||
|
||||
/*!
|
||||
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
|
||||
and a few extra flags of metadata.
|
||||
*/
|
||||
struct Sector {
|
||||
uint8_t track, side, sector;
|
||||
uint8_t track, side, sector, size;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool has_data_crc_error;
|
||||
bool has_header_crc_error;
|
||||
bool is_deleted;
|
||||
|
||||
Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {}
|
||||
};
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> §ors);
|
||||
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> §ors);
|
||||
extern const size_t DefaultSectorGapLength;
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded MFM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded FM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
@ -53,7 +79,9 @@ class Encoder {
|
||||
virtual void add_data_address_mark() = 0;
|
||||
virtual void add_deleted_data_address_mark() = 0;
|
||||
virtual void output_short(uint16_t value);
|
||||
void add_crc();
|
||||
|
||||
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
|
||||
void add_crc(bool incorrectly);
|
||||
|
||||
protected:
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
@ -75,7 +103,7 @@ class Parser: public Storage::Disk::Controller {
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t track, uint8_t sector);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t head, uint8_t track, uint8_t sector);
|
||||
|
||||
/*!
|
||||
Attempts to read the track at @c track, starting from the index hole.
|
||||
@ -92,10 +120,10 @@ class Parser: public Storage::Disk::Controller {
|
||||
private:
|
||||
Parser(bool is_mfm);
|
||||
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
uint8_t track_;
|
||||
uint8_t track_, head_;
|
||||
int bit_count_;
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
bool is_mfm_;
|
||||
@ -110,6 +138,9 @@ class Parser: public Storage::Disk::Controller {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector();
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
|
||||
std::vector<uint8_t> get_track();
|
||||
|
||||
std::map<int, std::shared_ptr<Storage::Encodings::MFM::Sector>> sectors_by_index_;
|
||||
int get_index(uint8_t head, uint8_t track, uint8_t sector);
|
||||
};
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
namespace {
|
||||
static const unsigned int sectors_per_track = 16;
|
||||
static const unsigned int bytes_per_sector = 256;
|
||||
static const unsigned int sector_size = 1;
|
||||
}
|
||||
|
||||
using namespace Storage::Disk;
|
||||
@ -69,6 +70,7 @@ std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int hea
|
||||
new_sector.track = (uint8_t)position;
|
||||
new_sector.side = (uint8_t)head;
|
||||
new_sector.sector = (uint8_t)sector;
|
||||
new_sector.size = sector_size;
|
||||
|
||||
new_sector.data.resize(bytes_per_sector);
|
||||
fread(&new_sector.data[0], 1, bytes_per_sector, file_);
|
||||
@ -87,7 +89,7 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p
|
||||
std::vector<uint8_t> parsed_track;
|
||||
Storage::Encodings::MFM::Parser parser(true, track);
|
||||
for(unsigned int c = 0; c < sectors_per_track; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
|
||||
if(sector) {
|
||||
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
|
||||
} else {
|
||||
|
@ -75,8 +75,8 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
|
||||
// Grab the track information.
|
||||
fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector
|
||||
int number_of_sectors = fgetc(file_);
|
||||
__unused uint8_t gap3_length = (uint8_t)fgetc(file_);
|
||||
__unused uint8_t filler_byte = (uint8_t)fgetc(file_);
|
||||
uint8_t gap3_length = (uint8_t)fgetc(file_);
|
||||
uint8_t filler_byte = (uint8_t)fgetc(file_);
|
||||
|
||||
// Grab the sector information
|
||||
struct SectorInfo {
|
||||
@ -111,6 +111,7 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
|
||||
new_sector.track = sector_info.track;
|
||||
new_sector.side = sector_info.side;
|
||||
new_sector.sector = sector_info.sector;
|
||||
new_sector.size = sector_info.length;
|
||||
|
||||
size_t data_size;
|
||||
if(is_extended_) {
|
||||
@ -122,32 +123,34 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
|
||||
new_sector.data.resize(data_size);
|
||||
fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_);
|
||||
|
||||
// TODO: obey the status bytes, somehow (?)
|
||||
if(sector_info.status1 || sector_info.status2) {
|
||||
if(sector_info.status1 & 0x08) {
|
||||
// The CRC failed in the ID field.
|
||||
}
|
||||
if(sector_info.status1 & 0x08) {
|
||||
// The CRC failed in the ID field.
|
||||
new_sector.has_header_crc_error = true;
|
||||
}
|
||||
|
||||
if(sector_info.status2 & 0x20) {
|
||||
// The CRC failed in the data field.
|
||||
}
|
||||
if(sector_info.status2 & 0x20) {
|
||||
// The CRC failed in the data field.
|
||||
new_sector.has_data_crc_error = true;
|
||||
}
|
||||
|
||||
if(sector_info.status2 & 0x40) {
|
||||
// This sector is marked as deleted.
|
||||
}
|
||||
if(sector_info.status2 & 0x40) {
|
||||
// This sector is marked as deleted.
|
||||
new_sector.is_deleted = true;
|
||||
}
|
||||
|
||||
if(sector_info.status2 & 0x01) {
|
||||
// Data field wasn't found.
|
||||
}
|
||||
|
||||
printf("Unhandled: status errors\n");
|
||||
if(sector_info.status2 & 0x01) {
|
||||
// Data field wasn't found.
|
||||
new_sector.data.clear();
|
||||
}
|
||||
|
||||
sectors.push_back(std::move(new_sector));
|
||||
}
|
||||
|
||||
// TODO: supply gay 3 length and filler byte
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
||||
// TODO: extensions to the extended format; John Elliot's addition of single-density support,
|
||||
// and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential
|
||||
// FM/MFM encoding that can produce specified weak values.
|
||||
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, un
|
||||
new_sector.track = (uint8_t)position;
|
||||
new_sector.side = 0;
|
||||
new_sector.sector = (uint8_t)sector;
|
||||
new_sector.size = 1;
|
||||
|
||||
new_sector.data.resize(256);
|
||||
fread(new_sector.data.data(), 1, 256, file_);
|
||||
@ -81,7 +82,7 @@ void SSD::store_updated_track_at_position(unsigned int head, unsigned int positi
|
||||
std::vector<uint8_t> parsed_track;
|
||||
Storage::Encodings::MFM::Parser parser(false, track);
|
||||
for(unsigned int c = 0; c < 10; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
|
||||
if(sector) {
|
||||
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
|
||||
} else {
|
||||
|
120
Storage/Disk/Parsers/CPM.cpp
Normal file
120
Storage/Disk/Parsers/CPM.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
//
|
||||
// CPM.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CPM.hpp"
|
||||
|
||||
#include "../Encodings/MFM.hpp"
|
||||
|
||||
using namespace Storage::Disk::CPM;
|
||||
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock ¶meters) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
// Assemble the actual bytes of the catalogue.
|
||||
std::vector<uint8_t> catalogue;
|
||||
size_t sector_size = 1;
|
||||
uint16_t catalogue_allocation_bitmap = parameters.catalogue_allocation_bitmap;
|
||||
if(!catalogue_allocation_bitmap) return nullptr;
|
||||
int sector = 0;
|
||||
int track = parameters.reserved_tracks;
|
||||
while(catalogue_allocation_bitmap) {
|
||||
if(catalogue_allocation_bitmap & 0x8000) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
if(!sector_contents) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end());
|
||||
sector_size = sector_contents->data.size();
|
||||
}
|
||||
|
||||
catalogue_allocation_bitmap <<= 1;
|
||||
|
||||
sector++;
|
||||
if(sector == parameters.sectors_per_track) {
|
||||
sector = 0;
|
||||
track++;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Catalogue> result(new Catalogue);
|
||||
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
|
||||
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
|
||||
|
||||
// From the catalogue, create files.
|
||||
std::map<std::vector<uint8_t>, size_t> indices_by_name;
|
||||
File empty_file;
|
||||
for(size_t c = 0; c < catalogue.size(); c += 32) {
|
||||
// Skip this file if it's deleted; this is marked by it having 0xe5 as its user number
|
||||
if(catalogue[c] == 0xe5) continue;
|
||||
|
||||
// Check whether this file has yet been seen; if not then add it to the list
|
||||
std::vector<uint8_t> descriptor;
|
||||
size_t index;
|
||||
descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]);
|
||||
auto iterator = indices_by_name.find(descriptor);
|
||||
if(iterator != indices_by_name.end()) {
|
||||
index = iterator->second;
|
||||
} else {
|
||||
File new_file;
|
||||
new_file.user_number = catalogue[c];
|
||||
for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]);
|
||||
for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f);
|
||||
new_file.read_only = catalogue[c + 9] & 0x80;
|
||||
new_file.system = catalogue[c + 10] & 0x80;
|
||||
index = result->files.size();
|
||||
result->files.push_back(new_file);
|
||||
indices_by_name[descriptor] = index;
|
||||
}
|
||||
|
||||
// figure out where this data needs to be pasted in
|
||||
size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
|
||||
int number_of_records = catalogue[c + 15];
|
||||
|
||||
size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128;
|
||||
if(result->files[index].data.size() < required_size) {
|
||||
result->files[index].data.resize(required_size);
|
||||
}
|
||||
|
||||
int sectors_per_block = parameters.block_size / (int)sector_size;
|
||||
int records_per_sector = (int)sector_size / 128;
|
||||
int record = 0;
|
||||
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) {
|
||||
int block_number;
|
||||
if(has_long_allocation_units) {
|
||||
block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8);
|
||||
} else {
|
||||
block_number = catalogue[c + 16 + block];
|
||||
}
|
||||
if(!block_number) {
|
||||
record += parameters.block_size / 128;
|
||||
continue;
|
||||
}
|
||||
int first_sector = block_number * sectors_per_block;
|
||||
|
||||
sector = first_sector % parameters.sectors_per_track;
|
||||
track = first_sector / parameters.sectors_per_track;
|
||||
|
||||
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
if(!sector_contents) break;
|
||||
sector++;
|
||||
if(sector == parameters.sectors_per_track) {
|
||||
sector = 0;
|
||||
track++;
|
||||
}
|
||||
|
||||
int records_to_copy = std::min(number_of_records - record, records_per_sector);
|
||||
memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
|
||||
record += records_to_copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
51
Storage/Disk/Parsers/CPM.hpp
Normal file
51
Storage/Disk/Parsers/CPM.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// CPM.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Disk_Parsers_CPM_hpp
|
||||
#define Storage_Disk_Parsers_CPM_hpp
|
||||
|
||||
#include "../Disk.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
namespace CPM {
|
||||
|
||||
struct ParameterBlock {
|
||||
int sectors_per_track;
|
||||
int tracks;
|
||||
int block_size;
|
||||
int first_sector;
|
||||
uint16_t catalogue_allocation_bitmap;
|
||||
int reserved_tracks;
|
||||
};
|
||||
|
||||
struct File {
|
||||
uint8_t user_number;
|
||||
std::string name;
|
||||
std::string type;
|
||||
bool read_only;
|
||||
bool system;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct Catalogue {
|
||||
std::vector<File> files;
|
||||
};
|
||||
|
||||
std::unique_ptr<Catalogue> GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock ¶meters);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Storage_Disk_Parsers_CPM_hpp */
|
Loading…
Reference in New Issue
Block a user