1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-02 20:30:00 +00:00

Merge pull request #88 from TomHarte/WDWrites

Implements write support for the WD.
This commit is contained in:
Thomas Harte 2016-12-30 18:11:00 -05:00 committed by GitHub
commit c6fcc40ac5
32 changed files with 1066 additions and 370 deletions

View File

@ -27,12 +27,13 @@ WD1770::Status::Status() :
WD1770::WD1770(Personality p) :
Storage::Disk::Controller(8000000, 16, 300),
crc_generator_(0x1021, 0xffff),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
is_awaiting_marker_value_(false),
is_reading_data_(false),
data_mode_(DataMode::Scanning),
delegate_(nullptr),
personality_(p),
head_is_loaded_(false)
@ -74,7 +75,12 @@ void WD1770::set_register(int address, uint8_t value)
break;
case 1: track_ = value; break;
case 2: sector_ = value; break;
case 3: data_ = value; break;
case 3:
data_ = value;
update_status([] (Status &status) {
status.data_request = false;
});
break;
}
}
@ -154,11 +160,13 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles)
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
Token::Type token_type = Token::Byte;
if(!is_reading_data_)
if(data_mode_ == DataMode::Scanning)
{
if(!is_double_density_)
{
@ -166,15 +174,23 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
crc_generator_.reset();
crc_generator_.add(Storage::Encodings::MFM::MFMIndexAddressByte);
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
crc_generator_.reset();
crc_generator_.add(Storage::Encodings::MFM::MFMIDAddressByte);
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
crc_generator_.reset();
crc_generator_.add(Storage::Encodings::MFM::MFMDataAddressByte);
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
crc_generator_.reset();
crc_generator_.add(Storage::Encodings::MFM::MFMDeletedDataAddressByte);
break;
default:
break;
@ -184,13 +200,14 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::MFMIndexAddressMark:
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
return;
case Storage::Encodings::MFM::MFMAddressMark:
case Storage::Encodings::MFM::MFMSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
return;
default:
break;
@ -241,6 +258,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
}
}
crc_generator_.add(latest_token_.byte_value);
posit_event(Event::Token);
return;
}
@ -269,6 +287,12 @@ void WD1770::process_index_hole()
}
}
void WD1770::process_write_completed()
{
posit_event(Event::DataWritten);
}
// +------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
@ -288,13 +312,14 @@ void WD1770::process_index_hole()
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
#define READ_ID() \
if(new_event_type == Event::Token) \
{ \
if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
{ \
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
@ -325,7 +350,7 @@ void WD1770::posit_event(Event new_event_type)
// Wait for a new command, branch to the appropriate handler.
wait_for_command:
printf("Idle...\n");
is_reading_data_ = false;
data_mode_ = DataMode::Scanning;
index_hole_count_ = 0;
update_status([] (Status &status) {
@ -445,7 +470,7 @@ void WD1770::posit_event(Event new_event_type)
}
if(distance_into_section_ == 7)
{
is_reading_data_ = false;
data_mode_ = DataMode::Scanning;
// TODO: CRC check
if(header_[0] == track_)
{
@ -498,7 +523,7 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20) // TODO:: && is_write_protected
if(command_&0x20 && get_drive_is_read_only())
{
update_status([] (Status &status) {
status.write_protect = true;
@ -520,11 +545,24 @@ void WD1770::posit_event(Event new_event_type)
}
if(distance_into_section_ == 7)
{
is_reading_data_ = false;
printf("Considering %d/%d\n", header_[0], header_[2]);
data_mode_ = DataMode::Scanning;
if(header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
{
// TODO: test CRC
printf("Found %d/%d\n", header_[0], header_[2]);
if(crc_generator_.get_value())
{
printf("CRC error; back to searching\n");
update_status([] (Status &status) {
status.crc_error = true;
});
goto type2_get_header;
}
update_status([] (Status &status) {
status.crc_error = false;
});
goto type2_read_or_write_data;
}
distance_into_section_ = 0;
@ -545,7 +583,7 @@ void WD1770::posit_event(Event new_event_type)
status.record_type = (latest_token_.type == Token::DeletedData);
});
distance_into_section_ = 0;
is_reading_data_ = true;
data_mode_ = DataMode::Reading;
goto type2_read_byte;
}
goto type2_read_data;
@ -573,7 +611,15 @@ void WD1770::posit_event(Event new_event_type)
distance_into_section_++;
if(distance_into_section_ == 2)
{
// TODO: check CRC
if(crc_generator_.get_value())
{
printf("CRC error; terminating\n");
update_status([this] (Status &status) {
status.crc_error = true;
});
goto wait_for_command;
}
if(command_ & 0x10)
{
sector_++;
@ -586,7 +632,95 @@ void WD1770::posit_event(Event new_event_type)
type2_write_data:
printf("!!!TODO: data portion of sector!!!\n");
WAIT_FOR_BYTES(2);
update_status([] (Status &status) {
status.data_request = true;
});
WAIT_FOR_BYTES(9);
if(status_.data_request)
{
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
WAIT_FOR_BYTES(1);
if(is_double_density_)
{
WAIT_FOR_BYTES(11);
}
data_mode_ = DataMode::Writing;
begin_writing();
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++)
{
write_byte(0);
}
WAIT_FOR_EVENT(Event::DataWritten);
if(is_double_density_)
{
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
write_byte((command_&0x01) ? Storage::Encodings::MFM::MFMDeletedDataAddressByte : Storage::Encodings::MFM::MFMDataAddressByte);
}
else
{
crc_generator_.reset();
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::MFMDeletedDataAddressByte : Storage::Encodings::MFM::MFMDataAddressByte);
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
}
WAIT_FOR_EVENT(Event::DataWritten);
distance_into_section_ = 0;
type2_write_loop:
/*
This deviates from the data sheet slightly since that would prima facie request one more byte
of data than is actually written the last time around the loop it has transferred from the
data register to the data shift register, set data request, written the byte, checked that data
request has been satified, then finally considers whether all bytes are done. Based on both
natural expectations and the way that emulated machines responded, I believe that to be a
documentation error.
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3])
{
goto type2_write_crc;
}
update_status([] (Status &status) {
status.data_request = true;
});
WAIT_FOR_EVENT(Event::DataWritten);
if(status_.data_request)
{
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
goto type2_write_loop;
type2_write_crc:
{
uint16_t crc = crc_generator_.get_value();
write_byte(crc >> 8);
write_byte(crc & 0xff);
}
write_byte(0xff);
WAIT_FOR_EVENT(Event::DataWritten);
end_writing();
if(command_ & 0x10)
{
sector_++;
goto test_type2_write_protection;
}
printf("Wrote sector %d\n", sector_);
goto wait_for_command;
begin_type_3:
update_status([] (Status &status) {
@ -619,3 +753,32 @@ void WD1770::set_head_loaded(bool head_loaded)
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(Event::HeadLoad);
}
void WD1770::write_bit(int bit)
{
if(is_double_density_)
{
Controller::write_bit(!bit && !last_bit_);
Controller::write_bit(!!bit);
last_bit_ = bit;
}
else
{
Controller::write_bit(true);
Controller::write_bit(!!bit);
}
}
void WD1770::write_byte(uint8_t byte)
{
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
crc_generator_.add(byte);
}
void WD1770::write_raw_short(uint16_t value)
{
for(int c = 0; c < 16; c++)
{
Controller::write_bit(!!((value << c)&0x8000));
}
}

View File

@ -10,6 +10,7 @@
#define _770_hpp
#include "../../Storage/Disk/DiskController.hpp"
#include "../../NumberTheory/CRC.hpp"
namespace WD {
@ -94,7 +95,11 @@ class WD1770: public Storage::Disk::Controller {
void update_status(std::function<void(Status &)> updater);
// Tokeniser
bool is_reading_data_;
enum DataMode {
Scanning,
Reading,
Writing
} data_mode_;
bool is_double_density_;
int shift_register_;
struct Token {
@ -110,18 +115,28 @@ class WD1770: public Storage::Disk::Controller {
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only).
DataWritten = (1 << 4), // Indicates that all queued bits have been written
Timer = (1 << 4), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 5) // Indicates that index_hole_count_ has reached index_hole_count_target_.
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
};
void posit_event(Event type);
int interesting_event_mask_;
int resume_point_;
int delay_time_;
// Output
int last_bit_;
void write_bit(int bit);
void write_byte(uint8_t byte);
void write_raw_short(uint16_t value);
// ID buffer
uint8_t header_[6];
// CRC generator
NumberTheory::CRC16 crc_generator_;
// 1793 head-loading logic
bool head_is_loaded_;
@ -131,6 +146,7 @@ class WD1770: public Storage::Disk::Controller {
// Storage::Disk::Controller
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
virtual void process_write_completed();
};
}

View File

@ -10,7 +10,10 @@
using namespace Electron;
Plus3::Plus3() : WD1770(P1770) {}
Plus3::Plus3() : WD1770(P1770), last_control_(0)
{
set_control_register(last_control_, 0xff);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
@ -24,18 +27,32 @@ void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
void Plus3::set_control_register(uint8_t control)
{
// TODO:
// bit 0 => enable or disable drive 1
// bit 1 => enable or disable drive 2
// bit 2 => side select
// bit 3 => single density select
switch(control&3)
{
case 0: selected_drive_ = -1; set_drive(nullptr); break;
default: selected_drive_ = 0; set_drive(drives_[0]); break;
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
}
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
set_is_double_density(!(control & 0x08));
uint8_t changes = control ^ last_control_;
last_control_ = control;
set_control_register(control, changes);
}
void Plus3::set_control_register(uint8_t control, uint8_t changes)
{
if(changes&3)
{
switch(control&3)
{
case 0: selected_drive_ = -1; set_drive(nullptr); break;
default: selected_drive_ = 0; set_drive(drives_[0]); break;
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
}
}
if(changes & 0x04)
{
invalidate_track();
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}

View File

@ -21,8 +21,10 @@ class Plus3 : public WD::WD1770 {
void set_control_register(uint8_t control);
private:
void set_control_register(uint8_t control, uint8_t changes);
std::shared_ptr<Storage::Disk::Drive> drives_[2];
int selected_drive_;
uint8_t last_control_;
};
}

View File

@ -23,8 +23,11 @@ Microdisc::Microdisc() :
delegate_(nullptr),
paging_flags_(BASICDisable),
head_load_request_counter_(-1),
WD1770(P1793)
{}
WD1770(P1793),
last_control_(0)
{
set_control_register(last_control_, 0xff);
}
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
@ -38,47 +41,55 @@ void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
void Microdisc::set_control_register(uint8_t control)
{
printf("control: %d%d%d%d%d%d%d%d\n",
(control >> 7)&1,
(control >> 6)&1,
(control >> 5)&1,
(control >> 4)&1,
(control >> 3)&1,
(control >> 2)&1,
(control >> 1)&1,
(control >> 0)&1);
uint8_t changes = last_control_ ^ control;
last_control_ = control;
set_control_register(control, changes);
}
void Microdisc::set_control_register(uint8_t control, uint8_t changes)
{
// b2: data separator clock rate select (1 = double) [TODO]
// b65: drive select
selected_drive_ = (control >> 5)&3;
set_drive(drives_[selected_drive_]);
if((changes >> 5)&3)
{
selected_drive_ = (control >> 5)&3;
set_drive(drives_[selected_drive_]);
}
// b4: side select
unsigned int head = (control & 0x10) ? 1 : 0;
for(int c = 0; c < 4; c++)
if(changes & 0x10)
{
if(drives_[c]) drives_[c]->set_head(head);
unsigned int head = (control & 0x10) ? 1 : 0;
for(int c = 0; c < 4; c++)
{
if(drives_[c]) drives_[c]->set_head(head);
}
}
// b3: double density select (0 = double)
set_is_double_density(!(control & 0x08));
if(changes & 0x08)
{
set_is_double_density(!(control & 0x08));
}
// b0: IRQ enable
bool had_irq = get_interrupt_request_line();
irq_enable_ = !!(control & 0x01);
bool has_irq = get_interrupt_request_line();
if(has_irq != had_irq && delegate_)
if(changes & 0x01)
{
delegate_->wd1770_did_change_output(this);
bool had_irq = get_interrupt_request_line();
irq_enable_ = !!(control & 0x01);
bool has_irq = get_interrupt_request_line();
if(has_irq != had_irq && delegate_)
{
delegate_->wd1770_did_change_output(this);
}
}
// b7: EPROM select (0 = select)
// b1: ROM disable (0 = disable)
int new_paging_flags = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
if(new_paging_flags != paging_flags_)
if(changes & 0x82)
{
paging_flags_ = new_paging_flags;
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
}
}

View File

@ -39,6 +39,7 @@ class Microdisc: public WD::WD1770 {
inline int get_paging_flags() { return paging_flags_; }
private:
void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load);
bool get_drive_is_ready();
std::shared_ptr<Storage::Disk::Drive> drives_[4];
@ -47,6 +48,7 @@ class Microdisc: public WD::WD1770 {
int paging_flags_;
int head_load_request_counter_;
Delegate *delegate_;
uint8_t last_control_;
};
}

View File

@ -28,10 +28,11 @@ class CRC16 {
value_ = (uint16_t)(value_ << 1) ^ exclusive_or;
}
}
inline uint16_t get_value() { return value_; }
inline uint16_t get_value() const { return value_; }
inline void set_value(uint16_t value) { value_ = value; }
private:
uint16_t reset_value_, polynomial_;
const uint16_t reset_value_, polynomial_;
uint16_t value_;
};

View File

@ -354,6 +354,7 @@
4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; };
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
@ -841,6 +842,7 @@
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
@ -1691,6 +1693,7 @@
isa = PBXGroup;
children = (
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
@ -2465,6 +2468,7 @@
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,

View File

@ -0,0 +1,72 @@
//
// CRCTests.m
// Clock Signal
//
// Created by Thomas Harte on 27/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "CRC.hpp"
@interface CRCTests : XCTestCase
@end
@implementation CRCTests
- (NumberTheory::CRC16)mfmCRCGenerator
{
return NumberTheory::CRC16(0x1021, 0xffff);
}
- (uint16_t)crcOfData:(uint8_t *)data length:(size_t)length generator:(NumberTheory::CRC16 &)generator
{
generator.reset();
for(size_t c = 0; c < length; c++)
generator.add(data[c]);
return generator.get_value();
}
- (void)testIDMark
{
uint8_t IDMark[] =
{
0xa1, 0xa1, 0xa1, 0xfe, 0x00, 0x00, 0x01, 0x01
};
uint16_t crc = 0xfa0c;
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
uint16_t computedCRC = [self crcOfData:IDMark length:sizeof(IDMark) generator:crcGenerator];
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
}
- (void)testData
{
uint8_t sectorData[] =
{
0xa1, 0xa1, 0xa1, 0xfb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x53, 0x45, 0x44, 0x4f,
0x52, 0x49, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x53, 0x45, 0x44, 0x4f, 0x52, 0x49, 0x43, 0x20, 0x56, 0x31, 0x2e, 0x30,
0x30, 0x36, 0x20, 0x30, 0x31, 0x2f, 0x30, 0x31, 0x2f, 0x38, 0x36, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20
};
uint16_t crc = 0x4de7;
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
uint16_t computedCRC = [self crcOfData:sectorData length:sizeof(sectorData) generator:crcGenerator];
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
}
@end

View File

@ -14,180 +14,11 @@
using namespace StaticAnalyser::Acorn;
class FMParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
FMParser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm)
{
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);
set_motor_on(true);
}
/*!
Attempts to read the sector located at @c track and @c sector.
@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)
{
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) step(direction);
}
return get_sector(sector);
}
private:
unsigned int shift_register_;
int index_count_;
uint8_t track_;
int bit_count_;
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_cache_[65536];
NumberTheory::CRC16 crc_generator_;
bool is_mfm_;
void process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
void process_index_hole()
{
index_count_++;
}
uint8_t get_next_byte()
{
bit_count_ = 0;
while(bit_count_ < 16) run_for_cycles(1);
uint8_t byte = (uint8_t)(
((shift_register_&0x0001) >> 0) |
((shift_register_&0x0004) >> 1) |
((shift_register_&0x0010) >> 2) |
((shift_register_&0x0040) >> 3) |
((shift_register_&0x0100) >> 4) |
((shift_register_&0x0400) >> 5) |
((shift_register_&0x1000) >> 6) |
((shift_register_&0x4000) >> 7));
crc_generator_.add(byte);
return byte;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector()
{
std::shared_ptr<Storage::Encodings::MFM::Sector> sector(new Storage::Encodings::MFM::Sector);
index_count_ = 0;
while(index_count_ < 2)
{
// look for an ID address mark
while(1)
{
run_for_cycles(1);
if(is_mfm_)
{
if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMIDAddressByte) break;
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break;
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.reset();
sector->track = get_next_byte();
sector->side = get_next_byte();
sector->sector = get_next_byte();
uint8_t size = get_next_byte();
uint16_t header_crc = crc_generator_.get_value();
if((header_crc >> 8) != get_next_byte()) continue;
if((header_crc & 0xff) != get_next_byte()) continue;
// look for data mark
while(1)
{
run_for_cycles(1);
if(is_mfm_)
{
if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMDataAddressByte) break;
if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr;
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break;
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr;
}
if(index_count_ >= 2) return nullptr;
}
size_t data_size = (size_t)(128 << size);
sector->data.reserve(data_size);
crc_generator_.reset();
for(size_t c = 0; c < data_size; c++)
{
sector->data.push_back(get_next_byte());
}
uint16_t data_crc = crc_generator_.get_value();
if((data_crc >> 8) != get_next_byte()) continue;
if((data_crc & 0xff) != get_next_byte()) continue;
return sector;
}
return nullptr;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector)
{
// uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
// if(sector_cache_[sector_address]) return sector_cache_[sector_address];
std::shared_ptr<Storage::Encodings::MFM::Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
};
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
std::unique_ptr<Catalogue> catalogue(new Catalogue);
FMParser parser(false);
parser.drive->set_disk(disk);
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);
@ -197,6 +28,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
uint8_t final_file_offset = details->data[5];
if(final_file_offset&7) return nullptr;
if(final_file_offset < 8) return nullptr;
char disk_name[13];
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
@ -248,8 +80,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::unique_ptr<Catalogue> catalogue(new Catalogue);
FMParser parser(true);
parser.drive->set_disk(disk);
Storage::Encodings::MFM::Parser parser(true, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1);
if(!free_space_map_second_half) return nullptr;

View File

@ -17,11 +17,16 @@ int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position)
void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track)
{
if(!get_is_read_only()) return;
if(get_is_read_only()) return;
int address = get_id_for_track_at_position(head, position);
cached_tracks_[address] = track;
modified_tracks_.insert(address);
if(!update_queue_) update_queue_.reset(new Concurrency::AsyncTaskQueue);
std::shared_ptr<Track> track_copy(track->clone());
update_queue_->enqueue([this, head, position, track_copy] {
store_updated_track_at_position(head, position, track_copy, file_access_mutex_);
});
}
std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned int position)
@ -30,21 +35,15 @@ std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned i
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
if(cached_track != cached_tracks_.end()) return cached_track->second;
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
std::shared_ptr<Track> track = get_uncached_track_at_position(head, position);
cached_tracks_[address] = track;
return track;
}
std::shared_ptr<Track> Disk::get_modified_track_at_position(unsigned int head, unsigned int position)
{
int address = get_id_for_track_at_position(head, position);
if(modified_tracks_.find(address) == modified_tracks_.end()) return nullptr;
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
if(cached_track == cached_tracks_.end()) return nullptr;
return cached_track->second;
}
void Disk::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex) {}
bool Disk::get_is_modified()
void Disk::flush_updates()
{
return !modified_tracks_.empty();
if(update_queue_) update_queue_->flush();
}

View File

@ -9,10 +9,13 @@
#ifndef Disk_hpp
#define Disk_hpp
#include <memory>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include "../Storage.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace Storage {
namespace Disk {
@ -50,6 +53,11 @@ class Track {
@returns the time jumped to.
*/
virtual Time seek_to(const Time &time_since_index_hole) = 0;
/*!
The virtual copy constructor pattern; returns a copy of the Track.
*/
virtual Track *clone() = 0;
};
/*!
@ -66,6 +74,7 @@ class Track {
*/
class Disk {
public:
virtual ~Disk() {}
/*!
@returns the number of discrete positions that this disk uses to model its complete surface area.
@ -106,20 +115,15 @@ class Disk {
*/
virtual std::shared_ptr<Track> get_uncached_track_at_position(unsigned int head, unsigned int position) = 0;
/*!
@returns @c true if any calls to set_track_at_position occurred; @c false otherwise.
*/
bool get_is_modified();
/*!
@returns the @c Track at @c position underneath @c head if a modification was written there.
*/
std::shared_ptr<Track> get_modified_track_at_position(unsigned int head, unsigned int position);
virtual void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex);
void flush_updates();
private:
std::map<int, std::shared_ptr<Track>> cached_tracks_;
std::set<int> modified_tracks_;
int get_id_for_track_at_position(unsigned int head, unsigned int position);
std::mutex file_access_mutex_;
std::unique_ptr<Concurrency::AsyncTaskQueue> update_queue_;
};
}

View File

@ -7,6 +7,7 @@
//
#include "DiskController.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage::Disk;
@ -19,7 +20,6 @@ Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multipli
motor_is_on_(false),
is_reading_(true),
track_is_dirty_(false),
TimedEventLoop(clock_rate * clock_rate_multiplier)
{
@ -30,31 +30,23 @@ Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multipli
void Controller::setup_track()
{
if(patched_track_)
{
drive_->set_track(patched_track_);
}
track_ = drive_->get_track();
track_is_dirty_ = false;
Time offset;
Time track_time_now = get_time_into_track();
if(track_ && track_time_now > Time(0))
if(track_)
{
Time time_found = track_->seek_to(track_time_now);
offset = track_time_now - time_found;
}
else
{
offset = track_time_now;
}
get_next_event(offset);
}
void Controller::run_for_cycles(int number_of_cycles)
{
Time zero(0);
if(drive_ && drive_->has_disk() && motor_is_on_)
{
if(!track_) setup_track();
@ -64,11 +56,39 @@ void Controller::run_for_cycles(int number_of_cycles)
{
int cycles_until_next_event = (int)get_cycles_until_next_event();
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
if(!is_reading_ && cycles_until_bits_written_ > zero)
{
int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int();
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
}
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
number_of_cycles -= cycles_to_run_for;
if(is_reading_) pll_->run_for_cycles(cycles_to_run_for);
if(is_reading_)
{
pll_->run_for_cycles(cycles_to_run_for);
}
else
{
if(cycles_until_bits_written_ > zero)
{
Storage::Time cycles_to_run_for_time(cycles_to_run_for);
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
{
process_write_completed();
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
cycles_until_bits_written_.set_zero();
else
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
else
{
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
}
}
TimedEventLoop::run_for_cycles(cycles_to_run_for);
}
}
@ -123,7 +143,7 @@ void Controller::begin_writing()
{
is_reading_ = false;
write_segment_.length_of_a_bit = bit_length_ * rotational_multiplier_;
write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_;
write_segment_.data.clear();
write_segment_.number_of_bits = 0;
@ -136,6 +156,8 @@ void Controller::write_bit(bool value)
if(needs_new_byte) write_segment_.data.push_back(0);
if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7);
write_segment_.number_of_bits++;
cycles_until_bits_written_ += cycles_per_bit_;
}
void Controller::end_writing()
@ -144,9 +166,15 @@ void Controller::end_writing()
if(!patched_track_)
{
patched_track_.reset(new PCMPatchedTrack(track_));
// Avoid creating a new patched track if this one is already patched
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
if(!patched_track_)
{
patched_track_.reset(new PCMPatchedTrack(track_));
}
}
patched_track_->add_segment(write_start_time_, write_segment_);
invalidate_track(); // TEMPORARY: to force a seek
}
#pragma mark - PLL control and delegate
@ -154,10 +182,14 @@ void Controller::end_writing()
void Controller::set_expected_bit_length(Time bit_length)
{
bit_length_ = bit_length;
bit_length_.simplify();
cycles_per_bit_ = Storage::Time(clock_rate_) * bit_length;
cycles_per_bit_.simplify();
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = (int)((bit_length.length * clock_rate_) / bit_length.clock_rate);
int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int();
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
pll_->set_delegate(this);
}
@ -181,10 +213,16 @@ bool Controller::get_drive_is_ready()
return drive_->has_disk();
}
bool Controller::get_drive_is_read_only()
{
if(!drive_) return false;
return drive_->get_is_read_only();
}
void Controller::step(int direction)
{
if(drive_) drive_->step(direction);
invalidate_track();
if(drive_) drive_->step(direction);
}
void Controller::set_motor_on(bool motor_on)
@ -199,11 +237,23 @@ bool Controller::get_motor_on()
void Controller::set_drive(std::shared_ptr<Drive> drive)
{
drive_ = drive;
invalidate_track();
if(drive_ != drive)
{
invalidate_track();
drive_ = drive;
}
}
void Controller::invalidate_track()
{
track_ = nullptr;
if(patched_track_)
{
drive_->set_track(patched_track_);
patched_track_ = nullptr;
}
}
void Controller::process_write_completed()
{
}

View File

@ -49,6 +49,10 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
Sets the current drive.
*/
void set_drive(std::shared_ptr<Drive> drive);
/*!
Announces that the track the drive sees is about to change for a reason unknownt to the controller.
*/
void invalidate_track();
/*!
@ -86,10 +90,16 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
/*!
Should be implemented by subcalsses; communicates that the index hole has been reached.
Should be implemented by subclasses; communicates that the index hole has been reached.
*/
virtual void process_index_hole() = 0;
/*!
Should be implemented by subclasses if they implement writing; communicates that
all bits supplied to write_bit have now been written.
*/
virtual void process_write_completed();
// for TimedEventLoop
virtual void process_next_event();
@ -99,6 +109,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
bool get_is_track_zero();
void step(int direction);
virtual bool get_drive_is_ready();
bool get_drive_is_read_only();
private:
Time bit_length_;
@ -116,11 +127,13 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
bool motor_is_on_;
bool is_reading_;
bool track_is_dirty_;
std::shared_ptr<PCMPatchedTrack> patched_track_;
PCMSegment write_segment_;
Time write_start_time_;
Time cycles_until_bits_written_;
Time cycles_per_bit_;
void setup_track();
Time get_time_into_track();
};

View File

@ -14,14 +14,21 @@ using namespace Storage::Disk;
Drive::Drive()
: head_position_(0), head_(0) {}
void Drive::set_disk(std::shared_ptr<Disk> disk)
void Drive::set_disk(const std::shared_ptr<Disk> &disk)
{
disk_ = disk;
track_ = nullptr;
}
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track)
{
disk_ = nullptr;
track_ = track;
}
bool Drive::has_disk()
{
return (bool)disk_;
return (bool)disk_ || (bool)track_;
}
bool Drive::get_is_track_zero()
@ -42,12 +49,14 @@ void Drive::set_head(unsigned int head)
bool Drive::get_is_read_only()
{
if(disk_) return disk_->get_is_read_only();
if(track_) return true;
return false;
}
std::shared_ptr<Track> Drive::get_track()
{
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
if(track_) return track_;
return nullptr;
}

View File

@ -20,9 +20,14 @@ class Drive {
Drive();
/*!
Inserts @c disk into the drive.
Replaces whatever is in the drive with @c disk.
*/
void set_disk(std::shared_ptr<Disk> disk);
void set_disk(const std::shared_ptr<Disk> &disk);
/*!
Replaces whatever is in the drive with a disk that contains endless copies of @c track.
*/
void set_disk_with_track(const std::shared_ptr<Track> &track);
/*!
@returns @c true if a disk is currently inserted; @c false otherwise.
@ -61,6 +66,7 @@ class Drive {
void set_track(const std::shared_ptr<Track> &track);
private:
std::shared_ptr<Track> track_;
std::shared_ptr<Disk> disk_;
int head_position_;
unsigned int head_;

View File

@ -18,6 +18,7 @@ class MFMEncoder: public Encoder {
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
uint16_t spread_value =
(uint16_t)(
((input & 0x01) << 0) |
@ -29,33 +30,42 @@ class MFMEncoder: public Encoder {
((input & 0x40) << 6) |
((input & 0x80) << 7)
);
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15));
output_ = spread_value | ((~or_bits) & 0xaaaa);
output_short(output_);
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
output_short(output);
}
void add_index_address_mark() {
output_short(output_ = MFMIndexAddressMark);
for(int c = 0; c < 3; c++) output_short(MFMIndexSync);
add_byte(MFMIndexAddressByte);
}
void add_ID_address_mark() {
output_short(output_ = MFMAddressMark);
output_sync();
add_byte(MFMIDAddressByte);
}
void add_data_address_mark() {
output_short(output_ = MFMAddressMark);
output_sync();
add_byte(MFMDataAddressByte);
}
void add_deleted_data_address_mark() {
output_short(output_ = MFMAddressMark);
output_sync();
add_byte(MFMDeletedDataAddressByte);
}
private:
uint16_t output_;
uint16_t last_output_;
void output_short(uint16_t value) {
last_output_ = value;
Encoder::output_short(value);
}
void output_sync() {
for(int c = 0; c < 3; c++) output_short(MFMSync);
crc_generator_.set_value(MFMPostSyncCRCValue);
}
};
class FMEncoder: public Encoder {
@ -64,6 +74,7 @@ class FMEncoder: public Encoder {
FMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
output_short(
(uint16_t)(
((input & 0x01) << 0) |
@ -78,10 +89,33 @@ class FMEncoder: public Encoder {
));
}
void add_index_address_mark() { output_short(FMIndexAddressMark); }
void add_ID_address_mark() { output_short(FMIDAddressMark); }
void add_data_address_mark() { output_short(FMDataAddressMark); }
void add_deleted_data_address_mark() { output_short(FMDeletedDataAddressMark); }
void add_index_address_mark()
{
crc_generator_.reset();
crc_generator_.add(MFMIndexAddressByte);
output_short(FMIndexAddressMark);
}
void add_ID_address_mark()
{
crc_generator_.reset();
crc_generator_.add(MFMIDAddressByte);
output_short(FMIDAddressMark);
}
void add_data_address_mark()
{
crc_generator_.reset();
crc_generator_.add(MFMDataAddressByte);
output_short(FMDataAddressMark);
}
void add_deleted_data_address_mark()
{
crc_generator_.reset();
crc_generator_.add(MFMDeletedDataAddressByte);
output_short(FMDeletedDataAddressMark);
}
};
static uint8_t logarithmic_size_for_size(size_t size)
@ -109,7 +143,6 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
Storage::Disk::PCMSegment segment;
segment.data.reserve(expected_track_bytes);
T shifter(segment.data);
NumberTheory::CRC16 crc_generator(0x1021, 0xffff);
// output the index mark
shifter.add_index_address_mark();
@ -130,16 +163,7 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
shifter.add_byte(sector.sector);
uint8_t size = logarithmic_size_for_size(sector.data.size());
shifter.add_byte(size);
// header CRC
crc_generator.reset();
crc_generator.add(sector.track);
crc_generator.add(sector.side);
crc_generator.add(sector.sector);
crc_generator.add(size);
uint16_t crc_value = crc_generator.get_value();
shifter.add_byte(crc_value >> 8);
shifter.add_byte(crc_value & 0xff);
shifter.add_crc();
// gap
for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e);
@ -147,17 +171,11 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
// data
shifter.add_data_address_mark();
crc_generator.reset();
for(size_t c = 0; c < sector.data.size(); c++)
{
shifter.add_byte(sector.data[c]);
crc_generator.add(sector.data[c]);
}
// data CRC
crc_value = crc_generator.get_value();
shifter.add_byte(crc_value >> 8);
shifter.add_byte(crc_value & 0xff);
shifter.add_crc();
// gap
for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00);
@ -171,6 +189,7 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
}
Encoder::Encoder(std::vector<uint8_t> &target) :
crc_generator_(0x1021, 0xffff),
target_(target)
{}
@ -180,6 +199,13 @@ void Encoder::output_short(uint16_t value)
target_.push_back(value & 0xff);
}
void Encoder::add_crc()
{
uint16_t crc_value = crc_generator_.get_value();
add_byte(crc_value >> 8);
add_byte(crc_value & 0xff);
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors)
{
return GetTrackWithSectors<FMEncoder>(
@ -211,3 +237,186 @@ std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8
{
return std::unique_ptr<Encoder>(new FMEncoder(target));
}
#pragma mark - Parser
Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm)
{
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);
set_motor_on(true);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser(is_mfm)
{
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);
}
std::shared_ptr<Storage::Encodings::MFM::Sector> Parser::get_sector(uint8_t track, uint8_t sector)
{
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) step(direction);
}
return get_sector(sector);
}
void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
void Parser::process_index_hole()
{
index_count_++;
}
uint8_t Parser::get_next_byte()
{
bit_count_ = 0;
while(bit_count_ < 16) run_for_cycles(1);
uint8_t byte = (uint8_t)(
((shift_register_&0x0001) >> 0) |
((shift_register_&0x0004) >> 1) |
((shift_register_&0x0010) >> 2) |
((shift_register_&0x0040) >> 3) |
((shift_register_&0x0100) >> 4) |
((shift_register_&0x0400) >> 5) |
((shift_register_&0x1000) >> 6) |
((shift_register_&0x4000) >> 7));
crc_generator_.add(byte);
return byte;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> Parser::get_next_sector()
{
std::shared_ptr<Storage::Encodings::MFM::Sector> sector(new Storage::Encodings::MFM::Sector);
index_count_ = 0;
while(index_count_ < 2)
{
// look for an ID address mark
bool id_found = false;
while(!id_found)
{
run_for_cycles(1);
if(is_mfm_)
{
while(shift_register_ == Storage::Encodings::MFM::MFMSync)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMIDAddressByte)
{
crc_generator_.set_value(MFMPostSyncCRCValue);
id_found = true;
break;
}
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark)
{
crc_generator_.reset();
id_found = true;
}
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.add(MFMIDAddressByte);
sector->track = get_next_byte();
sector->side = get_next_byte();
sector->sector = get_next_byte();
uint8_t size = get_next_byte();
uint16_t header_crc = crc_generator_.get_value();
if((header_crc >> 8) != get_next_byte()) continue;
if((header_crc & 0xff) != get_next_byte()) continue;
// look for data mark
bool data_found = false;
while(!data_found)
{
run_for_cycles(1);
if(is_mfm_)
{
while(shift_register_ == Storage::Encodings::MFM::MFMSync)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMDataAddressByte)
{
crc_generator_.set_value(MFMPostSyncCRCValue);
data_found = true;
break;
}
if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr;
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark)
{
crc_generator_.reset();
data_found = true;
}
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr;
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.add(MFMDataAddressByte);
size_t data_size = (size_t)(128 << size);
sector->data.reserve(data_size);
for(size_t c = 0; c < data_size; c++)
{
sector->data.push_back(get_next_byte());
}
uint16_t data_crc = crc_generator_.get_value();
if((data_crc >> 8) != get_next_byte()) continue;
if((data_crc & 0xff) != get_next_byte()) continue;
return sector;
}
return nullptr;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> Parser::get_sector(uint8_t sector)
{
std::shared_ptr<Storage::Encodings::MFM::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->sector == sector) return first_sector;
while(1)
{
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = get_next_sector();
if(!next_sector) continue;
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}

View File

@ -12,6 +12,8 @@
#include <cstdint>
#include <vector>
#include "../Disk.hpp"
#include "../DiskController.hpp"
#include "../../../NumberTheory/CRC.hpp"
namespace Storage {
namespace Encodings {
@ -22,12 +24,13 @@ const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
const uint16_t MFMIndexAddressMark = 0x5224;
const uint16_t MFMAddressMark = 0x4489;
const uint16_t MFMIndexSync = 0x5224;
const uint16_t MFMSync = 0x4489;
const uint8_t MFMIndexAddressByte = 0xfc;
const uint8_t MFMIDAddressByte = 0xfe;
const uint8_t MFMDataAddressByte = 0xfb;
const uint8_t MFMDeletedDataAddressByte = 0xf8;
const uint16_t MFMPostSyncCRCValue = 0xcdb4;
struct Sector {
uint8_t track, side, sector;
@ -45,9 +48,11 @@ class Encoder {
virtual void add_ID_address_mark() = 0;
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();
protected:
void output_short(uint16_t value);
NumberTheory::CRC16 crc_generator_;
private:
std::vector<uint8_t> &target_;
@ -56,6 +61,37 @@ class Encoder {
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
class Parser: public Storage::Disk::Controller {
public:
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk);
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track);
/*!
Attempts to read the sector located at @c track and @c sector.
@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);
private:
Parser(bool is_mfm);
std::shared_ptr<Storage::Disk::Drive> drive;
unsigned int shift_register_;
int index_count_;
uint8_t track_;
int bit_count_;
NumberTheory::CRC16 crc_generator_;
bool is_mfm_;
void process_input_bit(int value, unsigned int cycles_since_index_hole);
void process_index_hole();
uint8_t get_next_byte();
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector();
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
};
}
}
}

View File

@ -37,6 +37,11 @@ AcornADF::AcornADF(const char *file_name) :
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
}
AcornADF::~AcornADF()
{
flush_updates();
}
unsigned int AcornADF::get_head_position_count()
{
return 80;
@ -47,12 +52,22 @@ unsigned int AcornADF::get_head_count()
return 1;
}
bool AcornADF::get_is_read_only()
{
return is_read_only_;
}
long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position)
{
return (position * 1 + head) * bytes_per_sector * sectors_per_track;
}
std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> track;
if(head >= 2) return track;
long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track;
long file_offset = get_file_offset_for_position(head, position);
fseek(file_, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
@ -75,3 +90,29 @@ std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int hea
return track;
}
void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex)
{
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);
if(sector)
{
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
}
else
{
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
parsed_track.resize(parsed_track.size() + bytes_per_sector);
}
}
std::lock_guard<std::mutex> lock_guard(file_access_mutex);
long file_offset = get_file_offset_for_position(head, position);
ensure_file_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET);
fwrite(parsed_track.data(), 1, parsed_track.size(), file_);
}

View File

@ -27,6 +27,7 @@ class AcornADF: public Disk, public Storage::FileHolder {
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const char *file_name);
~AcornADF();
enum {
ErrorNotAcornADF,
@ -35,8 +36,12 @@ class AcornADF: public Disk, public Storage::FileHolder {
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
bool get_is_read_only();
private:
void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex);
std::shared_ptr<Track> get_uncached_track_at_position(unsigned int head, unsigned int position);
long get_file_offset_for_position(unsigned int head, unsigned int position);
};
}

View File

@ -57,6 +57,7 @@ std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int h
size_t track_offset = 0;
uint8_t last_header[6];
std::unique_ptr<Encodings::MFM::Encoder> encoder = Encodings::MFM::GetMFMEncoder(segment.data);
bool did_sync = false;
while(track_offset < 6250)
{
uint8_t next_byte = (uint8_t)fgetc(file_);
@ -65,32 +66,46 @@ std::shared_ptr<Track> OricMFMDSK::get_uncached_track_at_position(unsigned int h
switch(next_byte)
{
default:
encoder->add_byte(next_byte);
break;
case 0xfe: // an ID synchronisation
{
encoder->add_ID_address_mark();
for(int byte = 0; byte < 6; byte++)
encoder->add_byte(next_byte);
if(did_sync)
{
last_header[byte] = (uint8_t)fgetc(file_);
encoder->add_byte(last_header[byte]);
track_offset++;
if(track_offset == 6250) break;
switch(next_byte)
{
default: break;
case 0xfe:
for(int byte = 0; byte < 6; byte++)
{
last_header[byte] = (uint8_t)fgetc(file_);
encoder->add_byte(last_header[byte]);
track_offset++;
if(track_offset == 6250) break;
}
break;
case 0xfb:
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++)
{
encoder->add_byte((uint8_t)fgetc(file_));
track_offset++;
if(track_offset == 6250) break;
}
break;
}
}
did_sync = false;
}
break;
case 0xfb: // a data synchronisation
encoder->add_data_address_mark();
case 0xa1: // a synchronisation mark that implies a sector or header coming
encoder->output_short(Storage::Encodings::MFM::MFMSync);
did_sync = true;
break;
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++)
{
encoder->add_byte((uint8_t)fgetc(file_));
track_offset++;
if(track_offset == 6250) break;
}
case 0xc2: // an 'ordinary' synchronisation mark
encoder->output_short(Storage::Encodings::MFM::MFMIndexSync);
break;
}
}

View File

@ -30,6 +30,11 @@ SSD::SSD(const char *file_name) :
else if(track_count_ < 80) track_count_ = 80;
}
SSD::~SSD()
{
flush_updates();
}
unsigned int SSD::get_head_position_count()
{
return track_count_;
@ -40,13 +45,22 @@ unsigned int SSD::get_head_count()
return head_count_;
}
bool SSD::get_is_read_only()
{
return is_read_only_;
}
long SSD::get_file_offset_for_position(unsigned int head, unsigned int position)
{
return (position * head_count_ + head) * 256 * 10;
}
std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> track;
if(head >= head_count_) return track;
long file_offset = (position * head_count_ + head) * 256 * 10;
fseek(file_, file_offset, SEEK_SET);
fseek(file_, get_file_offset_for_position(head, position), SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < 10; sector++)
@ -57,9 +71,12 @@ std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, un
new_sector.sector = (uint8_t)sector;
new_sector.data.resize(256);
fread(&new_sector.data[0], 1, 256, file_);
if(feof(file_))
break;
fread(new_sector.data.data(), 1, 256, file_);
// zero out if this wasn't present in the disk image; it's still appropriate to put a sector
// on disk because one will have been placed during formatting, but there's no reason to leak
// information from outside the emulated machine's world
if(feof(file_)) memset(new_sector.data.data(), 0, 256);
sectors.push_back(std::move(new_sector));
}
@ -68,3 +85,29 @@ std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, un
return track;
}
void SSD::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex)
{
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);
if(sector)
{
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
}
else
{
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
parsed_track.resize(parsed_track.size() + 256);
}
}
std::lock_guard<std::mutex> lock_guard(file_access_mutex);
long file_offset = get_file_offset_for_position(head, position);
ensure_file_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET);
fwrite(parsed_track.data(), 1, parsed_track.size(), file_);
}

View File

@ -27,6 +27,7 @@ class SSD: public Disk, public Storage::FileHolder {
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
*/
SSD(const char *file_name);
~SSD();
enum {
ErrorNotSSD,
@ -35,9 +36,13 @@ class SSD: public Disk, public Storage::FileHolder {
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
bool get_is_read_only();
private:
void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track, std::mutex &file_access_mutex);
std::shared_ptr<Track> get_uncached_track_at_position(unsigned int head, unsigned int position);
long get_file_offset_for_position(unsigned int head, unsigned int position);
unsigned int head_count_;
unsigned int track_count_;
};

View File

@ -20,6 +20,18 @@ PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr<Track> underlying_track) :
underlying_track_->seek_to(zero);
}
PCMPatchedTrack::PCMPatchedTrack(const PCMPatchedTrack &original)
{
underlying_track_.reset(original.underlying_track_->clone());
periods_ = original.periods_;
active_period_ = periods_.begin();
}
Track *PCMPatchedTrack::clone()
{
return new PCMPatchedTrack(*this);
}
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment)
{
std::shared_ptr<PCMSegmentEventSource> event_source(new PCMSegmentEventSource(segment));
@ -43,8 +55,7 @@ void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segm
// the vector may have been resized, potentially invalidating active_period_ even if
// the thing it pointed to is still the same thing. So work it out afresh.
active_period_ = periods_.begin();
while(active_period_->start_time > current_time_) active_period_++;
insertion_error_ = current_time_ - seek_to(current_time_);
}
void PCMPatchedTrack::insert_period(const Period &period)
@ -155,13 +166,18 @@ Track::Event PCMPatchedTrack::get_next_event()
else event = underlying_track_->get_next_event();
// see what time that gets us to. If it's still within the current period, return the found event
Time event_time = current_time_ + event.length - period_error;
Time event_time = current_time_ + event.length - period_error - insertion_error_;
if(event_time < active_period_->end_time)
{
current_time_ = event_time;
event.length += extra_time - period_error;
// TODO: this is spelt out in three steps because times don't necessarily do the sensible
// thing when 'negative' if intermediate result get simplified in the meantime. So fix Time.
event.length += extra_time;
event.length -= period_error;
event.length -= insertion_error_;
return event;
}
insertion_error_.set_zero();
// otherwise move time back to the end of the outgoing period, accumulating the error into
// extra_time, and advance the extra period
@ -196,15 +212,22 @@ Track::Event PCMPatchedTrack::get_next_event()
Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole)
{
// start at the beginning and continue while segments start after the time sought
// start at the beginning and continue while segments end before reaching the time sought
active_period_ = periods_.begin();
while(active_period_->start_time > time_since_index_hole) active_period_++;
while(active_period_->end_time < time_since_index_hole) active_period_++;
// allow whatever storage represents the period found to perform its seek
if(active_period_->event_source)
return active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time;
current_time_ = active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time;
else
return underlying_track_->seek_to(time_since_index_hole);
current_time_ = underlying_track_->seek_to(time_since_index_hole);
return current_time_;
}
PCMPatchedTrack::Period::Period(const Period &original) :
start_time(original.start_time), end_time(original.end_time), segment_start_time(original.segment_start_time)
{
if(original.event_source) event_source.reset(new PCMSegmentEventSource(*original.event_source));
}
void PCMPatchedTrack::Period::push_start_to_time(const Storage::Time &new_start_time)

