mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-18 17:06:15 +00:00
Merge pull request #460 from TomHarte/RWTSAcceleration
Introduces optional quick loading of Apple DOS 3.3 programs
This commit is contained in:
commit
a1c60152d4
@ -260,3 +260,7 @@ void DiskII::set_activity_observer(Activity::Observer *observer) {
|
||||
drives_[0].set_activity_observer(observer, "Drive 1", true);
|
||||
drives_[1].set_activity_observer(observer, "Drive 2", true);
|
||||
}
|
||||
|
||||
Storage::Disk::Drive &DiskII::get_drive(int index) {
|
||||
return drives_[index];
|
||||
}
|
||||
|
@ -81,6 +81,10 @@ class DiskII:
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// Returns the Storage::Disk::Drive in use for drive @c index.
|
||||
// *NOT FOR HARDWARE EMULATION USAGE*.
|
||||
Storage::Disk::Drive &get_drive(int index);
|
||||
|
||||
private:
|
||||
enum class Control {
|
||||
P0, P1, P2, P3,
|
||||
|
@ -24,20 +24,29 @@
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||
options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload"));
|
||||
return options;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public AppleII::Machine,
|
||||
@ -123,6 +132,10 @@ class ConcreteMachine:
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
AppleII::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
|
||||
}
|
||||
|
||||
// MARK: - Memory Map
|
||||
struct MemoryBlock {
|
||||
uint8_t *read_pointer = nullptr;
|
||||
@ -157,6 +170,9 @@ class ConcreteMachine:
|
||||
// MARK - typing
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
|
||||
// MARK - quick loading
|
||||
bool should_load_quickly_ = false;
|
||||
|
||||
public:
|
||||
ConcreteMachine():
|
||||
m6502_(*this),
|
||||
@ -205,7 +221,7 @@ class ConcreteMachine:
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) {
|
||||
++ cycles_since_video_update_;
|
||||
++ cycles_since_card_update_;
|
||||
cycles_since_audio_update_ += Cycles(7);
|
||||
@ -232,21 +248,101 @@ class ConcreteMachine:
|
||||
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
|
||||
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
|
||||
*/
|
||||
uint16_t accessed_address = address;
|
||||
MemoryBlock *block = nullptr;
|
||||
if(address < 0x200) block = &memory_blocks_[0];
|
||||
else if(address < 0xc000) {
|
||||
if(address < 0x6000 && !isReadOperation(operation)) update_video();
|
||||
block = &memory_blocks_[1];
|
||||
address -= 0x200;
|
||||
accessed_address -= 0x200;
|
||||
}
|
||||
else if(address < 0xd000) block = nullptr;
|
||||
else if(address < 0xe000) {block = &memory_blocks_[2]; address -= 0xd000; }
|
||||
else { block = &memory_blocks_[3]; address -= 0xe000; }
|
||||
else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; }
|
||||
else { block = &memory_blocks_[3]; accessed_address -= 0xe000; }
|
||||
|
||||
bool has_updated_cards = false;
|
||||
if(block) {
|
||||
if(isReadOperation(operation)) *value = block->read_pointer[address];
|
||||
else if(block->write_pointer) block->write_pointer[address] = *value;
|
||||
if(isReadOperation(operation)) *value = block->read_pointer[accessed_address];
|
||||
else if(block->write_pointer) block->write_pointer[accessed_address] = *value;
|
||||
|
||||
if(should_load_quickly_) {
|
||||
// Check for a prima facie entry into RWTS.
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) {
|
||||
// Grab the IO control block address for inspection.
|
||||
uint16_t io_control_block_address =
|
||||
static_cast<uint16_t>(
|
||||
(m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) |
|
||||
m6502_.get_value_of_register(CPU::MOS6502::Register::Y)
|
||||
);
|
||||
|
||||
// Verify that this is table type one, for execution on card six,
|
||||
// against drive 1 or 2, and that the command is either a seek or a sector read.
|
||||
if(
|
||||
ram_[io_control_block_address+0x00] == 0x01 &&
|
||||
ram_[io_control_block_address+0x01] == 0x60 &&
|
||||
ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 &&
|
||||
ram_[io_control_block_address+0x0c] < 2
|
||||
) {
|
||||
const uint8_t iob_track = ram_[io_control_block_address+4];
|
||||
const uint8_t iob_sector = ram_[io_control_block_address+5];
|
||||
const uint8_t iob_drive = ram_[io_control_block_address+2] - 1;
|
||||
|
||||
// Get the track identified and store the new head position.
|
||||
auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track));
|
||||
|
||||
// DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2
|
||||
// is also kept in that Disk II card's screen hole.
|
||||
ram_[0x478] = iob_track;
|
||||
if(ram_[io_control_block_address+0x02] == 1) {
|
||||
ram_[0x47e] = iob_track;
|
||||
} else {
|
||||
ram_[0x4fe] = iob_track;
|
||||
}
|
||||
|
||||
// Check whether this is a read, not merely a seek.
|
||||
if(ram_[io_control_block_address+0x0c] == 1) {
|
||||
// Apple the DOS 3.3 formula to map the requested logical sector to a physical sector.
|
||||
const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15);
|
||||
|
||||
// Parse the entire track. TODO: cache these.
|
||||
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000)));
|
||||
|
||||
bool found_sector = false;
|
||||
for(const auto &pair: sector_map) {
|
||||
if(pair.second.address.sector == physical_sector) {
|
||||
found_sector = true;
|
||||
|
||||
// Copy the sector contents to their destination.
|
||||
uint16_t target = static_cast<uint16_t>(
|
||||
ram_[io_control_block_address+8] |
|
||||
(ram_[io_control_block_address+9] << 8)
|
||||
);
|
||||
|
||||
for(size_t c = 0; c < 256; ++c) {
|
||||
ram_[target] = pair.second.data[c];
|
||||
++target;
|
||||
}
|
||||
|
||||
// Set no error encountered.
|
||||
ram_[io_control_block_address + 0xd] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found_sector) {
|
||||
// Set no error in the flags register too, and RTS.
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
|
||||
*value = 0x60;
|
||||
}
|
||||
} else {
|
||||
// No error encountered; RTS.
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
|
||||
*value = 0x60;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume a vapour read unless it turns out otherwise; this is a little
|
||||
// wasteful but works for now.
|
||||
@ -279,6 +375,12 @@ class ConcreteMachine:
|
||||
*value = keyboard_input_;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc061: // Switch input 0.
|
||||
case 0xc062: // Switch input 1.
|
||||
case 0xc063: // Switch input 2.
|
||||
*value &= 0x7f;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Write-only switches.
|
||||
@ -480,8 +582,9 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(!media.disks.empty() && cards_[5]) {
|
||||
dynamic_cast<AppleII::DiskIICard *>(cards_[5].get())->set_disk(media.disks[0], 0);
|
||||
if(!media.disks.empty()) {
|
||||
auto diskii = diskii_card();
|
||||
if(diskii) diskii->set_disk(media.disks[0], 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -492,6 +595,30 @@ class ConcreteMachine:
|
||||
if(card) card->set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Options
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return AppleII::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
should_load_quickly_ = quickload;
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
return selection_set;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -9,8 +9,16 @@
|
||||
#ifndef AppleII_hpp
|
||||
#define AppleII_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
/// @returns The options available for an Apple II.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
@ -57,3 +57,7 @@ void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component,
|
||||
diskii_clocking_preference_ = preference;
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
|
||||
}
|
||||
|
||||
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
|
||||
return diskii_.get_drive(drive);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
void set_activity_observer(Activity::Observer *observer) override;
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
Storage::Disk::Drive &get_drive(int drive);
|
||||
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Electron {
|
||||
|
@ -129,6 +129,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() {
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
|
||||
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), AppleII::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));
|
||||
|
@ -612,6 +612,7 @@
|
||||
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
||||
@ -1353,6 +1354,7 @@
|
||||
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
|
||||
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
|
||||
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
|
||||
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
||||
@ -1986,6 +1988,7 @@
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
|
||||
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
|
||||
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
|
||||
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */,
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
|
||||
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
|
||||
@ -3277,6 +3280,7 @@
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */,
|
||||
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||
@ -4036,6 +4040,14 @@
|
||||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4BC5FC2F20CDDDEE00410AA0 /* Base */,
|
||||
);
|
||||
name = AppleIIOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
49
OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
Normal file
49
OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Accelerate DOS 3.3" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="175" y="30"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
@ -183,7 +183,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_targets.front()->machine) {
|
||||
case Analyser::Machine::AmstradCPC: return nil;
|
||||
case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
|
@ -74,17 +74,15 @@ std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) {
|
||||
|
||||
// In either case below, the code aims for exactly 50,000 bits per track.
|
||||
if(sectors_per_track_ == 16) {
|
||||
// Write gap 1.
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(16);
|
||||
|
||||
// Write the sectors.
|
||||
for(uint8_t c = 0; c < 16; ++c) {
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(10);
|
||||
segment += Encodings::AppleGCR::header(254, track, c);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(10);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(7); // Gap 2: 7 sync words.
|
||||
segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]);
|
||||
}
|
||||
|
||||
// Pad if necessary.
|
||||
if(segment.number_of_bits < 50000) {
|
||||
segment += Encodings::AppleGCR::six_and_two_sync((50000 - segment.number_of_bits) / 10);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(16); // Gap 3: 16 sync words.
|
||||
}
|
||||
} else {
|
||||
// TODO: 5 and 3, 13-sector format. If DSK actually supports it?
|
||||
|
@ -77,6 +77,18 @@ void Drive::step(HeadPosition offset) {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Drive::step_to(HeadPosition offset) {
|
||||
HeadPosition old_head_position = head_position_;
|
||||
head_position_ = std::max(offset, HeadPosition(0));
|
||||
|
||||
if(head_position_ != old_head_position) {
|
||||
track_ = nullptr;
|
||||
setup_track();
|
||||
}
|
||||
|
||||
return track_;
|
||||
}
|
||||
|
||||
void Drive::set_head(int head) {
|
||||
head = std::min(head, available_heads_ - 1);
|
||||
if(head != head_) {
|
||||
|
@ -127,6 +127,17 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
/// The caller can specify whether to add an LED based on disk motor.
|
||||
void set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led);
|
||||
|
||||
/*!
|
||||
Attempts to step to the specified offset and returns the track there if one exists; an uninitialised
|
||||
track otherwise.
|
||||
|
||||
This is unambiguously **NOT A REALISTIC DRIVE FUNCTION**; real drives cannot step to a given offset.
|
||||
So it is **NOT FOR HARDWARE EMULATION USAGE**.
|
||||
|
||||
It's for the benefit of user-optional fast-loading mechanisms **ONLY**.
|
||||
*/
|
||||
std::shared_ptr<Track> step_to(HeadPosition offset);
|
||||
|
||||
private:
|
||||
// Drives contain an entire disk; from that a certain track
|
||||
// will be currently under the head.
|
||||
|
@ -46,16 +46,18 @@ void PCMSegmentEventSource::reset() {
|
||||
PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) {
|
||||
if(!rhs.number_of_bits) return *this;
|
||||
|
||||
assert(((rhs.number_of_bits+7) >> 3) == rhs.data.size());
|
||||
|
||||
if(number_of_bits&7) {
|
||||
auto target_number_of_bits = number_of_bits + rhs.number_of_bits;
|
||||
data.resize((target_number_of_bits + 7) >> 3);
|
||||
|
||||
std::size_t first_byte = number_of_bits >> 3;
|
||||
|
||||
int shift = number_of_bits&7;
|
||||
const int shift = number_of_bits&7;
|
||||
for(std::size_t source = 0; source < rhs.data.size(); ++source) {
|
||||
data[first_byte + source] |= rhs.data[source] >> shift;
|
||||
if(source*8 + static_cast<std::size_t>(shift) < rhs.number_of_bits)
|
||||
if(source*8 + static_cast<std::size_t>(8 - shift) < rhs.number_of_bits)
|
||||
data[first_byte + source + 1] = static_cast<uint8_t>(rhs.data[source] << (8-shift));
|
||||
}
|
||||
|
||||
@ -65,6 +67,8 @@ PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) {
|
||||
number_of_bits += rhs.number_of_bits;
|
||||
}
|
||||
|
||||
assert(((number_of_bits+7) >> 3) == data.size());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -39,22 +39,22 @@ class HeadPosition {
|
||||
position_ += rhs.position_;
|
||||
return *this;
|
||||
}
|
||||
bool operator ==(const HeadPosition &rhs) {
|
||||
bool operator ==(const HeadPosition &rhs) const {
|
||||
return position_ == rhs.position_;
|
||||
}
|
||||
bool operator !=(const HeadPosition &rhs) {
|
||||
bool operator !=(const HeadPosition &rhs) const {
|
||||
return position_ != rhs.position_;
|
||||
}
|
||||
bool operator <(const HeadPosition &rhs) {
|
||||
bool operator <(const HeadPosition &rhs) const {
|
||||
return position_ < rhs.position_;
|
||||
}
|
||||
bool operator <=(const HeadPosition &rhs) {
|
||||
bool operator <=(const HeadPosition &rhs) const {
|
||||
return position_ <= rhs.position_;
|
||||
}
|
||||
bool operator >(const HeadPosition &rhs) {
|
||||
bool operator >(const HeadPosition &rhs) const {
|
||||
return position_ > rhs.position_;
|
||||
}
|
||||
bool operator >=(const HeadPosition &rhs) {
|
||||
bool operator >=(const HeadPosition &rhs) const {
|
||||
return position_ >= rhs.position_;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user