From a9e65e9b7a3dcf4aaf517c3fcad79ea365a469ae Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Mon, 19 Sep 2016 22:06:56 -0400 Subject: [PATCH] Tweaked disk side density, added call-outs to a WD1770 if the Electron had one (albeit without `run_for_cycles` yet as I need to figure out the clock rate), added a shell of the basic functions of the WD1770. No implementation yet. --- Components/1770/1770.cpp | 23 ++ Components/1770/1770.hpp | 5 +- Machines/Electron/Electron.cpp | 530 +++++++++++++++++---------------- Machines/Electron/Electron.hpp | 4 + StaticAnalyser/Acorn/Disk.cpp | 2 +- Storage/Disk/Encodings/MFM.cpp | 4 +- 6 files changed, 308 insertions(+), 260 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 2c7ba3426..62aa5f5e3 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -7,3 +7,26 @@ // #include "1770.hpp" + +using namespace WD; + +void WD1770::set_drive(std::shared_ptr<Storage::Disk::Drive> drive) +{ +} + +void WD1770::set_is_double_density(bool is_double_density) +{ +} + +void WD1770::set_register(int address, uint8_t value) +{ +} + +uint8_t WD1770::get_register(int address) +{ + return 0; +} + +void WD1770::run_for_cycles(unsigned int number_of_cycles) +{ +} diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 32941fc1d..48187087e 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -15,11 +15,12 @@ namespace WD { class WD1770 { public: - void set_drive(std::shared_ptr<Storage::Disk::Drive> drive); void set_is_double_density(bool is_double_density); void set_register(int address, uint8_t value); - void get_register(int address); + uint8_t get_register(int address); + + void run_for_cycles(unsigned int number_of_cycles); }; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 795f288f8..ec44dd248 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -120,276 +120,296 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(address >= 0xc000) + switch(address & 0xff0f) { - if((address & 0xff00) == 0xfe00) - { - switch(address&0xf) - { - case 0x0: - if(isReadOperation(operation)) - { - *value = _interrupt_status; - _interrupt_status &= ~PowerOnReset; - } - else - { - _interrupt_control = (*value) & ~1; - evaluate_interrupts(); - } - break; - case 0x1: - break; - case 0x2: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x3: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x4: - if(isReadOperation(operation)) - { - *value = _tape.get_data_register(); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - } - else - { - _tape.set_data_register(*value); - _tape.clear_interrupts(Interrupt::TransmitDataEmpty); - } - break; - case 0x5: - if(!isReadOperation(operation)) - { - const uint8_t interruptDisable = (*value)&0xf0; - if( interruptDisable ) - { - if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; - if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; - if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; - evaluate_interrupts(); - - // TODO: NMI - } - - // latch the paged ROM in case external hardware is being emulated - _active_rom = (Electron::ROMSlot)(*value & 0xf); - - // apply the ULA's test - if(*value & 0x08) - { - if(*value & 0x04) - { - _keyboard_is_active = false; - _basic_is_active = false; - } - else - { - _keyboard_is_active = !(*value & 0x02); - _basic_is_active = !_keyboard_is_active; - } - } - } - break; - case 0x6: - if(!isReadOperation(operation)) - { - update_audio(); - _speaker->set_divider(*value); - _tape.set_counter(*value); - } - break; - case 0x7: - if(!isReadOperation(operation)) - { - // update screen mode - uint8_t new_screen_mode = ((*value) >> 3)&7; - if(new_screen_mode == 7) new_screen_mode = 4; - if(new_screen_mode != _screen_mode) - { -// printf("To mode %d, at %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); - update_display(); - _screen_mode = new_screen_mode; - switch(_screen_mode) - { - case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; - case 3: _screenModeBaseAddress = 0x4000; break; - case 4: case 5: _screenModeBaseAddress = 0x5800; break; - case 6: _screenModeBaseAddress = 0x6000; break; - } - } - - // update speaker mode - bool new_speaker_is_enabled = (*value & 6) == 2; - if(new_speaker_is_enabled != _speaker->get_is_enabled()) - { - update_audio(); - _speaker->set_is_enabled(new_speaker_is_enabled); - _tape.set_is_enabled(!new_speaker_is_enabled); - } - - _tape.set_is_running(((*value)&0x40) ? true : false); - _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); - - // TODO: caps lock LED - } - break; - default: - { - if(!isReadOperation(operation)) - { - update_display(); - - static const int registers[4][4] = { - {10, 8, 2, 0}, - {14, 12, 6, 4}, - {15, 13, 7, 5}, - {11, 9, 3, 1}, - }; - const int index = (address >> 1)&3; - const uint8_t colour = ~(*value); - if(address&1) - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); - _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); - _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); - _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); - - _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); - _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); - } - else - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); - _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); - _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); - _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); - - _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); - _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); - } - - // regenerate all palette tables for now -#define pack(a, b) (uint8_t)((a << 4) | (b)) - for(int byte = 0; byte < 256; byte++) - { - uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - - target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; - target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); - - target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); - target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); - - _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], - _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); - } -#undef pack - } - } - break; - } - } - else - { + case 0xfe00: if(isReadOperation(operation)) { - if( - _use_fast_tape_hack && - _tape.has_tape() && - (operation == CPU6502::BusOperation::ReadOpcode) && - ( - (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 - (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling - (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM - (address == 0xfa51) || (address == 0xfa52) || // pathway. - - (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be - // dispatched; we can check whether it would be call 14 - // (i.e. read byte) and, if so, whether the OS was about to - // issue a read byte call to a ROM despite being the tape - // FS being selected. If so then this is a get byte that - // we should service synthetically. Put the byte into Y - // and set A to zero to report that action was taken, then - // allow the PC read to return an RTS. - ) - ) + *value = _interrupt_status; + _interrupt_status &= ~PowerOnReset; + } + else + { + _interrupt_control = (*value) & ~1; + evaluate_interrupts(); + } + break; + case 0xfe02: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe03: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe04: + if(isReadOperation(operation)) + { + *value = _tape.get_data_register(); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + } + else + { + _tape.set_data_register(*value); + _tape.clear_interrupts(Interrupt::TransmitDataEmpty); + } + break; + case 0xfe05: + if(!isReadOperation(operation)) + { + const uint8_t interruptDisable = (*value)&0xf0; + if( interruptDisable ) { - uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); - if(address == 0xf0a8) + if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; + evaluate_interrupts(); + + // TODO: NMI + } + + // latch the paged ROM in case external hardware is being emulated + _active_rom = (Electron::ROMSlot)(*value & 0xf); + + // apply the ULA's test + if(*value & 0x08) + { + if(*value & 0x04) { - if(!_ram[0x247] && service_call == 14) - { - _tape.set_delegate(nullptr); - - // TODO: handle tape wrap around. - - int cycles_left_while_plausibly_in_data = 50; - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - while(1) - { - _tape.run_for_input_pulse(); - cycles_left_while_plausibly_in_data--; - if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; - if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && - (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) - ) break; - } - _tape.set_delegate(this); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - _interrupt_status |= _tape.get_interrupt_status(); - - _fast_load_is_in_data = true; - set_value_of_register(CPU6502::Register::A, 0); - set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); - *value = 0x60; // 0x60 is RTS - } - else - *value = _os[address & 16383]; + _keyboard_is_active = false; + _basic_is_active = false; } else - *value = 0xea; + { + _keyboard_is_active = !(*value & 0x02); + _basic_is_active = !_keyboard_is_active; + } + } + } + break; + case 0xfe06: + if(!isReadOperation(operation)) + { + update_audio(); + _speaker->set_divider(*value); + _tape.set_counter(*value); + } + break; + case 0xfe07: + if(!isReadOperation(operation)) + { + // update screen mode + uint8_t new_screen_mode = ((*value) >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != _screen_mode) + { + update_display(); + _screen_mode = new_screen_mode; + switch(_screen_mode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } + } + + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != _speaker->get_is_enabled()) + { + update_audio(); + _speaker->set_is_enabled(new_speaker_is_enabled); + _tape.set_is_enabled(!new_speaker_is_enabled); + } + + _tape.set_is_running(((*value)&0x40) ? true : false); + _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); + + // TODO: caps lock LED + } + break; + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: + { + if(!isReadOperation(operation)) + { + update_display(); + + static const int registers[4][4] = { + {10, 8, 2, 0}, + {14, 12, 6, 4}, + {15, 13, 7, 5}, + {11, 9, 3, 1}, + }; + const int index = (address >> 1)&3; + const uint8_t colour = ~(*value); + if(address&1) + { + _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); + _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); + _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); + _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); + + _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); + _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); } else { - *value = _os[address & 16383]; + _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); + _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); + _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); + _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); + + _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); + _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); } - } - } - } - else - { - if(isReadOperation(operation)) - { - *value = _roms[_active_rom][address & 16383]; - if(_keyboard_is_active) - { - *value &= 0xf0; - for(int address_line = 0; address_line < 14; address_line++) + + // regenerate all palette tables for now +#define pack(a, b) (uint8_t)((a << 4) | (b)) + for(int byte = 0; byte < 256; byte++) { - if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + + target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; + target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); + + target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); + target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); + + _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], + _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); } - } - if(_basic_is_active) - { - *value &= _roms[ROMSlotBASIC][address & 16383]; +#undef pack } } + break; + + case 0xfcc4: case 0xfcc5: case 0xfcc6: case 0xfcc7: + if(_wd1770 && (address&0x00f0) == 0x00c0) + { + if(isReadOperation(operation)) + *value = _wd1770->get_register(address); + else + _wd1770->set_register(address, *value); + } + break; + case 0xfcc0: + if(_wd1770 && (address&0x00f0) == 0x00c0) + { + if(isReadOperation(operation)) + *value = 1; + else + { + // TODO: + // bit 0 => enable or disable drive 1 + // bit 1 => enable or disable drive 2 + // bit 2 => side select + // bit 3 => single density select +// _wd1770->set_register(address, *value); + } + } + break; + + default: + if(address >= 0xc000) + { + if(isReadOperation(operation)) + { + if( + _use_fast_tape_hack && + _tape.has_tape() && + (operation == CPU6502::BusOperation::ReadOpcode) && + ( + (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 + (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling + (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM + (address == 0xfa51) || (address == 0xfa52) || // pathway. + + (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be + // dispatched; we can check whether it would be call 14 + // (i.e. read byte) and, if so, whether the OS was about to + // issue a read byte call to a ROM despite being the tape + // FS being selected. If so then this is a get byte that + // we should service synthetically. Put the byte into Y + // and set A to zero to report that action was taken, then + // allow the PC read to return an RTS. + ) + ) + { + uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); + if(address == 0xf0a8) + { + if(!_ram[0x247] && service_call == 14) + { + _tape.set_delegate(nullptr); + + // TODO: handle tape wrap around. + + int cycles_left_while_plausibly_in_data = 50; + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + while(1) + { + _tape.run_for_input_pulse(); + cycles_left_while_plausibly_in_data--; + if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; + if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && + (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) + ) break; + } + _tape.set_delegate(this); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + _interrupt_status |= _tape.get_interrupt_status(); + + _fast_load_is_in_data = true; + set_value_of_register(CPU6502::Register::A, 0); + set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); + *value = 0x60; // 0x60 is RTS + } + else + *value = _os[address & 16383]; + } + else + *value = 0xea; + } + else + { + *value = _os[address & 16383]; + } + } + } + else + { + if(isReadOperation(operation)) + { + *value = _roms[_active_rom][address & 16383]; + if(_keyboard_is_active) + { + *value &= 0xf0; + for(int address_line = 0; address_line < 14; address_line++) + { + if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + } + } + if(_basic_is_active) + { + *value &= _roms[ROMSlotBASIC][address & 16383]; + } + } + } + break; } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 10a6f33b8..57c2e6a60 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,6 +10,7 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" +#include "../../Components/1770/1770.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../ConfigurationTarget.hpp" @@ -227,6 +228,9 @@ class Machine: bool _use_fast_tape_hack; bool _fast_load_is_in_data; + // Disk + std::unique_ptr<WD::WD1770> _wd1770; + // Outputs std::shared_ptr<Outputs::CRT::CRT> _crt; std::shared_ptr<Speaker> _speaker; diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index d4023525e..72cf144eb 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -26,7 +26,7 @@ class FMParser: public Storage::Disk::Drive { Storage::Time bit_length; bit_length.length = 1; - bit_length.clock_rate = 250000; // i.e. 250 kbps + bit_length.clock_rate = 250000; // i.e. 250 kbps (including clocks) set_expected_bit_length(bit_length); } diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6412e3e2e..cc48d0520 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -199,7 +199,7 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSec 6, 0, 17, 14, 0, - 6400); + 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) @@ -213,5 +213,5 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSe 12, 22, 12, 18, 32, - 12800); + 12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm) }