View File

@ -26,6 +26,11 @@ class PCMPatchedTrack: public Track {
*/
PCMPatchedTrack(std::shared_ptr<Track> underlying_track);
/*!
Copy constructor, for Track.
*/
PCMPatchedTrack(const PCMPatchedTrack &);
/*!
Replaces whatever is currently on the track from @c start_position to @c start_position + segment length
with the contents of @c segment.
@ -35,6 +40,7 @@ class PCMPatchedTrack: public Track {
// To satisfy Storage::Disk::Track
Event get_next_event();
Time seek_to(const Time &time_since_index_hole);
Track *clone();
private:
std::shared_ptr<Track> underlying_track_;
@ -49,10 +55,11 @@ class PCMPatchedTrack: public Track {
Period(const Time &start_time, const Time &end_time, const Time &segment_start_time, std::shared_ptr<PCMSegmentEventSource> event_source) :
start_time(start_time), end_time(end_time), segment_start_time(segment_start_time), event_source(event_source) {}
Period(const Period &);
};
std::vector<Period> periods_;
std::vector<Period>::iterator active_period_;
Time current_time_;
Time current_time_, insertion_error_;
void insert_period(const Period &period);
};

View File

@ -11,24 +11,34 @@
using namespace Storage::Disk;
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) :
segment_(segment)
segment_(new PCMSegment(segment))
{
// add an extra bit of storage at the bottom if one is going to be needed;
// events returned are going to be in integral multiples of the length of a bit
// other than the very first and very last which will include a half bit length
if(segment_.length_of_a_bit.length&1)
if(segment_->length_of_a_bit.length&1)
{
segment_.length_of_a_bit.length <<= 1;
segment_.length_of_a_bit.clock_rate <<= 1;
segment_->length_of_a_bit.length <<= 1;
segment_->length_of_a_bit.clock_rate <<= 1;
}
// load up the clock rate once only
next_event_.length.clock_rate = segment_.length_of_a_bit.clock_rate;
next_event_.length.clock_rate = segment_->length_of_a_bit.clock_rate;
// set initial conditions
reset();
}
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegmentEventSource &original)
{
// share underlying data with the original
segment_ = original.segment_;
// load up the clock rate and set initial conditions
next_event_.length.clock_rate = segment_->length_of_a_bit.clock_rate;
reset();
}
void PCMSegmentEventSource::reset()
{
// start with the first bit to be considered the zeroth, and assume that it'll be
@ -45,15 +55,15 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
// if starting from the beginning, pull half a bit backward, as if the initial bit
// is set, it should be in the centre of its window
next_event_.length.length = bit_pointer_ ? 0 : -(segment_.length_of_a_bit.length >> 1);
next_event_.length.length = bit_pointer_ ? 0 : -(segment_->length_of_a_bit.length >> 1);
// search for the next bit that is set, if any
const uint8_t *segment_data = segment_.data.data();
while(bit_pointer_ < segment_.number_of_bits)
const uint8_t *segment_data = segment_->data.data();
while(bit_pointer_ < segment_->number_of_bits)
{
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
bit_pointer_++; // so this always points one beyond the most recent bit returned
next_event_.length.length += segment_.length_of_a_bit.length;
next_event_.length.length += segment_->length_of_a_bit.length;
// if this bit is set, return the event
if(bit) return next_event_;
@ -66,9 +76,9 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
// allow an extra half bit's length to run from the position of the potential final transition
// event to the end of the segment. Otherwise don't allow any extra time, as it's already
// been consumed
if(initial_bit_pointer <= segment_.number_of_bits)
if(initial_bit_pointer <= segment_->number_of_bits)
{
next_event_.length.length += (segment_.length_of_a_bit.length >> 1);
next_event_.length.length += (segment_->length_of_a_bit.length >> 1);
bit_pointer_++;
}
return next_event_;
@ -76,7 +86,7 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
Storage::Time PCMSegmentEventSource::get_length()
{
return segment_.length_of_a_bit * segment_.number_of_bits;
return segment_->length_of_a_bit * segment_->number_of_bits;
}
Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
@ -86,7 +96,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
if(time_from_start >= length)
{
next_event_.type = Track::Event::IndexHole;
bit_pointer_ = segment_.number_of_bits+1;
bit_pointer_ = segment_->number_of_bits+1;
return length;
}
@ -94,7 +104,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
next_event_.type = Track::Event::FluxTransition;
// test for requested time being before the first bit
Time half_bit_length = segment_.length_of_a_bit;
Time half_bit_length = segment_->length_of_a_bit;
half_bit_length.length >>= 1;
if(time_from_start < half_bit_length)
{
@ -107,8 +117,8 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
// bit_pointer_ always records _the next bit_ that might trigger an event,
// so should be one beyond the one reached by a seek.
Time relative_time = time_from_start - half_bit_length;
bit_pointer_ = 1 + (relative_time / segment_.length_of_a_bit).get_unsigned_int();
bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get_unsigned_int();
// map up to the correct amount of time
return half_bit_length + segment_.length_of_a_bit * (unsigned int)(bit_pointer_ - 1);
return half_bit_length + segment_->length_of_a_bit * (unsigned int)(bit_pointer_ - 1);
}

View File

@ -42,7 +42,13 @@ class PCMSegmentEventSource {
Constructs a @c PCMSegmentEventSource that will derive events from @c segment.
The event source is initially @c reset.
*/
PCMSegmentEventSource(const PCMSegment &segment);
PCMSegmentEventSource(const PCMSegment &);
/*!
Copy constructor; produces a segment event source with the same underlying segment
but a unique pointer into it.
*/
PCMSegmentEventSource(const PCMSegmentEventSource &);
/*!
@returns the next event that will occur in this event stream.
@ -69,7 +75,7 @@ class PCMSegmentEventSource {
Time get_length();
private:
PCMSegment segment_;
std::shared_ptr<PCMSegment> segment_;
size_t bit_pointer_;
Track::Event next_event_;
};

View File

@ -47,6 +47,16 @@ PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack()
segment_event_sources_.emplace_back(length_adjusted_segment);
}
PCMTrack::PCMTrack(const PCMTrack &original) : PCMTrack()
{
segment_event_sources_ = original.segment_event_sources_;
}
Track *PCMTrack::clone()
{
return new PCMTrack(*this);
}
Track::Event PCMTrack::get_next_event()
{
// ask the current segment for a new event

View File

@ -28,17 +28,23 @@ class PCMTrack: public Track {
/*!
Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates.
*/
PCMTrack(const std::vector<PCMSegment> &segments);
PCMTrack(const std::vector<PCMSegment> &);
/*!
Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate.
The segment's @c length_of_a_bit will be ignored and therefore need not be filled in.
*/
PCMTrack(const PCMSegment &segment);
PCMTrack(const PCMSegment &);
/*!
Copy constructor; required for Tracks in order to support modifiable disks.
*/
PCMTrack(const PCMTrack &);
// as per @c Track
Event get_next_event();
Time seek_to(const Time &time_since_index_hole);
Track *clone();
private:
// storage for the segments that describe this track

View File

@ -20,7 +20,13 @@ FileHolder::~FileHolder()
FileHolder::FileHolder(const char *file_name) : file_(nullptr)
{
stat(file_name, &file_stats_);
file_ = fopen(file_name, "rb");
is_read_only_ = false;
file_ = fopen(file_name, "rb+");
if(!file_)
{
is_read_only_ = true;
file_ = fopen(file_name, "rb");
}
if(!file_) throw ErrorCantOpen;
}
@ -79,3 +85,16 @@ uint16_t FileHolder::fgetc16be()
return result;
}
void FileHolder::ensure_file_is_at_least_length(long length)
{
fseek(file_, 0, SEEK_END);
long bytes_to_write = length - ftell(file_);
if(bytes_to_write > 0)
{
uint8_t *empty = new uint8_t[bytes_to_write];
memset(empty, 0, bytes_to_write);
fwrite(empty, sizeof(uint8_t), (size_t)bytes_to_write, file_);
delete[] empty;
}
}

View File

@ -65,8 +65,15 @@ class FileHolder {
*/
uint16_t fgetc16be();
/*!
Ensures the file is at least @c length bytes long, appending 0s until it is
if necessary.
*/
void ensure_file_is_at_least_length(long length);
FILE *file_;
struct stat file_stats_;
bool is_read_only_;
};
}

View File

@ -89,30 +89,78 @@ struct Time {
inline Time operator + (const Time &other) const
{
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
result_length = (uint64_t)length + (uint64_t)other.length;
result_clock_rate = clock_rate;
}
else
{
result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
return Time(result_length, result_clock_rate);
}
inline Time &operator += (const Time &other)
{
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
result_length = (uint64_t)length + (uint64_t)other.length;
result_clock_rate = (uint64_t)clock_rate;
}
else
{
result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
install_result(result_length, result_clock_rate);
return *this;
}
inline Time operator - (const Time &other) const
{
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
result_length = (uint64_t)length - (uint64_t)other.length;
result_clock_rate = clock_rate;
}
else
{
result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
return Time(result_length, result_clock_rate);
}
inline Time operator -= (const Time &other)
{
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
if(!other.length) return *this;
uint64_t result_length;
uint64_t result_clock_rate;
if(clock_rate == other.clock_rate)
{
result_length = (uint64_t)length - (uint64_t)other.length;
result_clock_rate = (uint64_t)clock_rate;
}
else
{
result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
}
install_result(result_length, result_clock_rate);
return *this;
}
@ -198,6 +246,12 @@ struct Time {
inline void install_result(uint64_t long_length, uint64_t long_clock_rate)
{
// TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy.
if(!long_length)
{
length = 0;
clock_rate = 1;
return;
}
while(!(long_length&1) && !(long_clock_rate&1))
{