From b447f5f17426ff3f0dc87512febd46e313f86f45 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 12:38:37 -0400 Subject: [PATCH 01/76] Starts adding the Spectrum to the static analyser. --- Analyser/Static/StaticAnalyser.cpp | 48 ++++++++++--------- Analyser/Static/ZXSpectrum/StaticAnalyser.cpp | 13 +++++ Analyser/Static/ZXSpectrum/StaticAnalyser.hpp | 26 ++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 16 +++++++ Storage/TargetPlatforms.hpp | 7 +-- 5 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 Analyser/Static/ZXSpectrum/StaticAnalyser.cpp create mode 100644 Analyser/Static/ZXSpectrum/StaticAnalyser.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 2b3cf18af..b81b4a9df 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -28,6 +28,7 @@ #include "Oric/StaticAnalyser.hpp" #include "Sega/StaticAnalyser.hpp" #include "ZX8081/StaticAnalyser.hpp" +#include "ZXSpectrum/StaticAnalyser.hpp" // Cartridges #include "../../Storage/Cartridge/Formats/BinaryDump.hpp" @@ -116,7 +117,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump) Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT - Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL + Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("d64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // D64 Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT @@ -168,7 +169,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format( "rom", result.cartridges, Cartridge::BinaryDump, - TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM + TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX) // ROM Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD @@ -177,7 +178,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX - Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX + Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) Format("woz", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // WOZ @@ -204,27 +205,28 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { // Hand off to platform-specific determination of whether these things are actually compatible and, // if so, how to load them. - #define Append(x) {\ - auto new_targets = x::GetTargets(media, file_name, potential_platforms);\ - std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\ - } - if(potential_platforms & TargetPlatform::Acorn) Append(Acorn); - if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC); - if(potential_platforms & TargetPlatform::AppleII) Append(AppleII); - if(potential_platforms & TargetPlatform::AppleIIgs) Append(AppleIIgs); - if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600); - if(potential_platforms & TargetPlatform::AtariST) Append(AtariST); - if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco); - if(potential_platforms & TargetPlatform::Commodore) Append(Commodore); - if(potential_platforms & TargetPlatform::DiskII) Append(DiskII); - if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh); - if(potential_platforms & TargetPlatform::MSX) Append(MSX); - if(potential_platforms & TargetPlatform::Oric) Append(Oric); - if(potential_platforms & TargetPlatform::Sega) Append(Sega); - if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081); - #undef Append +#define Append(x) if(potential_platforms & TargetPlatform::x) {\ + auto new_targets = x::GetTargets(media, file_name, potential_platforms);\ + std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\ +} + Append(Acorn); + Append(AmstradCPC); + Append(AppleII); + Append(AppleIIgs); + Append(Atari2600); + Append(AtariST); + Append(Coleco); + Append(Commodore); + Append(DiskII); + Append(Macintosh); + Append(MSX); + Append(Oric); + Append(Sega); + Append(ZX8081); + Append(ZXSpectrum); +#undef Append - // Reset any tapes to their initial position + // Reset any tapes to their initial position. for(const auto &target : targets) { for(auto &tape : target->media.tapes) { tape->reset(); diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp new file mode 100644 index 000000000..bd9c0acf8 --- /dev/null +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -0,0 +1,13 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { + return {}; +} diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.hpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.hpp new file mode 100644 index 000000000..756c28ad0 --- /dev/null +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.hpp @@ -0,0 +1,26 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp +#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" +#include "../../../Storage/TargetPlatforms.hpp" +#include + +namespace Analyser { +namespace Static { +namespace ZXSpectrum { + +TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); + +} +} +} + +#endif /* StaticAnalyser_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 03d6f0e1b..0d4925e16 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -125,6 +125,8 @@ 4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; 4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; + 4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; + 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1053,6 +1055,8 @@ 4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = ""; }; 4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = ""; }; 4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = ""; }; + 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4B0F1BB12602645900B85C66 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2150,6 +2154,15 @@ name = 9918; sourceTree = ""; }; + 4B0F1BAF2602645900B85C66 /* ZXSpectrum */ = { + isa = PBXGroup; + children = ( + 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */, + 4B0F1BB12602645900B85C66 /* StaticAnalyser.hpp */, + ); + path = ZXSpectrum; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -3089,6 +3102,7 @@ 4B8944F6201967B4007DE474 /* Oric */, 4B7F1894215486A100388727 /* Sega */, 4B894504201967B4007DE474 /* ZX8081 */, + 4B0F1BAF2602645900B85C66 /* ZXSpectrum */, ); path = Static; sourceTree = ""; @@ -5214,6 +5228,7 @@ 4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B302185208A550100773308 /* DiskII.cpp in Sources */, + 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, 4B89452D201967B4007DE474 /* Tape.cpp in Sources */, 4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */, @@ -5400,6 +5415,7 @@ 4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */, 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */, 4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */, + 4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */, 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */, 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index 91654ae23..298e2f816 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -26,7 +26,7 @@ enum Type: IntType { BBCMaster = 1 << 7, BBCModelA = 1 << 8, BBCModelB = 1 << 9, - ColecoVision = 1 << 10, + Coleco = 1 << 10, Commodore = 1 << 11, DiskII = 1 << 12, Sega = 1 << 13, @@ -35,12 +35,13 @@ enum Type: IntType { Oric = 1 << 16, ZX80 = 1 << 17, ZX81 = 1 << 18, + ZXSpectrum = 1 << 19, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB, ZX8081 = ZX80 | ZX81, - AllCartridge = Atari2600 | AcornElectron | ColecoVision | MSX, + AllCartridge = Atari2600 | AcornElectron | Coleco | MSX, AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX, // TODO: | AtariST - AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX, + AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX | ZXSpectrum, }; class TypeDistinguisher { From e53586df1d39c883396aea85f4eb4af310d019d3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:09:44 -0400 Subject: [PATCH 02/76] Adds tape-file static analysis for a hypothetical ZX Spectrum. --- Analyser/Machines.hpp | 3 +- Analyser/Static/ZXSpectrum/StaticAnalyser.cpp | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index 2f36858bf..7159408f8 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -24,7 +24,8 @@ enum class Machine { MSX, Oric, Vic20, - ZX8081 + ZX8081, + ZXSpectrum, }; } diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp index bd9c0acf8..16b772740 100644 --- a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -8,6 +8,48 @@ #include "StaticAnalyser.hpp" -Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { - return {}; +#include "../../../Storage/Tape/Parsers/Spectrum.hpp" + +namespace { + +bool IsSpectrumTape(const std::shared_ptr &tape) { + using Parser = Storage::Tape::ZXSpectrum::Parser; + Parser parser(Parser::MachineType::ZXSpectrum); + + while(true) { + const auto block = parser.find_block(tape); + if(!block) break; + + // Check for a Spectrum header block. + if(block->type == 0x00) { + return true; + } + } + + return false; +} + +} + +Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { + TargetList destination; + auto target = std::make_unique(); + target->confidence = 0.5; + + if(!media.tapes.empty()) { + bool has_spectrum_tape = false; + for(auto &tape: media.tapes) { + has_spectrum_tape |= IsSpectrumTape(tape); + } + + if(has_spectrum_tape) { + target->media.tapes = media.tapes; + } + } + + // If any media survived, add the target. + if(!target->media.empty()) + destination.push_back(std::move(target)); + + return destination; } From 0ddf09ac0f611f5e57fc395de463f1b3dfd1dba4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:16:57 -0400 Subject: [PATCH 03/76] Adds the +2a/+3 ROM. --- ROMImages/ZXSpectrum/plus3.rom | Bin 0 -> 65536 bytes ROMImages/ZXSpectrum/readme.txt | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 ROMImages/ZXSpectrum/plus3.rom create mode 100644 ROMImages/ZXSpectrum/readme.txt diff --git a/ROMImages/ZXSpectrum/plus3.rom b/ROMImages/ZXSpectrum/plus3.rom new file mode 100644 index 0000000000000000000000000000000000000000..ba17cabc3c390575e88cb9b60aca222629146575 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/ROMImages/ZXSpectrum/readme.txt b/ROMImages/ZXSpectrum/readme.txt new file mode 100644 index 000000000..63a56a9eb --- /dev/null +++ b/ROMImages/ZXSpectrum/readme.txt @@ -0,0 +1,11 @@ +Per https://groups.google.com/g/comp.sys.amstrad.8bit/c/HtpBU2Bzv_U/m/HhNDSU3MksAJ : + +"Amstrad are happy for emulator writers to include images of our copyrighted +code as long as the (c)opyright messages are not altered and we appreciate +it if the program/manual includes a note to the effect that "Amstrad have +kindly given their permission for the redistribution of their copyrighted +material but retain that copyright"." + +With that in mind, Amstrad have kindly given their permission for the redistribution of their copyrighted material but retain that copyright. Material expected here, copyright Amstrad: + +plus3.rom — the +2a/+3 ROM file, 64kb in size. From b63ca16ce2de686216f9db5dafe74fcea3997531 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:40:29 -0400 Subject: [PATCH 04/76] Attempts to hatch a Sinclair namespace. --- Analyser/Static/ZXSpectrum/StaticAnalyser.cpp | 2 +- Machines/{ => Sinclair}/ZX8081/Keyboard.cpp | 0 Machines/{ => Sinclair}/ZX8081/Keyboard.hpp | 0 Machines/{ => Sinclair}/ZX8081/Video.cpp | 0 Machines/{ => Sinclair}/ZX8081/Video.hpp | 0 Machines/{ => Sinclair}/ZX8081/ZX8081.cpp | 30 +++++----- Machines/{ => Sinclair}/ZX8081/ZX8081.hpp | 10 ++-- Machines/Utility/MachineForTarget.cpp | 6 +- .../Clock Signal.xcodeproj/project.pbxproj | 60 +++++++++---------- .../Clock Signal/Machine/Wrappers/CSZX8081.mm | 4 +- OSBindings/Qt/ClockSignal.pro | 5 +- OSBindings/SDL/SConstruct | 3 +- 12 files changed, 61 insertions(+), 59 deletions(-) rename Machines/{ => Sinclair}/ZX8081/Keyboard.cpp (100%) rename Machines/{ => Sinclair}/ZX8081/Keyboard.hpp (100%) rename Machines/{ => Sinclair}/ZX8081/Video.cpp (100%) rename Machines/{ => Sinclair}/ZX8081/Video.hpp (100%) rename Machines/{ => Sinclair}/ZX8081/ZX8081.cpp (95%) rename Machines/{ => Sinclair}/ZX8081/ZX8081.hpp (84%) diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp index 16b772740..82456be58 100644 --- a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -33,7 +33,7 @@ bool IsSpectrumTape(const std::shared_ptr &tape) { Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { TargetList destination; - auto target = std::make_unique(); + auto target = std::make_unique(Machine::ZXSpectrum); target->confidence = 0.5; if(!media.tapes.empty()) { diff --git a/Machines/ZX8081/Keyboard.cpp b/Machines/Sinclair/ZX8081/Keyboard.cpp similarity index 100% rename from Machines/ZX8081/Keyboard.cpp rename to Machines/Sinclair/ZX8081/Keyboard.cpp diff --git a/Machines/ZX8081/Keyboard.hpp b/Machines/Sinclair/ZX8081/Keyboard.hpp similarity index 100% rename from Machines/ZX8081/Keyboard.hpp rename to Machines/Sinclair/ZX8081/Keyboard.hpp diff --git a/Machines/ZX8081/Video.cpp b/Machines/Sinclair/ZX8081/Video.cpp similarity index 100% rename from Machines/ZX8081/Video.cpp rename to Machines/Sinclair/ZX8081/Video.cpp diff --git a/Machines/ZX8081/Video.hpp b/Machines/Sinclair/ZX8081/Video.hpp similarity index 100% rename from Machines/ZX8081/Video.hpp rename to Machines/Sinclair/ZX8081/Video.hpp diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp similarity index 95% rename from Machines/ZX8081/ZX8081.cpp rename to Machines/Sinclair/ZX8081/ZX8081.cpp index be604d1be..6c3526b3b 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -8,21 +8,21 @@ #include "ZX8081.hpp" -#include "../MachineTypes.hpp" +#include "../../MachineTypes.hpp" -#include "../../Components/AY38910/AY38910.hpp" -#include "../../Processors/Z80/Z80.hpp" -#include "../../Storage/Tape/Tape.hpp" -#include "../../Storage/Tape/Parsers/ZX8081.hpp" +#include "../../../Components/AY38910/AY38910.hpp" +#include "../../../Processors/Z80/Z80.hpp" +#include "../../../Storage/Tape/Tape.hpp" +#include "../../../Storage/Tape/Parsers/ZX8081.hpp" -#include "../../ClockReceiver/ForceInline.hpp" +#include "../../../ClockReceiver/ForceInline.hpp" -#include "../Utility/MemoryFuzzer.hpp" -#include "../Utility/Typer.hpp" +#include "../../Utility/MemoryFuzzer.hpp" +#include "../../Utility/Typer.hpp" -#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" -#include "../../Analyser/Static/ZX8081/Target.hpp" +#include "../../../Analyser/Static/ZX8081/Target.hpp" #include "Keyboard.hpp" #include "Video.hpp" @@ -42,6 +42,7 @@ namespace { // 7FFFh.W PSG index // 7FFEh.R/W PSG data +namespace Sinclair{ namespace ZX8081 { enum ROMType: uint8_t { @@ -515,17 +516,18 @@ template class ConcreteMachine: } }; +} } -using namespace ZX8081; +using namespace Sinclair::ZX8081; // See header; constructs and returns an instance of the ZX80 or 81. Machine *Machine::ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - const Analyser::Static::ZX8081::Target *const zx_target = dynamic_cast(target); + const auto zx_target = dynamic_cast(target); // Instantiate the correct type of machine. - if(zx_target->is_ZX81) return new ZX8081::ConcreteMachine(*zx_target, rom_fetcher); - else return new ZX8081::ConcreteMachine(*zx_target, rom_fetcher); + if(zx_target->is_ZX81) return new ConcreteMachine(*zx_target, rom_fetcher); + else return new ConcreteMachine(*zx_target, rom_fetcher); } Machine::~Machine() {} diff --git a/Machines/ZX8081/ZX8081.hpp b/Machines/Sinclair/ZX8081/ZX8081.hpp similarity index 84% rename from Machines/ZX8081/ZX8081.hpp rename to Machines/Sinclair/ZX8081/ZX8081.hpp index 5fff0ffe8..6a177cfbe 100644 --- a/Machines/ZX8081/ZX8081.hpp +++ b/Machines/Sinclair/ZX8081/ZX8081.hpp @@ -9,13 +9,14 @@ #ifndef ZX8081_hpp #define ZX8081_hpp -#include "../../Configurable/Configurable.hpp" -#include "../../Configurable/StandardOptions.hpp" -#include "../../Analyser/Static/StaticAnalyser.hpp" -#include "../ROMMachine.hpp" +#include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" +#include "../../../Analyser/Static/StaticAnalyser.hpp" +#include "../../ROMMachine.hpp" #include +namespace Sinclair { namespace ZX8081 { /// The ZX80/81 machine. @@ -47,6 +48,7 @@ class Machine { }; }; +} } #endif /* ZX8081_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index c0712421b..58f436db3 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -23,7 +23,7 @@ #include "../MasterSystem/MasterSystem.hpp" #include "../MSX/MSX.hpp" #include "../Oric/Oric.hpp" -#include "../ZX8081/ZX8081.hpp" +#include "../Sinclair/ZX8081/ZX8081.hpp" // Sources for construction options. #include "../../Analyser/Static/Acorn/Target.hpp" @@ -62,7 +62,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe Bind(MSX) Bind(Oric) BindD(Sega::MasterSystem, MasterSystem) - Bind(ZX8081) + BindD(Sinclair::ZX8081, ZX8081) default: error = Machine::Error::UnknownMachine; @@ -200,7 +200,7 @@ std::map> Machine::AllOptionsBy Emplace(MSX, MSX::Machine); Emplace(Oric, Oric::Machine); Emplace(Vic20, Commodore::Vic20::Machine); - Emplace(ZX8081, ZX8081::Machine); + Emplace(ZX8081, Sinclair::ZX8081::Machine); #undef Emplace diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 601686dfa..6a1cb8115 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -81,9 +81,6 @@ 4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; 4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; }; - 4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; }; - 4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; }; - 4B055AD91FAE9B180060FFFF /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; }; 4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; @@ -135,7 +132,6 @@ 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; 4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */; }; - 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; }; 4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */; }; 4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */; }; 4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; }; @@ -237,7 +233,6 @@ 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; }; 4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; }; 4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; }; - 4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; }; 4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; }; @@ -894,7 +889,6 @@ 4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; }; 4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; 4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; }; - 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; }; 4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; }; 4BD424E62193B5830097291A /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E12193B5820097291A /* Shader.cpp */; }; 4BD424E82193B5830097291A /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E22193B5820097291A /* Rectangle.cpp */; }; @@ -1059,6 +1053,12 @@ 4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = ""; }; 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B0F1BB12602645900B85C66 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4B0F1BCB2602F17B00B85C66 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; + 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = ""; }; + 4B0F1BCD2602F17B00B85C66 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; + 4B0F1BCE2602F17B00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; + 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; + 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -1072,8 +1072,6 @@ 4B1497871EE4A1DA00CE2596 /* ZX80O81P.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX80O81P.hpp; sourceTree = ""; }; 4B14978D1EE4B4D200CE2596 /* CSZX8081.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSZX8081.h; sourceTree = ""; }; 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSZX8081.mm; sourceTree = ""; }; - 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = ZX8081/ZX8081.cpp; sourceTree = ""; }; - 4B1497911EE4B5A800CE2596 /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = ZX8081/ZX8081.hpp; sourceTree = ""; }; 4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; }; 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = ""; }; 4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = ""; }; @@ -1247,8 +1245,6 @@ 4B54C0C41F8D91D90050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B54C0C61F8D91E50050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = Electron/Keyboard.cpp; sourceTree = ""; }; 4B54C0C71F8D91E50050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Electron/Keyboard.hpp; sourceTree = ""; }; - 4B54C0C91F8D92580050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = ZX8081/Keyboard.hpp; sourceTree = ""; }; - 4B54C0CA1F8D92580050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = ZX8081/Keyboard.cpp; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = ""; }; 4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = ""; }; @@ -1898,8 +1894,6 @@ 4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = ""; }; 4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = ""; }; 4BD388872239E198002D14B5 /* 68000Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000Tests.mm; sourceTree = ""; }; - 4BD3A3091EE755C800B5B501 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = ZX8081/Video.cpp; sourceTree = ""; }; - 4BD3A30A1EE755C800B5B501 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = ZX8081/Video.hpp; sourceTree = ""; }; 4BD424DD2193B5340097291A /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4BD424DE2193B5340097291A /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; 4BD424E12193B5820097291A /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; @@ -2167,6 +2161,27 @@ path = ZXSpectrum; sourceTree = ""; }; + 4B0F1BC92602F17B00B85C66 /* Sinclair */ = { + isa = PBXGroup; + children = ( + 4B0F1BCA2602F17B00B85C66 /* ZX8081 */, + ); + path = Sinclair; + sourceTree = ""; + }; + 4B0F1BCA2602F17B00B85C66 /* ZX8081 */ = { + isa = PBXGroup; + children = ( + 4B0F1BCB2602F17B00B85C66 /* Keyboard.hpp */, + 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */, + 4B0F1BCD2602F17B00B85C66 /* Video.cpp */, + 4B0F1BCE2602F17B00B85C66 /* Video.hpp */, + 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */, + 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */, + ); + path = ZX8081; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -2204,19 +2219,6 @@ name = "Test Binaries"; sourceTree = ""; }; - 4B1497931EE4B5AC00CE2596 /* ZX8081 */ = { - isa = PBXGroup; - children = ( - 4B54C0CA1F8D92580050900F /* Keyboard.cpp */, - 4BD3A3091EE755C800B5B501 /* Video.cpp */, - 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */, - 4B54C0C91F8D92580050900F /* Keyboard.hpp */, - 4BD3A30A1EE755C800B5B501 /* Video.hpp */, - 4B1497911EE4B5A800CE2596 /* ZX8081.hpp */, - ); - name = ZX8081; - sourceTree = ""; - }; 4B15A9FE20824C9F005E6C8D /* AppleII */ = { isa = PBXGroup; children = ( @@ -3980,8 +3982,8 @@ 4B7F188B2154825D00388727 /* MasterSystem */, 4B79A4FC1FC8FF9800EEDAD5 /* MSX */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, + 4B0F1BC92602F17B00B85C66 /* Sinclair */, 4B2B3A461F9B8FA70062DABF /* Utility */, - 4B1497931EE4B5AC00CE2596 /* ZX8081 */, ); name = Machines; path = ../../Machines; @@ -5069,7 +5071,6 @@ 4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */, 4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */, 4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */, - 4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, 4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */, 4B2E86D025D8D8C70024F1E9 /* Keyboard.cpp in Sources */, 4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, @@ -5086,7 +5087,6 @@ 4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */, 4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */, 4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */, - 4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */, 4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */, 4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */, 4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */, @@ -5240,7 +5240,6 @@ 4B89452D201967B4007DE474 /* Tape.cpp in Sources */, 4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */, 4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */, - 4B055AD91FAE9B180060FFFF /* ZX8081.cpp in Sources */, 4B89453B201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */, ); @@ -5287,7 +5286,6 @@ 4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */, 4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */, 4B894538201967B4007DE474 /* Tape.cpp in Sources */, - 4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */, 4BEDA43025B3C700000C2DBD /* Executor.cpp in Sources */, 4B1B58F6246CC4E8009C171E /* State.cpp in Sources */, 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, @@ -5297,7 +5295,6 @@ 4BEDA40C25B2844B000C2DBD /* Decoder.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, - 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B2E86BE25D74F160024F1E9 /* Mouse.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */, @@ -5388,7 +5385,6 @@ 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */, 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */, - 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm index 80b8fb822..cba3ec1fa 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm @@ -11,14 +11,14 @@ #include "ZX8081.hpp" @implementation CSZX8081 { - ZX8081::Machine *_zx8081; + Sinclair::ZX8081::Machine *_zx8081; __weak CSMachine *_machine; } - (instancetype)initWithZX8081:(void *)zx8081 owner:(CSMachine *)machine { self = [super init]; if(self) { - _zx8081 = (ZX8081::Machine *)zx8081; + _zx8081 = (Sinclair::ZX8081::Machine *)zx8081; _machine = machine; } return self; diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index 07f407684..b4901282e 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -45,6 +45,7 @@ SOURCES += \ $$SRC/Analyser/Static/Oric/*.cpp \ $$SRC/Analyser/Static/Sega/*.cpp \ $$SRC/Analyser/Static/ZX8081/*.cpp \ + $$SRC/Analyser/Static/ZXSpectrum/*.cpp \ \ $$SRC/Components/1770/*.cpp \ $$SRC/Components/5380/*.cpp \ @@ -88,7 +89,7 @@ SOURCES += \ $$SRC/Machines/MSX/*.cpp \ $$SRC/Machines/Oric/*.cpp \ $$SRC/Machines/Utility/*.cpp \ - $$SRC/Machines/ZX8081/*.cpp \ + $$SRC/Machines/Sinclair/ZX8081/*.cpp \ \ $$SRC/Outputs/*.cpp \ $$SRC/Outputs/CRT/*.cpp \ @@ -213,7 +214,7 @@ HEADERS += \ $$SRC/Machines/MSX/*.hpp \ $$SRC/Machines/Oric/*.hpp \ $$SRC/Machines/Utility/*.hpp \ - $$SRC/Machines/ZX8081/*.hpp \ + $$SRC/Machines/Sinclair/ZX8081/*.hpp \ \ $$SRC/Numeric/*.hpp \ \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index a9cb456b1..f05458c17 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -36,6 +36,7 @@ SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp') SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp') +SOURCES += glob.glob('../../Analyser/Static/ZXSpectrum/*.cpp') SOURCES += glob.glob('../../Components/1770/*.cpp') SOURCES += glob.glob('../../Components/5380/*.cpp') @@ -82,7 +83,7 @@ SOURCES += glob.glob('../../Machines/MasterSystem/*.cpp') SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') -SOURCES += glob.glob('../../Machines/ZX8081/*.cpp') +SOURCES += glob.glob('../../Machines/Sinclair/ZX8081/*.cpp') SOURCES += glob.glob('../../Outputs/*.cpp') SOURCES += glob.glob('../../Outputs/CRT/*.cpp') From 814c057570b3eff319fba672a6f36a35fb66ed9e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:46:25 -0400 Subject: [PATCH 05/76] Update further path references. --- Machines/Sinclair/ZX8081/Keyboard.hpp | 4 ++-- Machines/Sinclair/ZX8081/Video.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/Sinclair/ZX8081/Keyboard.hpp b/Machines/Sinclair/ZX8081/Keyboard.hpp index 56cb848ad..da4d5fe47 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.hpp +++ b/Machines/Sinclair/ZX8081/Keyboard.hpp @@ -9,8 +9,8 @@ #ifndef Machines_ZX8081_Keyboard_hpp #define Machines_ZX8081_Keyboard_hpp -#include "../KeyboardMachine.hpp" -#include "../Utility/Typer.hpp" +#include "../../KeyboardMachine.hpp" +#include "../../Utility/Typer.hpp" namespace ZX8081 { diff --git a/Machines/Sinclair/ZX8081/Video.hpp b/Machines/Sinclair/ZX8081/Video.hpp index fff641a32..9ea9cb4d9 100644 --- a/Machines/Sinclair/ZX8081/Video.hpp +++ b/Machines/Sinclair/ZX8081/Video.hpp @@ -9,8 +9,8 @@ #ifndef Machines_ZX8081_Video_hpp #define Machines_ZX8081_Video_hpp -#include "../../Outputs/CRT/CRT.hpp" -#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../../Outputs/CRT/CRT.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" namespace ZX8081 { From 3d1775d8532f289383cf82edbc8b8026b6030fc0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:52:23 -0400 Subject: [PATCH 06/76] Correct namespace. --- Machines/Sinclair/ZX8081/Keyboard.hpp | 4 +++- Machines/Sinclair/ZX8081/Video.hpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZX8081/Keyboard.hpp b/Machines/Sinclair/ZX8081/Keyboard.hpp index da4d5fe47..354647ad4 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.hpp +++ b/Machines/Sinclair/ZX8081/Keyboard.hpp @@ -12,6 +12,7 @@ #include "../../KeyboardMachine.hpp" #include "../../Utility/Typer.hpp" +namespace Sinclair { namespace ZX8081 { enum Key: uint16_t { @@ -44,6 +45,7 @@ class CharacterMapper: public ::Utility::CharacterMapper { bool is_zx81_; }; -}; +} +} #endif /* KeyboardMapper_hpp */ diff --git a/Machines/Sinclair/ZX8081/Video.hpp b/Machines/Sinclair/ZX8081/Video.hpp index 9ea9cb4d9..555c75603 100644 --- a/Machines/Sinclair/ZX8081/Video.hpp +++ b/Machines/Sinclair/ZX8081/Video.hpp @@ -12,6 +12,7 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" +namespace Sinclair { namespace ZX8081 { /*! @@ -57,6 +58,7 @@ class Video { void flush(bool next_sync); }; +} } #endif /* Video_hpp */ From 9bec91c2b97d3f46aecfe78dbe587a99ec3ebfc9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 22:56:03 -0400 Subject: [PATCH 07/76] Correct further namespace references. --- Machines/Sinclair/ZX8081/Keyboard.cpp | 2 +- Machines/Sinclair/ZX8081/Video.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Sinclair/ZX8081/Keyboard.cpp b/Machines/Sinclair/ZX8081/Keyboard.cpp index d3e25f381..02e681a44 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.cpp +++ b/Machines/Sinclair/ZX8081/Keyboard.cpp @@ -8,7 +8,7 @@ #include "Keyboard.hpp" -using namespace ZX8081; +using namespace Sinclair::ZX8081; uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { #define BIND(source, dest) case Inputs::Keyboard::Key::source: return ZX8081::dest diff --git a/Machines/Sinclair/ZX8081/Video.cpp b/Machines/Sinclair/ZX8081/Video.cpp index 3ccd75266..da1e72109 100644 --- a/Machines/Sinclair/ZX8081/Video.cpp +++ b/Machines/Sinclair/ZX8081/Video.cpp @@ -10,7 +10,7 @@ #include -using namespace ZX8081; +using namespace Sinclair::ZX8081; namespace { From b7d6b8efcf93da279a5b3352214599a698417bbe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 23:27:34 -0400 Subject: [PATCH 08/76] Fix Xcode project. --- .../Mac/Clock Signal.xcodeproj/project.pbxproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6a1cb8115..1a99204ec 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -124,6 +124,12 @@ 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; + 4B0F1BDA2602FF9800B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCD2602F17B00B85C66 /* Video.cpp */; }; + 4B0F1BDE2602FF9900B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCD2602F17B00B85C66 /* Video.cpp */; }; + 4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */; }; + 4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */; }; + 4B0F1BEA2602FFA000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; + 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -5070,6 +5076,7 @@ 4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */, 4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */, 4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */, + 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */, 4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */, 4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */, 4B2E86D025D8D8C70024F1E9 /* Keyboard.cpp in Sources */, @@ -5086,6 +5093,8 @@ 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */, 4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */, 4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */, + 4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */, + 4B0F1BDE2602FF9900B85C66 /* Video.cpp in Sources */, 4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */, 4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */, 4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */, @@ -5291,6 +5300,7 @@ 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */, 4BE211FF253FC80900435408 /* StaticAnalyser.cpp in Sources */, + 4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4BEDA40C25B2844B000C2DBD /* Decoder.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, @@ -5407,6 +5417,7 @@ 4B0ACC02237756ED008902D0 /* Line.cpp in Sources */, 4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, + 4B0F1BEA2602FFA000B85C66 /* Keyboard.cpp in Sources */, 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, 4BD0FBC3233706A200148981 /* CSApplication.m in Sources */, @@ -5443,6 +5454,7 @@ 4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */, 4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, 4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */, + 4B0F1BDA2602FF9800B85C66 /* Video.cpp in Sources */, 4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */, 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, From 5a1bda1d827ab26a6a86bf474654c565690a208f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Mar 2021 23:38:55 -0400 Subject: [PATCH 09/76] Performs boilerplate towards a ZX Spectrum class. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 25 ++++++++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp | 33 +++++++++++++++++++ Machines/Utility/MachineForTarget.cpp | 2 ++ .../Clock Signal.xcodeproj/project.pbxproj | 16 +++++++++ OSBindings/Qt/ClockSignal.pro | 2 ++ OSBindings/SDL/SConstruct | 1 + 6 files changed, 79 insertions(+) create mode 100644 Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp create mode 100644 Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp new file mode 100644 index 000000000..0fb126aa8 --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -0,0 +1,25 @@ +// +// ZXSpectrum.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "ZXSpectrum.hpp" + +#include "../../MachineTypes.hpp" + +#include "../../../Analyser/Static/StaticAnalyser.hpp" + + +using namespace Sinclair::ZXSpectrum; + +Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { + /* TODO */ + (void)target; + (void)rom_fetcher; + return nullptr; +} + +Machine::~Machine() {} diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp new file mode 100644 index 000000000..9a294b95d --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp @@ -0,0 +1,33 @@ +// +// ZXSpectrum.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef ZXSpectrum_hpp +#define ZXSpectrum_hpp + +#include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" +#include "../../../Analyser/Static/StaticAnalyser.hpp" +#include "../../ROMMachine.hpp" + +#include + +namespace Sinclair { +namespace ZXSpectrum { + +class Machine { + public: + virtual ~Machine(); + + static Machine *ZXSpectrum(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); +}; + + +} +} + +#endif /* ZXSpectrum_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 58f436db3..f716635f1 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -24,6 +24,7 @@ #include "../MSX/MSX.hpp" #include "../Oric/Oric.hpp" #include "../Sinclair/ZX8081/ZX8081.hpp" +#include "../Sinclair/ZXSpectrum/ZXSpectrum.hpp" // Sources for construction options. #include "../../Analyser/Static/Acorn/Target.hpp" @@ -63,6 +64,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe Bind(Oric) BindD(Sega::MasterSystem, MasterSystem) BindD(Sinclair::ZX8081, ZX8081) + BindD(Sinclair::ZXSpectrum, ZXSpectrum) default: error = Machine::Error::UnknownMachine; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1a99204ec..c3b07e5b4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -130,6 +130,8 @@ 4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */; }; 4B0F1BEA2602FFA000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; + 4B0F1BFC260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; + 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1065,6 +1067,8 @@ 4B0F1BCE2602F17B00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = ""; }; + 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrum.cpp; sourceTree = ""; }; + 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrum.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2171,6 +2175,7 @@ isa = PBXGroup; children = ( 4B0F1BCA2602F17B00B85C66 /* ZX8081 */, + 4B0F1BF9260300D900B85C66 /* ZXSpectrum */, ); path = Sinclair; sourceTree = ""; @@ -2188,6 +2193,15 @@ path = ZX8081; sourceTree = ""; }; + 4B0F1BF9260300D900B85C66 /* ZXSpectrum */ = { + isa = PBXGroup; + children = ( + 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */, + 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */, + ); + path = ZXSpectrum; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -5212,6 +5226,7 @@ 4B894525201967B4007DE474 /* Tape.cpp in Sources */, 4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */, 4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */, + 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */, 4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */, 4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, 4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */, @@ -5464,6 +5479,7 @@ 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */, 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, + 4B0F1BFC260300D900B85C66 /* ZXSpectrum.cpp in Sources */, 4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */, 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index b4901282e..04e9c0b8e 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -90,6 +90,7 @@ SOURCES += \ $$SRC/Machines/Oric/*.cpp \ $$SRC/Machines/Utility/*.cpp \ $$SRC/Machines/Sinclair/ZX8081/*.cpp \ + $$SRC/Machines/Sinclair/ZXSpectrum/*.cpp \ \ $$SRC/Outputs/*.cpp \ $$SRC/Outputs/CRT/*.cpp \ @@ -215,6 +216,7 @@ HEADERS += \ $$SRC/Machines/Oric/*.hpp \ $$SRC/Machines/Utility/*.hpp \ $$SRC/Machines/Sinclair/ZX8081/*.hpp \ + $$SRC/Machines/Sinclair/ZXSpectrum/*.hpp \ \ $$SRC/Numeric/*.hpp \ \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index f05458c17..251ba283c 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -84,6 +84,7 @@ SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') SOURCES += glob.glob('../../Machines/Sinclair/ZX8081/*.cpp') +SOURCES += glob.glob('../../Machines/Sinclair/ZXSpectrum/*.cpp') SOURCES += glob.glob('../../Outputs/*.cpp') SOURCES += glob.glob('../../Outputs/CRT/*.cpp') From 97249b0edd830b3a1f4296685fa2fb9999a2a5bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 10:18:17 -0400 Subject: [PATCH 10/76] Slow walks further towards a functioning Spectrum. --- Analyser/Static/ZXSpectrum/StaticAnalyser.cpp | 3 +- Analyser/Static/ZXSpectrum/Target.hpp | 40 +++++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 69 +++++++++++++++++-- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 Analyser/Static/ZXSpectrum/Target.hpp diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp index 82456be58..637a106a2 100644 --- a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -9,6 +9,7 @@ #include "StaticAnalyser.hpp" #include "../../../Storage/Tape/Parsers/Spectrum.hpp" +#include "Target.hpp" namespace { @@ -33,7 +34,7 @@ bool IsSpectrumTape(const std::shared_ptr &tape) { Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { TargetList destination; - auto target = std::make_unique(Machine::ZXSpectrum); + auto target = std::make_unique(); target->confidence = 0.5; if(!media.tapes.empty()) { diff --git a/Analyser/Static/ZXSpectrum/Target.hpp b/Analyser/Static/ZXSpectrum/Target.hpp new file mode 100644 index 000000000..159cec9ef --- /dev/null +++ b/Analyser/Static/ZXSpectrum/Target.hpp @@ -0,0 +1,40 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_ZXSpectrum_Target_h +#define Analyser_Static_ZXSpectrum_Target_h + +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" +#include "../StaticAnalyser.hpp" + +namespace Analyser { +namespace Static { +namespace ZXSpectrum { + +struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(Model, + Plus2a, + Plus3, + ); + + Model model = Model::Plus2a; + + Target(): Analyser::Static::Target(Machine::ZXSpectrum) { + if(needs_declare()) { + DeclareField(model); + AnnounceEnum(Model); + } + } +}; + +} +} +} + +#endif /* Target_h */ diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 0fb126aa8..a253fbed7 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -10,15 +10,76 @@ #include "../../MachineTypes.hpp" -#include "../../../Analyser/Static/StaticAnalyser.hpp" +#include "../../../Analyser/Static/ZXSpectrum/Target.hpp" +#include + +namespace { + const unsigned int ClockRate = 3'500'000; +} + + +namespace Sinclair { +namespace ZXSpectrum { + +using Model = Analyser::Static::ZXSpectrum::Target::Model; +template class ConcreteMachine: + public MachineTypes::ScanProducer, + public MachineTypes::TimedMachine, + public Machine { + public: + ConcreteMachine(const Analyser::Static::ZXSpectrum::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) + { + set_clock_rate(ClockRate); + + // With only the +2a and +3 currently supported, the +3 ROM is always + // the one required. + const auto roms = + rom_fetcher({ {"ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, 0x96e3c17a} }); + if(!roms[0]) throw ROMMachine::Error::MissingROMs; + memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size())); + + // TODO: insert media, set up memory map. + (void)target; + } + + // MARK: - TimedMachine + + void run_for(const Cycles cycles) override { + // TODO. + (void)cycles; + } + + // MARK: - ScanProducer + + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + (void)scan_target; + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + // TODO. + return Outputs::Display::ScanStatus(); + } + + private: + std::array rom_; + std::array ram_; +}; + + +} +} using namespace Sinclair::ZXSpectrum; Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - /* TODO */ - (void)target; - (void)rom_fetcher; + const auto zx_target = dynamic_cast(target); + + switch(zx_target->model) { + case Model::Plus2a: return new ConcreteMachine(*zx_target, rom_fetcher); + case Model::Plus3: return new ConcreteMachine(*zx_target, rom_fetcher); + } + return nullptr; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c3b07e5b4..cfe9b411e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1069,6 +1069,7 @@ 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = ""; }; 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrum.cpp; sourceTree = ""; }; 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrum.hpp; sourceTree = ""; }; + 4B0F1C04260391F100B85C66 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2167,6 +2168,7 @@ children = ( 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */, 4B0F1BB12602645900B85C66 /* StaticAnalyser.hpp */, + 4B0F1C04260391F100B85C66 /* Target.hpp */, ); path = ZXSpectrum; sourceTree = ""; From 730bfcd1fd5af975270a37b7c4999f5f93a3634c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 10:43:51 -0400 Subject: [PATCH 11/76] Stumbles towards a memory map. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 99 +++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index a253fbed7..08a973fff 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -10,6 +10,8 @@ #include "../../MachineTypes.hpp" +#include "../../../Processors/Z80/Z80.hpp" + #include "../../../Analyser/Static/ZXSpectrum/Target.hpp" #include @@ -24,11 +26,13 @@ namespace ZXSpectrum { using Model = Analyser::Static::ZXSpectrum::Target::Model; template class ConcreteMachine: + public Machine, public MachineTypes::ScanProducer, public MachineTypes::TimedMachine, - public Machine { + public CPU::Z80::BusHandler { public: - ConcreteMachine(const Analyser::Static::ZXSpectrum::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) + ConcreteMachine(const Analyser::Static::ZXSpectrum::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + z80_(*this) { set_clock_rate(ClockRate); @@ -39,15 +43,17 @@ template class ConcreteMachine: if(!roms[0]) throw ROMMachine::Error::MissingROMs; memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size())); - // TODO: insert media, set up memory map. + // Set up initial memory map. + update_memory_map(); + + // TODO: insert media. (void)target; } // MARK: - TimedMachine void run_for(const Cycles cycles) override { - // TODO. - (void)cycles; + z80_.run_for(cycles); } // MARK: - ScanProducer @@ -61,9 +67,92 @@ template class ConcreteMachine: return Outputs::Display::ScanStatus(); } + // MARK: - BusHandler + + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + (void)cycle; + + return HalfCycles(0); + } + private: + CPU::Z80::Processor z80_; + + // MARK: - Memory. std::array rom_; std::array ram_; + + std::array scratch_; + const uint8_t *read_pointers_[4]; + uint8_t *write_pointers_[4]; + + uint8_t port1ffd_ = 0; + uint8_t port7ffd_ = 0; + bool disable_paging_ = false; + + void update_memory_map() { + if(disable_paging_) { + // Set 48kb-esque memory map. + set_memory(0, rom_.data(), nullptr); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); + set_memory(3, &ram_[0 * 16384], &ram_[0 * 16384]); + return; + } + + if(port1ffd_ & 1) { + // "Special paging mode", i.e. one of four fixed + // RAM configurations, port 7ffd doesn't matter. + + switch(port1ffd_ & 0x6) { + default: + case 0x00: + set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384]); + set_memory(1, &ram_[1 * 16384], &ram_[1 * 16384]); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + break; + + case 0x02: + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); + set_memory(3, &ram_[7 * 16384], &ram_[7 * 16384]); + break; + + case 0x04: + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + break; + + case 0x06: + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); + set_memory(1, &ram_[7 * 16384], &ram_[7 * 16384]); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + break; + } + + return; + } + + // Apply standard 128kb-esque mapping (albeit with extra ROM to pick from). + const auto rom = &rom_[ (((port1ffd_ >> 1) & 2) | ((port7ffd_ >> 4) & 1)) * 16384]; + set_memory(0, rom, nullptr); + + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); + + const auto high_ram = &ram_[(port7ffd_ & 7) * 16384]; + set_memory(3, high_ram, high_ram); + } + + void set_memory(int bank, const uint8_t *read, uint8_t *write) { + read_pointers_[bank] = read - bank*16384; + write_pointers_[bank] = (write ? write : scratch_.data()) - bank*16384; + } }; From 404c1f06e6c3a0598110cf788240db621ff6600f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 10:44:01 -0400 Subject: [PATCH 12/76] Insert missing space. --- Machines/Sinclair/ZX8081/ZX8081.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index 6c3526b3b..152b9f382 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -42,7 +42,7 @@ namespace { // 7FFFh.W PSG index // 7FFEh.R/W PSG data -namespace Sinclair{ +namespace Sinclair { namespace ZX8081 { enum ROMType: uint8_t { From 4a4da90d560aee1634e440cddb930414cca15aa9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:14:48 -0400 Subject: [PATCH 13/76] Implements some of the memory map, and instantiates audio objects. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 74 +++++++++++++++++++-- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 08a973fff..a49db612f 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -8,10 +8,20 @@ #include "ZXSpectrum.hpp" +#define LOG_PREFIX "[Spectrum] " + #include "../../MachineTypes.hpp" #include "../../../Processors/Z80/Z80.hpp" +#include "../../../Components/AudioToggle/AudioToggle.hpp" +#include "../../../Components/AY38910/AY38910.hpp" + +#include "../../../Outputs/Log.hpp" +#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" + #include "../../../Analyser/Static/ZXSpectrum/Target.hpp" #include @@ -32,7 +42,11 @@ template class ConcreteMachine: public CPU::Z80::BusHandler { public: ConcreteMachine(const Analyser::Static::ZXSpectrum::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - z80_(*this) + z80_(*this), + ay_(GI::AY38910::Personality::AY38910, audio_queue_), + audio_toggle_(audio_queue_), + mixer_(ay_, audio_toggle_), + speaker_(mixer_) { set_clock_rate(ClockRate); @@ -70,7 +84,50 @@ template class ConcreteMachine: // MARK: - BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - (void)cycle; + // Ignore all but terminal cycles. + // TODO: I doubt this is correct for timing. + if(!cycle.is_terminal()) return HalfCycles(0); + + uint16_t address = cycle.address ? *cycle.address : 0x0000; + using PartialMachineCycle = CPU::Z80::PartialMachineCycle; + switch(cycle.operation) { + default: break; + case PartialMachineCycle::ReadOpcode: + case PartialMachineCycle::Read: + *cycle.value = read_pointers_[address >> 14][address]; + break; + + case PartialMachineCycle::Write: + write_pointers_[address >> 14][address] = *cycle.value; + break; + + case PartialMachineCycle::Output: + if(!(address&1)) { + // TODO: port FE. + } + + switch(address) { + default: break; + + case 0x1ffd: + port1ffd_ = *cycle.value; + update_memory_map(); + break; + + case 0x7ffd: + disable_paging_ |= *cycle.value & 0x20; + port7ffd_ = *cycle.value; + update_memory_map(); + break; + } + break; + + case PartialMachineCycle::Input: + if(!(address&1)) { + // TODO: port FE. + } + break; + } return HalfCycles(0); } @@ -91,12 +148,8 @@ template class ConcreteMachine: bool disable_paging_ = false; void update_memory_map() { + // If paging is permanently disabled, don't react. if(disable_paging_) { - // Set 48kb-esque memory map. - set_memory(0, rom_.data(), nullptr); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); - set_memory(3, &ram_[0 * 16384], &ram_[0 * 16384]); return; } @@ -153,6 +206,13 @@ template class ConcreteMachine: read_pointers_[bank] = read - bank*16384; write_pointers_[bank] = (write ? write : scratch_.data()) - bank*16384; } + + // MARK: - Audio. + Concurrency::DeferringAsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay_; + Audio::Toggle audio_toggle_; + Outputs::Speaker::CompoundSource, Audio::Toggle> mixer_; + Outputs::Speaker::LowpassSpeaker, Audio::Toggle>> speaker_; }; From 17f551e89d1bead2c1c2ce67824fff9f2455d2c1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:23:54 -0400 Subject: [PATCH 14/76] Attempts a full audio wiring. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 51 ++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index a49db612f..b5d6beaff 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -49,6 +49,7 @@ template class ConcreteMachine: speaker_(mixer_) { set_clock_rate(ClockRate); + speaker_.set_input_rate(float(ClockRate) / 2.0f); // With only the +2a and +3 currently supported, the +3 ROM is always // the one required. @@ -64,12 +65,21 @@ template class ConcreteMachine: (void)target; } + ~ConcreteMachine() { + audio_queue_.flush(); + } + // MARK: - TimedMachine void run_for(const Cycles cycles) override { z80_.run_for(cycles); } + void flush() { + update_audio(); + audio_queue_.perform(); + } + // MARK: - ScanProducer void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { @@ -84,6 +94,8 @@ template class ConcreteMachine: // MARK: - BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + time_since_audio_update_ += cycle.length; + // Ignore all but terminal cycles. // TODO: I doubt this is correct for timing. if(!cycle.is_terminal()) return HalfCycles(0); @@ -103,22 +115,42 @@ template class ConcreteMachine: case PartialMachineCycle::Output: if(!(address&1)) { - // TODO: port FE. + // TODO: rest of port FE. + update_audio(); + audio_toggle_.set_output(*cycle.value & 0x10); } switch(address) { default: break; case 0x1ffd: + // Write to +2a/+3 paging register. port1ffd_ = *cycle.value; update_memory_map(); break; case 0x7ffd: + // Write to classic 128kb paging register. disable_paging_ |= *cycle.value & 0x20; port7ffd_ = *cycle.value; update_memory_map(); break; + + case 0xfffd: + // Select AY register. + update_audio(); + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | GI::AY38910::BC1)); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + + case 0xbffd: + // Write to AY register. + update_audio(); + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2)); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; } break; @@ -126,6 +158,18 @@ template class ConcreteMachine: if(!(address&1)) { // TODO: port FE. } + + switch(address) { + default: break; + + case 0xfffd: + // Read from AY register. + update_audio(); + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value &= ay_.get_data_output(); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + } break; } @@ -213,6 +257,11 @@ template class ConcreteMachine: Audio::Toggle audio_toggle_; Outputs::Speaker::CompoundSource, Audio::Toggle> mixer_; Outputs::Speaker::LowpassSpeaker, Audio::Toggle>> speaker_; + + HalfCycles time_since_audio_update_; + void update_audio() { + speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(2))); + } }; From b830d62850873f95d0adc7471743359e0c15533a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:32:54 -0400 Subject: [PATCH 15/76] Adds quick notes on port FE. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index b5d6beaff..cf9e00556 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -118,6 +118,10 @@ template class ConcreteMachine: // TODO: rest of port FE. update_audio(); audio_toggle_.set_output(*cycle.value & 0x10); + + // b0–b2: border colour + // b3: enable tape input (?) + // b4: tape and speaker output } switch(address) { @@ -157,6 +161,10 @@ template class ConcreteMachine: case PartialMachineCycle::Input: if(!(address&1)) { // TODO: port FE. + + // address b8+: mask of keyboard lines to select + // result: b0–b4: mask of keys pressed + // b6: tape input } switch(address) { From c3539235578a9fffce90b0d2f148331dc81e8ec6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:40:59 -0400 Subject: [PATCH 16/76] This can be constexpr. --- Machines/Sinclair/ZX8081/ZX8081.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index 152b9f382..814cb4c80 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -34,7 +34,7 @@ namespace { // The clock rate is 3.25Mhz. - const unsigned int ZX8081ClockRate = 3250000; + constexpr unsigned int ZX8081ClockRate = 3250000; } // TODO: From 5664e81d48dd932f2545498d844ae14330ccbcc9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:41:24 -0400 Subject: [PATCH 17/76] It appears the +2a and +3 have a different clock rate. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index cf9e00556..8ca2ce2f3 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -26,10 +26,6 @@ #include -namespace { - const unsigned int ClockRate = 3'500'000; -} - namespace Sinclair { namespace ZXSpectrum { @@ -48,8 +44,8 @@ template class ConcreteMachine: mixer_(ay_, audio_toggle_), speaker_(mixer_) { - set_clock_rate(ClockRate); - speaker_.set_input_rate(float(ClockRate) / 2.0f); + set_clock_rate(clock_rate()); + speaker_.set_input_rate(float(clock_rate()) / 2.0f); // With only the +2a and +3 currently supported, the +3 ROM is always // the one required. @@ -69,6 +65,12 @@ template class ConcreteMachine: audio_queue_.flush(); } + static constexpr unsigned int clock_rate() { +// constexpr unsigned int ClockRate = 3'500'000; + constexpr unsigned int Plus3ClockRate = 3'546'900; + return Plus3ClockRate; + } + // MARK: - TimedMachine void run_for(const Cycles cycles) override { @@ -114,8 +116,8 @@ template class ConcreteMachine: break; case PartialMachineCycle::Output: + // Test for port FE. if(!(address&1)) { - // TODO: rest of port FE. update_audio(); audio_toggle_.set_output(*cycle.value & 0x10); @@ -124,6 +126,14 @@ template class ConcreteMachine: // b4: tape and speaker output } + // Test for classic 128kb paging. +// if(!(address&0x8002)) { +// } + + // Test for +2a/+3 paging. +// if((address & 0xc002) == 0x4000) { +// } + switch(address) { default: break; From 135134acfd793e67e2e3a21e436d5b3065ac61fc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 12:47:48 -0400 Subject: [PATCH 18/76] Adds a shell for video emulation. --- InstructionSets/Sizes.hpp | 2 +- Machines/Sinclair/ZXSpectrum/Video.cpp | 9 +++++++ Machines/Sinclair/ZXSpectrum/Video.hpp | 26 +++++++++++++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 8 ++++++ .../Clock Signal.xcodeproj/project.pbxproj | 8 ++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Machines/Sinclair/ZXSpectrum/Video.cpp create mode 100644 Machines/Sinclair/ZXSpectrum/Video.hpp diff --git a/InstructionSets/Sizes.hpp b/InstructionSets/Sizes.hpp index 62f95845a..9d3ed9522 100644 --- a/InstructionSets/Sizes.hpp +++ b/InstructionSets/Sizes.hpp @@ -12,7 +12,7 @@ #include /*! - Maps to the smallest of the following integers that can contain max_value: + Maps to the smallest integral type that can contain max_value, from the following options: * uint8_t; * uint16_t; diff --git a/Machines/Sinclair/ZXSpectrum/Video.cpp b/Machines/Sinclair/ZXSpectrum/Video.cpp new file mode 100644 index 000000000..d0c5efb38 --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/Video.cpp @@ -0,0 +1,9 @@ +// +// Video.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Video.hpp" diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp new file mode 100644 index 000000000..f72cff6ab --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -0,0 +1,26 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Video_hpp +#define Video_hpp + +namespace Sinclair { +namespace ZXSpectrum { + +enum class VideoTiming { + Plus3 +}; + +template class Video { + +}; + +} +} + +#endif /* Video_hpp */ diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 8ca2ce2f3..f404c676d 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -8,6 +8,8 @@ #include "ZXSpectrum.hpp" +#include "Video.hpp" + #define LOG_PREFIX "[Spectrum] " #include "../../MachineTypes.hpp" @@ -24,6 +26,8 @@ #include "../../../Analyser/Static/ZXSpectrum/Target.hpp" +#include "../../../ClockReceiver/JustInTime.hpp" + #include @@ -280,6 +284,10 @@ template class ConcreteMachine: void update_audio() { speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(2))); } + + // MARK: - Video. + static constexpr VideoTiming video_timing = VideoTiming::Plus3; + Video video_; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cfe9b411e..ef24c3d37 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -132,6 +132,8 @@ 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; 4B0F1BFC260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; + 4B0F1C0A2603BA5F00B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C082603BA5F00B85C66 /* Video.cpp */; }; + 4B0F1C0B2603BA5F00B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C082603BA5F00B85C66 /* Video.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1070,6 +1072,8 @@ 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrum.cpp; sourceTree = ""; }; 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrum.hpp; sourceTree = ""; }; 4B0F1C04260391F100B85C66 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4B0F1C082603BA5F00B85C66 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; + 4B0F1C092603BA5F00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2200,6 +2204,8 @@ children = ( 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */, 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */, + 4B0F1C082603BA5F00B85C66 /* Video.cpp */, + 4B0F1C092603BA5F00B85C66 /* Video.hpp */, ); path = ZXSpectrum; sourceTree = ""; @@ -5152,6 +5158,7 @@ 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, + 4B0F1C0B2603BA5F00B85C66 /* Video.cpp in Sources */, 4B2E86C925D892EF0024F1E9 /* DAT.cpp in Sources */, 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, @@ -5297,6 +5304,7 @@ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */, 4B80CD76256CA16400176FCC /* 2MG.cpp in Sources */, + 4B0F1C0A2603BA5F00B85C66 /* Video.cpp in Sources */, 4B8DF505254E3C9D00F3433C /* ADB.cpp in Sources */, 4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */, 4B0ACC2623775819008902D0 /* AtariST.cpp in Sources */, From ab2ad708856a57fd9d1c2e27718aad056c1d4abe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 21:29:52 -0400 Subject: [PATCH 19/76] Takes a run at interrupts. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 100 +++++++++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 8 +- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index f72cff6ab..a8e2c7cba 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -9,6 +9,9 @@ #ifndef Video_hpp #define Video_hpp + +#include "../../../ClockReceiver/ClockReceiver.hpp" + namespace Sinclair { namespace ZXSpectrum { @@ -16,8 +19,103 @@ enum class VideoTiming { Plus3 }; -template class Video { +/* + Timing notes: + As of the +2a/+3: + + 311 lines, 228 cycles/line + Delays begin at 14361, follow the pattern 1, 0, 7, 6, 5, 4, 3, 2; run for 129 cycles/line. + Possibly delays only affect actual reads and writes; documentation is unclear. + + Unknowns, to me, presently: + + How long the interrupt line held for. + + So... + + Probably two bytes of video and attribute are fetched in each 8-cycle block, + with 16 such blocks therefore providing the whole visible display, an island + within 28.5 blocks horizontally. + + 14364 is 228*63, so I I guess almost 63 lines run from the start of vertical + blank through to the top of the display, implying 56 lines on to vertical blank. + +*/ + +template class Video { + private: + struct Timings { + int cycles_per_line; + int lines_per_frame; + int first_delay; + int first_border; + int delays[16]; + }; + + static constexpr Timings get_timings() { + constexpr Timings result = { + .cycles_per_line = 228 * 2, + .lines_per_frame = 311, + .first_delay = 14361 * 2, + .first_border = 14490 * 2, + .delays = { + 2, 1, + 0, 0, + 14, 13, + 12, 11, + 10, 9, + 8, 7, + 6, 5, + 4, 3, + } + }; + return result; + } + + public: + void run_for(HalfCycles duration) { + constexpr auto timings = get_timings(); + + // Advance time. TODO: all the drawing. + time_since_interrupt_ = (time_since_interrupt_ + duration.as()) % (timings.cycles_per_line * timings.lines_per_frame); + } + + private: + // TODO: how long is the interrupt line held for? + static constexpr int interrupt_duration = 48; + + public: + + HalfCycles get_next_sequence_point() { + if(time_since_interrupt_ < interrupt_duration) { + return HalfCycles(interrupt_duration - time_since_interrupt_); + } + + constexpr auto timings = get_timings(); + return timings.cycles_per_line * timings.lines_per_frame - time_since_interrupt_; + } + + bool get_interrupt_line() const { + return time_since_interrupt_ < interrupt_duration; + } + + int access_delay() const { + constexpr auto timings = get_timings(); + if(time_since_interrupt_ < timings.first_delay) return 0; + + const int time_since = time_since_interrupt_ - timings.first_delay; + const int lines = time_since / timings.cycles_per_line; + if(lines >= 192) return 0; + + const int line_position = time_since % timings.cycles_per_line; + if(line_position >= timings.first_border - timings.first_delay) return 0; + + return timings.delays[line_position & 7]; + } + + private: + int time_since_interrupt_ = 0; }; } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index f404c676d..ab8301fc5 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -82,6 +82,7 @@ template class ConcreteMachine: } void flush() { + video_.flush(); update_audio(); audio_queue_.perform(); } @@ -102,6 +103,11 @@ template class ConcreteMachine: forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { time_since_audio_update_ += cycle.length; + video_ += cycle.length; + if(video_.did_flush()) { + z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); + } + // Ignore all but terminal cycles. // TODO: I doubt this is correct for timing. if(!cycle.is_terminal()) return HalfCycles(0); @@ -287,7 +293,7 @@ template class ConcreteMachine: // MARK: - Video. static constexpr VideoTiming video_timing = VideoTiming::Plus3; - Video video_; + JustInTimeActor> video_; }; From f2620e6afb680faf0f212552b485c0354af57e0b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 21:54:42 -0400 Subject: [PATCH 20/76] Adds a CRT. Not yet clocked. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 21 ++++++++++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 5 ++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index a8e2c7cba..234289c71 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -9,7 +9,7 @@ #ifndef Video_hpp #define Video_hpp - +#include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" namespace Sinclair { @@ -86,6 +86,14 @@ template class Video { static constexpr int interrupt_duration = 48; public: + Video() : + crt_(227 * 2, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2) + { + // Show only the centre 80% of the TV frame. + crt_.set_display_type(Outputs::Display::DisplayType::RGB); + crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f)); + + } HalfCycles get_next_sequence_point() { if(time_since_interrupt_ < interrupt_duration) { @@ -114,8 +122,19 @@ template class Video { return timings.delays[line_position & 7]; } + /// Sets the scan target. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); + } + + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const { + return crt_.get_scaled_scan_status(); + } + private: int time_since_interrupt_ = 0; + Outputs::CRT::CRT crt_; }; } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index ab8301fc5..fa34151e4 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -90,12 +90,11 @@ template class ConcreteMachine: // MARK: - ScanProducer void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - (void)scan_target; + video_->set_scan_target(scan_target); } Outputs::Display::ScanStatus get_scaled_scan_status() const final { - // TODO. - return Outputs::Display::ScanStatus(); + return video_->get_scaled_scan_status(); } // MARK: - BusHandler From 87fc7c02e8c2cfb7a97e9d4779901f979e4d7d5c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 22:04:41 -0400 Subject: [PATCH 21/76] Provides a base pointer for video output. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 5 +++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 234289c71..ee3c3ecdb 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -95,6 +95,10 @@ template class Video { } + void set_video_source(const uint8_t *source) { + memory_ = source; + } + HalfCycles get_next_sequence_point() { if(time_since_interrupt_ < interrupt_duration) { return HalfCycles(interrupt_duration - time_since_interrupt_); @@ -135,6 +139,7 @@ template class Video { private: int time_since_interrupt_ = 0; Outputs::CRT::CRT crt_; + const uint8_t *memory_ = nullptr; }; } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index fa34151e4..8d0788558 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -224,6 +224,9 @@ template class ConcreteMachine: return; } + // Set the proper video base pointer. + video_->set_video_source(&ram_[((port7ffd_ & 0x08) ? 7 : 5) * 16384]); + if(port1ffd_ & 1) { // "Special paging mode", i.e. one of four fixed // RAM configurations, port 7ffd doesn't matter. From fe3e8f87e79334c9f7b124e143cae26d5e9233f5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 22:29:24 -0400 Subject: [PATCH 22/76] Takes a shot at an all-border output. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 62 ++++++++++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 2 + 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index ee3c3ecdb..ee10ec5d1 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -12,6 +12,8 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" +#include + namespace Sinclair { namespace ZXSpectrum { @@ -76,15 +78,57 @@ template class Video { public: void run_for(HalfCycles duration) { constexpr auto timings = get_timings(); + constexpr int first_line = timings.first_delay / timings.cycles_per_line; + constexpr int sync_position = 200 * 2; + constexpr int sync_length = 14 * 2; - // Advance time. TODO: all the drawing. - time_since_interrupt_ = (time_since_interrupt_ + duration.as()) % (timings.cycles_per_line * timings.lines_per_frame); + int cycles_remaining = duration.as(); + while(cycles_remaining) { + int line = time_since_interrupt_ / timings.cycles_per_line; + int offset = time_since_interrupt_ % timings.cycles_per_line; + const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset); + const int end_offset = offset + cycles_this_line; + + if(line < 3) { + // Output sync line. + crt_.output_sync(cycles_this_line); + } else /* if((line < first_line) || (line >= first_line+192)) */ { + // Output plain border line. + if(offset < sync_position) { + const int border_duration = std::min(sync_position, end_offset) - offset; + output_border(border_duration); + offset += border_duration; + } + + if(offset >= sync_position && offset < sync_position+sync_length && end_offset > offset) { + const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; + crt_.output_sync(sync_duration); + offset += sync_duration; + } + + if(offset >= sync_position + sync_length && end_offset > offset) { + const int border_duration = end_offset - offset; + output_border(border_duration); + } + } /* else { + // TODO: output pixel line. + } */ + + cycles_remaining -= cycles_this_line; + time_since_interrupt_ = (time_since_interrupt_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame); + } } private: // TODO: how long is the interrupt line held for? static constexpr int interrupt_duration = 48; + void output_border(int duration) { + uint8_t *const colour_pointer = crt_.begin_data(1); + if(colour_pointer) *colour_pointer = border_colour_; + crt_.output_level(duration); + } + public: Video() : crt_(227 * 2, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2) @@ -126,6 +170,10 @@ template class Video { return timings.delays[line_position & 7]; } + void set_border_colour(uint8_t colour) { + border_colour_ = palette[colour]; + } + /// Sets the scan target. void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); @@ -140,6 +188,16 @@ template class Video { int time_since_interrupt_ = 0; Outputs::CRT::CRT crt_; const uint8_t *memory_ = nullptr; + uint8_t border_colour_ = 0; + +#define RGB(r, g, b) (r << 4) | (g << 2) | b + static constexpr uint8_t palette[] = { + RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), + RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2), + RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3), + RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3), + }; +#undef RGB }; } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 8d0788558..638565e57 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -130,6 +130,8 @@ template class ConcreteMachine: update_audio(); audio_toggle_.set_output(*cycle.value & 0x10); + video_->set_border_colour(*cycle.value & 7); + // b0–b2: border colour // b3: enable tape input (?) // b4: tape and speaker output From 871bac6c8aef27770744d207e2c8c5b9262025b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 22:41:20 -0400 Subject: [PATCH 23/76] Marks out and approximately centres a pixel region. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 35 ++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index ee10ec5d1..eec4eb0f9 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -79,7 +79,7 @@ template class Video { void run_for(HalfCycles duration) { constexpr auto timings = get_timings(); constexpr int first_line = timings.first_delay / timings.cycles_per_line; - constexpr int sync_position = 200 * 2; + constexpr int sync_position = 166 * 2; constexpr int sync_length = 14 * 2; int cycles_remaining = duration.as(); @@ -92,7 +92,7 @@ template class Video { if(line < 3) { // Output sync line. crt_.output_sync(cycles_this_line); - } else /* if((line < first_line) || (line >= first_line+192)) */ { + } else if((line < first_line) || (line >= first_line+192)) { // Output plain border line. if(offset < sync_position) { const int border_duration = std::min(sync_position, end_offset) - offset; @@ -110,9 +110,34 @@ template class Video { const int border_duration = end_offset - offset; output_border(border_duration); } - } /* else { - // TODO: output pixel line. - } */ + } else { + // Output pixel line. + if(offset < 256) { + // TODO: actual pixels. + const int pixel_duration = std::min(256, end_offset) - offset; + uint8_t *const colour_pointer = crt_.begin_data(1); + if(colour_pointer) *colour_pointer = 0; + crt_.output_level(pixel_duration); + offset += pixel_duration; + } + + if(offset >= 256 && offset < sync_position && end_offset > offset) { + const int border_duration = std::min(sync_position, end_offset) - offset; + output_border(border_duration); + offset += border_duration; + } + + if(offset >= sync_position && offset < sync_position+sync_length && end_offset > offset) { + const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; + crt_.output_sync(sync_duration); + offset += sync_duration; + } + + if(offset >= sync_position + sync_length && end_offset > offset) { + const int border_duration = end_offset - offset; + output_border(border_duration); + } + } cycles_remaining -= cycles_this_line; time_since_interrupt_ = (time_since_interrupt_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame); From 622a8abf7f2b3e062d3153ab34d38d6618bf8748 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 22:57:10 -0400 Subject: [PATCH 24/76] Takes a stab at pixel output. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 50 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index eec4eb0f9..72c46e914 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -113,12 +113,48 @@ template class Video { } else { // Output pixel line. if(offset < 256) { - // TODO: actual pixels. const int pixel_duration = std::min(256, end_offset) - offset; - uint8_t *const colour_pointer = crt_.begin_data(1); - if(colour_pointer) *colour_pointer = 0; - crt_.output_level(pixel_duration); + + if(!offset) { + const int pixel_line = line - first_line; + + pixel_target_ = crt_.begin_data(256); + attribute_address_ = ((pixel_line / 8) * 32) + 6144; + pixel_address_ = ((pixel_line & 0x07) << 8) | ((pixel_line&0x38) << 2) | ((pixel_line&0xc0) << 3); + } + + if(pixel_target_) { + const int start_column = offset >> 3; + const int end_column = (offset + pixel_duration) >> 3; + for(int column = start_column; column < end_column; column++) { + const uint8_t pixels = memory_[pixel_address_]; + const uint8_t attributes = memory_[attribute_address_]; + + const uint8_t colours[2] = { + palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], + palette[(attributes & 0x78) >> 3], + }; + + pixel_target_[0] = colours[(pixels >> 7) & 1]; + pixel_target_[1] = colours[(pixels >> 6) & 1]; + pixel_target_[2] = colours[(pixels >> 5) & 1]; + pixel_target_[3] = colours[(pixels >> 4) & 1]; + pixel_target_[4] = colours[(pixels >> 3) & 1]; + pixel_target_[5] = colours[(pixels >> 2) & 1]; + pixel_target_[6] = colours[(pixels >> 1) & 1]; + pixel_target_[7] = colours[(pixels >> 0) & 1]; + pixel_target_ += 8; + + ++pixel_address_; + ++attribute_address_; + } + } + offset += pixel_duration; + if(offset == 256) { + crt_.output_data(256); + pixel_target_ = nullptr; + } } if(offset >= 256 && offset < sync_position && end_offset > offset) { @@ -156,7 +192,7 @@ template class Video { public: Video() : - crt_(227 * 2, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2) + crt_(227 * 2, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2) { // Show only the centre 80% of the TV frame. crt_.set_display_type(Outputs::Display::DisplayType::RGB); @@ -215,6 +251,10 @@ template class Video { const uint8_t *memory_ = nullptr; uint8_t border_colour_ = 0; + uint8_t *pixel_target_ = nullptr; + int attribute_address_ = 0; + int pixel_address_ = 0; + #define RGB(r, g, b) (r << 4) | (g << 2) | b static constexpr uint8_t palette[] = { RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), From 020a04006e7c0f54862434d5f685ed1669eb6f07 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:07:51 -0400 Subject: [PATCH 25/76] Adds flashing, randomises initial RAM contents. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 13 ++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 72c46e914..e24aed551 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -89,6 +89,12 @@ template class Video { const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset); const int end_offset = offset + cycles_this_line; + if(!line && !offset) { + ++flash_counter_; + flash_mask_ = uint8_t(flash_counter_ >> 4); + flash_counter_ &= 31; + } + if(line < 3) { // Output sync line. crt_.output_sync(cycles_this_line); @@ -127,9 +133,11 @@ template class Video { const int start_column = offset >> 3; const int end_column = (offset + pixel_duration) >> 3; for(int column = start_column; column < end_column; column++) { - const uint8_t pixels = memory_[pixel_address_]; const uint8_t attributes = memory_[attribute_address_]; + constexpr uint8_t masks[] = {0, 0xff}; + const uint8_t pixels = memory_[pixel_address_] ^ masks[flash_mask_ & (attributes >> 7)]; + const uint8_t colours[2] = { palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], palette[(attributes & 0x78) >> 3], @@ -255,6 +263,9 @@ template class Video { int attribute_address_ = 0; int pixel_address_ = 0; + uint8_t flash_mask_ = 0; + int flash_counter_ = 0; + #define RGB(r, g, b) (r << 4) | (g << 2) | b static constexpr uint8_t palette[] = { RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 638565e57..ce649eaa9 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -26,6 +26,8 @@ #include "../../../Analyser/Static/ZXSpectrum/Target.hpp" +#include "../../Utility/MemoryFuzzer.hpp" + #include "../../../ClockReceiver/JustInTime.hpp" #include @@ -60,6 +62,7 @@ template class ConcreteMachine: // Set up initial memory map. update_memory_map(); + Memory::Fuzz(ram_); // TODO: insert media. (void)target; From ed587a4db5b7932f929dd32c5d3cdcbf03812c16 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:14:39 -0400 Subject: [PATCH 26/76] Provides a better no-port-here value. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index ce649eaa9..0663917c7 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -183,6 +183,8 @@ template class ConcreteMachine: break; case PartialMachineCycle::Input: + *cycle.value = 0xff; + if(!(address&1)) { // TODO: port FE. From 44240773ef528c2e0f46bedbe21e321498ca3e35 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:30:48 -0400 Subject: [PATCH 27/76] Corrects address generation, ink/paper selection. Seemingly to give a correct +2a boot. Time to add a keyboard and find out, I guess. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index e24aed551..dde63b3ed 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -126,7 +126,7 @@ template class Video { pixel_target_ = crt_.begin_data(256); attribute_address_ = ((pixel_line / 8) * 32) + 6144; - pixel_address_ = ((pixel_line & 0x07) << 8) | ((pixel_line&0x38) << 2) | ((pixel_line&0xc0) << 3); + pixel_address_ = ((pixel_line & 0x07) << 8) | ((pixel_line&0x38) << 2) | ((pixel_line&0xc0) << 5); } if(pixel_target_) { @@ -139,8 +139,8 @@ template class Video { const uint8_t pixels = memory_[pixel_address_] ^ masks[flash_mask_ & (attributes >> 7)]; const uint8_t colours[2] = { - palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], palette[(attributes & 0x78) >> 3], + palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], }; pixel_target_[0] = colours[(pixels >> 7) & 1]; From c3d96b30d7fa991d66650c3c50a996296d38d500 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:45:57 -0400 Subject: [PATCH 28/76] Factors out a little of the ZX81's keyboard logic. --- Machines/Sinclair/ZX8081/Keyboard.cpp | 50 +++++++++++++++++++++++++++ Machines/Sinclair/ZX8081/Keyboard.hpp | 13 +++++++ Machines/Sinclair/ZX8081/ZX8081.cpp | 40 +++------------------ 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/Machines/Sinclair/ZX8081/Keyboard.cpp b/Machines/Sinclair/ZX8081/Keyboard.cpp index 02e681a44..afd477f3d 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.cpp +++ b/Machines/Sinclair/ZX8081/Keyboard.cpp @@ -192,3 +192,53 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { bool CharacterMapper::needs_pause_after_key(uint16_t key) const { return key != KeyShift; } + +Keyboard::Keyboard(bool is_zx81) : is_zx81_(is_zx81) { + clear_all_keys(); +} + +void Keyboard::set_key_state(uint16_t key, bool is_pressed) { + const auto line = key >> 8; + + // Check for special cases. + if(line > 7) { + switch(key) { +#define ShiftedKey(source, base) \ + case source: \ + set_key_state(KeyShift, is_pressed); \ + set_key_state(base, is_pressed); \ + break; + + ShiftedKey(KeyDelete, Key0); + ShiftedKey(KeyBreak, KeySpace); + ShiftedKey(KeyUp, Key7); + ShiftedKey(KeyDown, Key6); + ShiftedKey(KeyLeft, Key5); + ShiftedKey(KeyRight, Key8); + ShiftedKey(KeyEdit, is_zx81_ ? Key1 : KeyEnter); + +#undef ShiftedKey + } + } else { + if(is_pressed) + key_states_[line] &= uint8_t(~key); + else + key_states_[line] |= uint8_t(key); + } +} + +void Keyboard::clear_all_keys() { + memset(key_states_, 0xff, 8); +} + +uint8_t Keyboard::read(uint16_t address) { + uint8_t value = 0xff; + + uint16_t mask = 0x100; + for(int c = 0; c < 8; c++) { + if(!(address & mask)) value &= key_states_[c]; + mask <<= 1; + } + + return value; +} diff --git a/Machines/Sinclair/ZX8081/Keyboard.hpp b/Machines/Sinclair/ZX8081/Keyboard.hpp index 354647ad4..8f52e701a 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.hpp +++ b/Machines/Sinclair/ZX8081/Keyboard.hpp @@ -30,6 +30,19 @@ enum Key: uint16_t { KeyBreak, KeyLeft, KeyRight, KeyUp, KeyDown, KeyEdit }; +struct Keyboard { + Keyboard(bool is_zx81); + + void set_key_state(uint16_t key, bool is_pressed); + void clear_all_keys(); + + uint8_t read(uint16_t address); + + private: + uint8_t key_states_[8]; + const bool is_zx81_; +}; + struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override; }; diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index 814cb4c80..c1749fdbb 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -63,12 +63,12 @@ template class ConcreteMachine: ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : Utility::TypeRecipient(is_zx81), z80_(*this), + keyboard_(is_zx81), tape_player_(ZX8081ClockRate), ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { set_clock_rate(ZX8081ClockRate); speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f); - clear_all_keys(); const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; const auto roms = @@ -183,12 +183,7 @@ template class ConcreteMachine: if(!(address&1)) { if(!nmi_is_enabled_) set_vsync(true); - uint16_t mask = 0x100; - for(int c = 0; c < 8; c++) { - if(!(address & mask)) value &= key_states_[c]; - mask <<= 1; - } - + value &= keyboard_.read(address); value &= ~(tape_player_.get_input() ? 0x00 : 0x80); } @@ -339,36 +334,11 @@ template class ConcreteMachine: // MARK: - Keyboard void set_key_state(uint16_t key, bool is_pressed) final { - const auto line = key >> 8; - - // Check for special cases. - if(line > 7) { - switch(key) { -#define ShiftedKey(source, base) \ - case source: \ - set_key_state(KeyShift, is_pressed); \ - set_key_state(base, is_pressed); \ - break; - - ShiftedKey(KeyDelete, Key0); - ShiftedKey(KeyBreak, KeySpace); - ShiftedKey(KeyUp, Key7); - ShiftedKey(KeyDown, Key6); - ShiftedKey(KeyLeft, Key5); - ShiftedKey(KeyRight, Key8); - ShiftedKey(KeyEdit, is_zx81 ? Key1 : KeyEnter); -#undef ShiftedKey - } - } else { - if(is_pressed) - key_states_[line] &= uint8_t(~key); - else - key_states_[line] |= uint8_t(key); - } + keyboard_.set_key_state(key, is_pressed); } void clear_all_keys() final { - memset(key_states_, 0xff, 8); + keyboard_.clear_all_keys(); } // MARK: - Tape control @@ -448,7 +418,7 @@ template class ConcreteMachine: bool vsync_ = false, hsync_ = false; int line_counter_ = 0; - uint8_t key_states_[8]; + Keyboard keyboard_; ZX8081::KeyboardMapper keyboard_mapper_; HalfClockReceiver tape_player_; From 2d51924a3c6dfea96b5f8ec4e6646d648a780978 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:51:21 -0400 Subject: [PATCH 29/76] Wires up Spectrum keyboard. The machine now appears to be fully interactive and functional. Timing and media aside, that is. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 0663917c7..71584d8f2 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -30,8 +30,10 @@ #include "../../../ClockReceiver/JustInTime.hpp" -#include +// TODO: Factor this file into an appropriate place and namespace. +#include "../ZX8081/Keyboard.hpp" +#include namespace Sinclair { namespace ZXSpectrum { @@ -39,6 +41,7 @@ namespace ZXSpectrum { using Model = Analyser::Static::ZXSpectrum::Target::Model; template class ConcreteMachine: public Machine, + public MachineTypes::MappedKeyboardMachine, public MachineTypes::ScanProducer, public MachineTypes::TimedMachine, public CPU::Z80::BusHandler { @@ -48,7 +51,8 @@ template class ConcreteMachine: ay_(GI::AY38910::Personality::AY38910, audio_queue_), audio_toggle_(audio_queue_), mixer_(ay_, audio_toggle_), - speaker_(mixer_) + speaker_(mixer_), + keyboard_(true) { set_clock_rate(clock_rate()); speaker_.set_input_rate(float(clock_rate()) / 2.0f); @@ -186,7 +190,7 @@ template class ConcreteMachine: *cycle.value = 0xff; if(!(address&1)) { - // TODO: port FE. + *cycle.value &= keyboard_.read(address); // address b8+: mask of keyboard lines to select // result: b0–b4: mask of keys pressed @@ -210,6 +214,28 @@ template class ConcreteMachine: return HalfCycles(0); } + // MARK: - Typer +// HalfCycles get_typer_delay(const std::string &) const final { +// return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); +// } +// +// HalfCycles get_typer_frequency() const final { +// return Cycles(146'250); +// } + + KeyboardMapper *get_keyboard_mapper() override { + return &keyboard_mapper_; + } + + // MARK: - Keyboard + void set_key_state(uint16_t key, bool is_pressed) override { + keyboard_.set_key_state(key, is_pressed); + } + + void clear_all_keys() override { + keyboard_.clear_all_keys(); + } + private: CPU::Z80::Processor z80_; @@ -303,6 +329,10 @@ template class ConcreteMachine: // MARK: - Video. static constexpr VideoTiming video_timing = VideoTiming::Plus3; JustInTimeActor> video_; + + // MARK: - Keyboard. + Sinclair::ZX8081::Keyboard keyboard_; + ZX8081::KeyboardMapper keyboard_mapper_; }; From 87fac15cc4385604f617d41e848ffac06380a602 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:51:45 -0400 Subject: [PATCH 30/76] This is going to remain purely a template; no .cpp. --- Machines/Sinclair/ZXSpectrum/Video.cpp | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 Machines/Sinclair/ZXSpectrum/Video.cpp diff --git a/Machines/Sinclair/ZXSpectrum/Video.cpp b/Machines/Sinclair/ZXSpectrum/Video.cpp deleted file mode 100644 index d0c5efb38..000000000 --- a/Machines/Sinclair/ZXSpectrum/Video.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// Video.cpp -// Clock Signal -// -// Created by Thomas Harte on 18/03/2021. -// Copyright © 2021 Thomas Harte. All rights reserved. -// - -#include "Video.hpp" From 69ca2e88038125561ff52937518cf37130092b97 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Mar 2021 23:52:35 -0400 Subject: [PATCH 31/76] Update Xcode project. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ef24c3d37..f02baa208 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -132,8 +132,6 @@ 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; 4B0F1BFC260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; - 4B0F1C0A2603BA5F00B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C082603BA5F00B85C66 /* Video.cpp */; }; - 4B0F1C0B2603BA5F00B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C082603BA5F00B85C66 /* Video.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1072,7 +1070,6 @@ 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrum.cpp; sourceTree = ""; }; 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrum.hpp; sourceTree = ""; }; 4B0F1C04260391F100B85C66 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; - 4B0F1C082603BA5F00B85C66 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; 4B0F1C092603BA5F00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; @@ -2204,7 +2201,6 @@ children = ( 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */, 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */, - 4B0F1C082603BA5F00B85C66 /* Video.cpp */, 4B0F1C092603BA5F00B85C66 /* Video.hpp */, ); path = ZXSpectrum; @@ -5158,7 +5154,6 @@ 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, - 4B0F1C0B2603BA5F00B85C66 /* Video.cpp in Sources */, 4B2E86C925D892EF0024F1E9 /* DAT.cpp in Sources */, 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, @@ -5304,7 +5299,6 @@ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */, 4B80CD76256CA16400176FCC /* 2MG.cpp in Sources */, - 4B0F1C0A2603BA5F00B85C66 /* Video.cpp in Sources */, 4B8DF505254E3C9D00F3433C /* ADB.cpp in Sources */, 4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */, 4B0ACC2623775819008902D0 /* AtariST.cpp in Sources */, From f8c9ef2950e4effd9c66e7122bb29016e085ad59 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 00:00:59 -0400 Subject: [PATCH 32/76] Add necessary header for memset. --- Machines/Sinclair/ZX8081/Keyboard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Machines/Sinclair/ZX8081/Keyboard.cpp b/Machines/Sinclair/ZX8081/Keyboard.cpp index afd477f3d..198210fa3 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.cpp +++ b/Machines/Sinclair/ZX8081/Keyboard.cpp @@ -8,6 +8,8 @@ #include "Keyboard.hpp" +#include + using namespace Sinclair::ZX8081; uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { From 93b9ea67e60c75ed76811e5dd65da8382c0c28e4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 08:49:56 -0400 Subject: [PATCH 33/76] Takes a run at contended timings. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 8 +- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 84 +++++++++++++-------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index dde63b3ed..d96d72c45 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -225,11 +225,13 @@ template class Video { return time_since_interrupt_ < interrupt_duration; } - int access_delay() const { + int access_delay(HalfCycles offset) const { constexpr auto timings = get_timings(); - if(time_since_interrupt_ < timings.first_delay) return 0; + const int delay_time = (time_since_interrupt_ + offset.as()) % (timings.cycles_per_line * timings.lines_per_frame); - const int time_since = time_since_interrupt_ - timings.first_delay; + if(delay_time < timings.first_delay) return 0; + + const int time_since = delay_time - timings.first_delay; const int lines = time_since / timings.cycles_per_line; if(lines >= 192) return 0; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 71584d8f2..69af74b2f 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -107,27 +107,36 @@ template class ConcreteMachine: // MARK: - BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - time_since_audio_update_ += cycle.length; - - video_ += cycle.length; - if(video_.did_flush()) { - z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); - } - // Ignore all but terminal cycles. // TODO: I doubt this is correct for timing. - if(!cycle.is_terminal()) return HalfCycles(0); + if(!cycle.is_terminal()) { + advance(cycle.length); + return HalfCycles(0); + } + HalfCycles delay(0); uint16_t address = cycle.address ? *cycle.address : 0x0000; using PartialMachineCycle = CPU::Z80::PartialMachineCycle; switch(cycle.operation) { default: break; case PartialMachineCycle::ReadOpcode: case PartialMachineCycle::Read: + // Apply contention if necessary. + if(is_contended_[address >> 14]) { + delay = video_.last_valid()->access_delay(video_.time_since_flush()); + } + *cycle.value = read_pointers_[address >> 14][address]; break; case PartialMachineCycle::Write: + // Apply contention if necessary. + // For now this causes a video sync up every time any contended area is written to. + // TODO: flush only upon a video-area write. + if(is_contended_[address >> 14]) { + delay = video_->access_delay(HalfCycles(0)); + } + write_pointers_[address >> 14][address] = *cycle.value; break; @@ -211,9 +220,22 @@ template class ConcreteMachine: break; } - return HalfCycles(0); + advance(cycle.length + delay); + return delay; } + private: + void advance(HalfCycles duration) { + time_since_audio_update_ += duration; + + video_ += duration; + if(video_.did_flush()) { + z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); + } + } + + public: + // MARK: - Typer // HalfCycles get_typer_delay(const std::string &) const final { // return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); @@ -246,6 +268,7 @@ template class ConcreteMachine: std::array scratch_; const uint8_t *read_pointers_[4]; uint8_t *write_pointers_[4]; + bool is_contended_[4]; uint8_t port1ffd_ = 0; uint8_t port7ffd_ = 0; @@ -267,31 +290,31 @@ template class ConcreteMachine: switch(port1ffd_ & 0x6) { default: case 0x00: - set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384]); - set_memory(1, &ram_[1 * 16384], &ram_[1 * 16384]); - set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384], false); + set_memory(1, &ram_[1 * 16384], &ram_[1 * 16384], false); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384], false); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; case 0x02: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[7 * 16384], &ram_[7 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[7 * 16384], &ram_[7 * 16384], true); break; case 0x04: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; case 0x06: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[7 * 16384], &ram_[7 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[7 * 16384], &ram_[7 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; } @@ -300,16 +323,17 @@ template class ConcreteMachine: // Apply standard 128kb-esque mapping (albeit with extra ROM to pick from). const auto rom = &rom_[ (((port1ffd_ >> 1) & 2) | ((port7ffd_ >> 4) & 1)) * 16384]; - set_memory(0, rom, nullptr); + set_memory(0, rom, nullptr, false); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384], false); const auto high_ram = &ram_[(port7ffd_ & 7) * 16384]; - set_memory(3, high_ram, high_ram); + set_memory(3, high_ram, high_ram, (port7ffd_ & 7) >= 4); } - void set_memory(int bank, const uint8_t *read, uint8_t *write) { + void set_memory(int bank, const uint8_t *read, uint8_t *write, bool is_contended) { + is_contended_[bank] = is_contended; read_pointers_[bank] = read - bank*16384; write_pointers_[bank] = (write ? write : scratch_.data()) - bank*16384; } From 2371048ad1d6163484a6dae2f419879d128e1f3f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 10:36:08 -0400 Subject: [PATCH 34/76] Formally separates keyboard code. With an eye to formalising the Spectrum/ZX81/ZX80 differences. --- .../{ZX8081 => Keyboard}/Keyboard.cpp | 20 ++++++--- .../{ZX8081 => Keyboard}/Keyboard.hpp | 43 +++++++++++++------ Machines/Sinclair/ZX8081/ZX8081.cpp | 17 +++++--- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 10 ++--- .../Clock Signal.xcodeproj/project.pbxproj | 24 +++++++---- 5 files changed, 76 insertions(+), 38 deletions(-) rename Machines/Sinclair/{ZX8081 => Keyboard}/Keyboard.cpp (94%) rename Machines/Sinclair/{ZX8081 => Keyboard}/Keyboard.hpp (59%) diff --git a/Machines/Sinclair/ZX8081/Keyboard.cpp b/Machines/Sinclair/Keyboard/Keyboard.cpp similarity index 94% rename from Machines/Sinclair/ZX8081/Keyboard.cpp rename to Machines/Sinclair/Keyboard/Keyboard.cpp index 198210fa3..48bc24fca 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.cpp +++ b/Machines/Sinclair/Keyboard/Keyboard.cpp @@ -10,10 +10,12 @@ #include -using namespace Sinclair::ZX8081; +using namespace Sinclair::ZX::Keyboard; + +KeyboardMapper::KeyboardMapper(Machine machine) : machine_(machine) {} uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { -#define BIND(source, dest) case Inputs::Keyboard::Key::source: return ZX8081::dest +#define BIND(source, dest) case Inputs::Keyboard::Key::source: return dest switch(key) { default: break; @@ -44,7 +46,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { return MachineTypes::MappedKeyboardMachine::KeyNotMapped; } -CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {} +CharacterMapper::CharacterMapper(Machine machine) : machine_(machine) {} const uint16_t *CharacterMapper::sequence_for_character(char character) const { #define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} @@ -185,17 +187,21 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { #undef SHIFT #undef X - if(is_zx81_) + switch(machine_) { + case Machine::ZX81: + case Machine::ZXSpectrum: // TODO: some differences exist for the Spectrum. return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character); - else + + case Machine::ZX80: return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character); + } } bool CharacterMapper::needs_pause_after_key(uint16_t key) const { return key != KeyShift; } -Keyboard::Keyboard(bool is_zx81) : is_zx81_(is_zx81) { +Keyboard::Keyboard(Machine machine) : machine_(machine) { clear_all_keys(); } @@ -217,7 +223,7 @@ void Keyboard::set_key_state(uint16_t key, bool is_pressed) { ShiftedKey(KeyDown, Key6); ShiftedKey(KeyLeft, Key5); ShiftedKey(KeyRight, Key8); - ShiftedKey(KeyEdit, is_zx81_ ? Key1 : KeyEnter); + ShiftedKey(KeyEdit, (machine_ == Machine::ZX80) ? KeyEnter : Key1); #undef ShiftedKey } diff --git a/Machines/Sinclair/ZX8081/Keyboard.hpp b/Machines/Sinclair/Keyboard/Keyboard.hpp similarity index 59% rename from Machines/Sinclair/ZX8081/Keyboard.hpp rename to Machines/Sinclair/Keyboard/Keyboard.hpp index 8f52e701a..1f3ec2bae 100644 --- a/Machines/Sinclair/ZX8081/Keyboard.hpp +++ b/Machines/Sinclair/Keyboard/Keyboard.hpp @@ -13,7 +13,12 @@ #include "../../Utility/Typer.hpp" namespace Sinclair { -namespace ZX8081 { +namespace ZX { +namespace Keyboard { + +enum class Machine { + ZX80, ZX81, ZXSpectrum +}; enum Key: uint16_t { KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10, @@ -23,41 +28,53 @@ enum Key: uint16_t { Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10, KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10, KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10, - KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10, + KeySpace = 0x0700 | 0x01, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10, - // Add some virtual keys; these do not exist on a real ZX80 or ZX81. They're just a convenience. + // The ZX80 and ZX81 keyboards have a full stop; the ZX Spectrum replaces that key with symbol shift. + KeyDot = 0x0700 | 0x02, KeySymbolShift = KeyDot, + + // Add some virtual keys; these do not exist on a real ZX80, ZX81 or early Spectrum, those all were added to the 128kb Spectrums. + // Either way, they're a convenience. KeyDelete = 0x0801, KeyBreak, KeyLeft, KeyRight, KeyUp, KeyDown, KeyEdit }; -struct Keyboard { - Keyboard(bool is_zx81); +class Keyboard { + public: + Keyboard(Machine machine); - void set_key_state(uint16_t key, bool is_pressed); - void clear_all_keys(); + void set_key_state(uint16_t key, bool is_pressed); + void clear_all_keys(); - uint8_t read(uint16_t address); + uint8_t read(uint16_t address); private: uint8_t key_states_[8]; - const bool is_zx81_; + const Machine machine_; }; -struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { - uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override; +class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + public: + KeyboardMapper(Machine machine); + + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override; + + private: + const Machine machine_; }; class CharacterMapper: public ::Utility::CharacterMapper { public: - CharacterMapper(bool is_zx81); + CharacterMapper(Machine machine); const uint16_t *sequence_for_character(char character) const override; bool needs_pause_after_key(uint16_t key) const override; private: - bool is_zx81_; + const Machine machine_; }; +} } } diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index c1749fdbb..48455a880 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -24,7 +24,7 @@ #include "../../../Analyser/Static/ZX8081/Target.hpp" -#include "Keyboard.hpp" +#include "../Keyboard/Keyboard.hpp" #include "Video.hpp" #include @@ -49,6 +49,8 @@ enum ROMType: uint8_t { ZX80 = 0, ZX81 }; +using CharacterMapper = Sinclair::ZX::Keyboard::CharacterMapper; + template class ConcreteMachine: public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, @@ -61,9 +63,10 @@ template class ConcreteMachine: public Machine { public: ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - Utility::TypeRecipient(is_zx81), + Utility::TypeRecipient(keyboard_machine()), z80_(*this), - keyboard_(is_zx81), + keyboard_(keyboard_machine()), + keyboard_mapper_(keyboard_machine()), tape_player_(ZX8081ClockRate), ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { @@ -341,6 +344,10 @@ template class ConcreteMachine: keyboard_.clear_all_keys(); } + static constexpr Sinclair::ZX::Keyboard::Machine keyboard_machine() { + return is_zx81 ? Sinclair::ZX::Keyboard::Machine::ZX81 : Sinclair::ZX::Keyboard::Machine::ZX80; + } + // MARK: - Tape control void set_use_automatic_tape_motor_control(bool enabled) { @@ -418,8 +425,8 @@ template class ConcreteMachine: bool vsync_ = false, hsync_ = false; int line_counter_ = 0; - Keyboard keyboard_; - ZX8081::KeyboardMapper keyboard_mapper_; + Sinclair::ZX::Keyboard::Keyboard keyboard_; + Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_; HalfClockReceiver tape_player_; Storage::Tape::ZX8081::Parser parser_; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 69af74b2f..d1c84ed27 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -30,8 +30,7 @@ #include "../../../ClockReceiver/JustInTime.hpp" -// TODO: Factor this file into an appropriate place and namespace. -#include "../ZX8081/Keyboard.hpp" +#include "../Keyboard/Keyboard.hpp" #include @@ -52,7 +51,8 @@ template class ConcreteMachine: audio_toggle_(audio_queue_), mixer_(ay_, audio_toggle_), speaker_(mixer_), - keyboard_(true) + keyboard_(Sinclair::ZX::Keyboard::Machine::ZXSpectrum), + keyboard_mapper_(Sinclair::ZX::Keyboard::Machine::ZXSpectrum) { set_clock_rate(clock_rate()); speaker_.set_input_rate(float(clock_rate()) / 2.0f); @@ -355,8 +355,8 @@ template class ConcreteMachine: JustInTimeActor> video_; // MARK: - Keyboard. - Sinclair::ZX8081::Keyboard keyboard_; - ZX8081::KeyboardMapper keyboard_mapper_; + Sinclair::ZX::Keyboard::Keyboard keyboard_; + Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f02baa208..98375593d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -128,10 +128,10 @@ 4B0F1BDE2602FF9900B85C66 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCD2602F17B00B85C66 /* Video.cpp */; }; 4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */; }; 4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */; }; - 4B0F1BEA2602FFA000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; - 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */; }; 4B0F1BFC260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; + 4B0F1C1C2604EA1000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */; }; + 4B0F1C1D2604EA1000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1061,16 +1061,16 @@ 4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = ""; }; 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B0F1BB12602645900B85C66 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; - 4B0F1BCB2602F17B00B85C66 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = ""; }; 4B0F1BCD2602F17B00B85C66 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; 4B0F1BCE2602F17B00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; - 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = ""; }; 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrum.cpp; sourceTree = ""; }; 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrum.hpp; sourceTree = ""; }; 4B0F1C04260391F100B85C66 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4B0F1C092603BA5F00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; + 4B0F1C1A2604EA1000B85C66 /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; + 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2177,6 +2177,7 @@ 4B0F1BC92602F17B00B85C66 /* Sinclair */ = { isa = PBXGroup; children = ( + 4B0F1C192604EA1000B85C66 /* Keyboard */, 4B0F1BCA2602F17B00B85C66 /* ZX8081 */, 4B0F1BF9260300D900B85C66 /* ZXSpectrum */, ); @@ -2186,11 +2187,9 @@ 4B0F1BCA2602F17B00B85C66 /* ZX8081 */ = { isa = PBXGroup; children = ( - 4B0F1BCB2602F17B00B85C66 /* Keyboard.hpp */, 4B0F1BCC2602F17B00B85C66 /* ZX8081.cpp */, 4B0F1BCD2602F17B00B85C66 /* Video.cpp */, 4B0F1BCE2602F17B00B85C66 /* Video.hpp */, - 4B0F1BCF2602F17B00B85C66 /* Keyboard.cpp */, 4B0F1BD02602F17B00B85C66 /* ZX8081.hpp */, ); path = ZX8081; @@ -2206,6 +2205,15 @@ path = ZXSpectrum; sourceTree = ""; }; + 4B0F1C192604EA1000B85C66 /* Keyboard */ = { + isa = PBXGroup; + children = ( + 4B0F1C1A2604EA1000B85C66 /* Keyboard.hpp */, + 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */, + ); + path = Keyboard; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -5094,7 +5102,6 @@ 4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */, 4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */, 4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */, - 4B0F1BEB2602FFA100B85C66 /* Keyboard.cpp in Sources */, 4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */, 4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */, 4B2E86D025D8D8C70024F1E9 /* Keyboard.cpp in Sources */, @@ -5141,6 +5148,7 @@ 4B055A9E1FAE85DA0060FFFF /* G64.cpp in Sources */, 4B055AB81FAE860F0060FFFF /* ZX80O81P.cpp in Sources */, 4B055AB01FAE86070060FFFF /* PulseQueuedTape.cpp in Sources */, + 4B0F1C1D2604EA1000B85C66 /* Keyboard.cpp in Sources */, 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, 4BB307BC235001C300457D33 /* 6850.cpp in Sources */, 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, @@ -5291,6 +5299,7 @@ 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, 4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */, 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, + 4B0F1C1C2604EA1000B85C66 /* Keyboard.cpp in Sources */, 4B228CD924DA12C60077EF25 /* CSScanTargetView.m in Sources */, 4B6AAEAD230E40250078E864 /* Target.cpp in Sources */, 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, @@ -5436,7 +5445,6 @@ 4B0ACC02237756ED008902D0 /* Line.cpp in Sources */, 4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, - 4B0F1BEA2602FFA000B85C66 /* Keyboard.cpp in Sources */, 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, 4BD0FBC3233706A200148981 /* CSApplication.m in Sources */, From a35e1f4fbe63dd2da1c8697bbea0c08ca381166f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 11:06:09 -0400 Subject: [PATCH 35/76] Starts to make formal Spectrum accommodations. --- Machines/Sinclair/Keyboard/Keyboard.cpp | 57 +++++++++++++++++++------ Machines/Sinclair/Keyboard/Keyboard.hpp | 2 +- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Machines/Sinclair/Keyboard/Keyboard.cpp b/Machines/Sinclair/Keyboard/Keyboard.cpp index 48bc24fca..3677adaca 100644 --- a/Machines/Sinclair/Keyboard/Keyboard.cpp +++ b/Machines/Sinclair/Keyboard/Keyboard.cpp @@ -29,10 +29,28 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM); BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift); - BIND(FullStop, KeyDot); BIND(Enter, KeyEnter); BIND(Space, KeySpace); + // Full stop has a key on the ZX80 and ZX81; it doesn't have a dedicated key on the Spectrum. + case Inputs::Keyboard::Key::FullStop: + if(machine_ == Machine::ZXSpectrum) { + return KeySpectrumDot; + } else { + return KeyDot; + } + break; + + // Map controls and options to symbol shift, if this is a ZX Spectrum. + case Inputs::Keyboard::Key::LeftOption: + case Inputs::Keyboard::Key::RightOption: + case Inputs::Keyboard::Key::LeftControl: + case Inputs::Keyboard::Key::RightControl: + if(machine_ == Machine::ZXSpectrum) { + return KeySymbolShift; + } + break; + // Virtual keys follow. BIND(Backspace, KeyDelete); BIND(Escape, KeyBreak); @@ -41,6 +59,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(BackTick, KeyEdit); BIND(F1, KeyEdit); + BIND(Comma, KeyComma); } #undef BIND return MachineTypes::MappedKeyboardMachine::KeyNotMapped; @@ -211,19 +230,33 @@ void Keyboard::set_key_state(uint16_t key, bool is_pressed) { // Check for special cases. if(line > 7) { switch(key) { -#define ShiftedKey(source, base) \ - case source: \ - set_key_state(KeyShift, is_pressed); \ - set_key_state(base, is_pressed); \ +#define ShiftedKey(source, base, shift) \ + case source: \ + set_key_state(shift, is_pressed); \ + set_key_state(base, is_pressed); \ break; - ShiftedKey(KeyDelete, Key0); - ShiftedKey(KeyBreak, KeySpace); - ShiftedKey(KeyUp, Key7); - ShiftedKey(KeyDown, Key6); - ShiftedKey(KeyLeft, Key5); - ShiftedKey(KeyRight, Key8); - ShiftedKey(KeyEdit, (machine_ == Machine::ZX80) ? KeyEnter : Key1); + ShiftedKey(KeyDelete, Key0, KeyShift); + ShiftedKey(KeyBreak, KeySpace, KeyShift); + ShiftedKey(KeyUp, Key7, KeyShift); + ShiftedKey(KeyDown, Key6, KeyShift); + ShiftedKey(KeyLeft, Key5, KeyShift); + ShiftedKey(KeyRight, Key8, KeyShift); + ShiftedKey(KeyEdit, (machine_ == Machine::ZX80) ? KeyEnter : Key1, KeyShift); + + ShiftedKey(KeySpectrumDot, KeyM, KeySymbolShift); + + case KeyComma: + if(machine_ == Machine::ZXSpectrum) { + // Spectrum: comma = symbol shift + n. + set_key_state(KeySymbolShift, is_pressed); + set_key_state(KeyN, is_pressed); + } else { + // ZX80/81: comma = shift + dot. + set_key_state(KeyShift, is_pressed); + set_key_state(KeyDot, is_pressed); + } + break; #undef ShiftedKey } diff --git a/Machines/Sinclair/Keyboard/Keyboard.hpp b/Machines/Sinclair/Keyboard/Keyboard.hpp index 1f3ec2bae..25fac9699 100644 --- a/Machines/Sinclair/Keyboard/Keyboard.hpp +++ b/Machines/Sinclair/Keyboard/Keyboard.hpp @@ -36,7 +36,7 @@ enum Key: uint16_t { // Add some virtual keys; these do not exist on a real ZX80, ZX81 or early Spectrum, those all were added to the 128kb Spectrums. // Either way, they're a convenience. KeyDelete = 0x0801, - KeyBreak, KeyLeft, KeyRight, KeyUp, KeyDown, KeyEdit + KeyBreak, KeyLeft, KeyRight, KeyUp, KeyDown, KeyEdit, KeySpectrumDot, KeyComma, }; class Keyboard { From a482ce1546c46cb711041e95a361d856823cd016 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 11:12:50 -0400 Subject: [PATCH 36/76] Adds a tape player. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 34 ++++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index d1c84ed27..daa3c751f 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -41,6 +41,7 @@ using Model = Analyser::Static::ZXSpectrum::Target::Model; template class ConcreteMachine: public Machine, public MachineTypes::MappedKeyboardMachine, + public MachineTypes::MediaTarget, public MachineTypes::ScanProducer, public MachineTypes::TimedMachine, public CPU::Z80::BusHandler { @@ -52,7 +53,8 @@ template class ConcreteMachine: mixer_(ay_, audio_toggle_), speaker_(mixer_), keyboard_(Sinclair::ZX::Keyboard::Machine::ZXSpectrum), - keyboard_mapper_(Sinclair::ZX::Keyboard::Machine::ZXSpectrum) + keyboard_mapper_(Sinclair::ZX::Keyboard::Machine::ZXSpectrum), + tape_player_(clock_rate() * 2) { set_clock_rate(clock_rate()); speaker_.set_input_rate(float(clock_rate()) / 2.0f); @@ -68,8 +70,11 @@ template class ConcreteMachine: update_memory_map(); Memory::Fuzz(ram_); - // TODO: insert media. - (void)target; + // Insert media. + insert_media(target.media); + + // TODO: intelligent motor control (?) + tape_player_.set_motor_control(true); } ~ConcreteMachine() { @@ -199,11 +204,14 @@ template class ConcreteMachine: *cycle.value = 0xff; if(!(address&1)) { - *cycle.value &= keyboard_.read(address); - + // Port FE: + // // address b8+: mask of keyboard lines to select // result: b0–b4: mask of keys pressed // b6: tape input + + *cycle.value &= keyboard_.read(address); + *cycle.value &= tape_player_.get_input() ? 0xbf : 0xff; } switch(address) { @@ -232,6 +240,9 @@ template class ConcreteMachine: if(video_.did_flush()) { z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); } + + // TODO: sleeping support here. + tape_player_.run_for(duration.as_integral()); } public: @@ -258,6 +269,16 @@ template class ConcreteMachine: keyboard_.clear_all_keys(); } + // MARK: - MediaTarget. + bool insert_media(const Analyser::Static::Media &media) override { + // If there are any tapes supplied, use the first of them. + if(!media.tapes.empty()) { + tape_player_.set_tape(media.tapes.front()); + } + + return !media.tapes.empty(); + } + private: CPU::Z80::Processor z80_; @@ -357,6 +378,9 @@ template class ConcreteMachine: // MARK: - Keyboard. Sinclair::ZX::Keyboard::Keyboard keyboard_; Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_; + + // MARK: - Tape and disc. + Storage::Tape::BinaryTapePlayer tape_player_; }; From 84774a791073e2bc82bb88cf5b979d35bf142d59 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 11:19:10 -0400 Subject: [PATCH 37/76] Update Qt and SDL build files. --- OSBindings/Qt/ClockSignal.pro | 2 ++ OSBindings/SDL/SConstruct | 1 + 2 files changed, 3 insertions(+) diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index 04e9c0b8e..a78500ab7 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -89,6 +89,7 @@ SOURCES += \ $$SRC/Machines/MSX/*.cpp \ $$SRC/Machines/Oric/*.cpp \ $$SRC/Machines/Utility/*.cpp \ + $$SRC/Machines/Sinclair/Keyboard/*.cpp \ $$SRC/Machines/Sinclair/ZX8081/*.cpp \ $$SRC/Machines/Sinclair/ZXSpectrum/*.cpp \ \ @@ -215,6 +216,7 @@ HEADERS += \ $$SRC/Machines/MSX/*.hpp \ $$SRC/Machines/Oric/*.hpp \ $$SRC/Machines/Utility/*.hpp \ + $$SRC/Machines/Sinclair/Keyboard/*.hpp \ $$SRC/Machines/Sinclair/ZX8081/*.hpp \ $$SRC/Machines/Sinclair/ZXSpectrum/*.hpp \ \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 251ba283c..6be9e9e28 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -83,6 +83,7 @@ SOURCES += glob.glob('../../Machines/MasterSystem/*.cpp') SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') +SOURCES += glob.glob('../../Machines/Sinclair/Keyboard/*.cpp') SOURCES += glob.glob('../../Machines/Sinclair/ZX8081/*.cpp') SOURCES += glob.glob('../../Machines/Sinclair/ZXSpectrum/*.cpp') From bb0d35e3d008508aa9a3179b0119daea95278d50 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 22:17:03 -0400 Subject: [PATCH 38/76] Minor formatting/layout fixes. --- Machines/MSX/MSX.cpp | 1 - Machines/Sinclair/ZX8081/ZX8081.cpp | 1 + Machines/Sinclair/ZX8081/ZX8081.hpp | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 30695d0e9..2adc8f38b 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -657,7 +657,6 @@ class ConcreteMachine: void set_options(const std::unique_ptr &str) final { const auto options = dynamic_cast(str.get()); - set_video_signal_configurable(options->output); allow_fast_tape_ = options->quickload; set_use_fast_tape(); diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index 48455a880..ff3c6c170 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -379,6 +379,7 @@ template class ConcreteMachine: } // MARK: - Configuration options. + std::unique_ptr get_options() final { auto options = std::make_unique(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional. options->automatic_tape_motor_control = use_automatic_tape_motor_control_; diff --git a/Machines/Sinclair/ZX8081/ZX8081.hpp b/Machines/Sinclair/ZX8081/ZX8081.hpp index 6a177cfbe..d16960dca 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.hpp +++ b/Machines/Sinclair/ZX8081/ZX8081.hpp @@ -23,7 +23,6 @@ namespace ZX8081 { class Machine { public: virtual ~Machine(); - static Machine *ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); virtual void set_tape_is_playing(bool is_playing) = 0; From 2ee478b4c4210b9b96944f67b567cd96a0c27eb6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 22:17:20 -0400 Subject: [PATCH 39/76] Goes some way towards wiring up Spectrum options. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 51 ++++++++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp | 22 +++++++- Machines/Utility/MachineForTarget.cpp | 7 ++- .../StaticAnalyser/CSStaticAnalyser.mm | 1 + 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index daa3c751f..ff7b46ecc 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -39,6 +39,7 @@ namespace ZXSpectrum { using Model = Analyser::Static::ZXSpectrum::Target::Model; template class ConcreteMachine: + public Configurable::Device, public Machine, public MachineTypes::MappedKeyboardMachine, public MachineTypes::MediaTarget, @@ -101,14 +102,18 @@ template class ConcreteMachine: // MARK: - ScanProducer - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { video_->set_scan_target(scan_target); } - Outputs::Display::ScanStatus get_scaled_scan_status() const final { + Outputs::Display::ScanStatus get_scaled_scan_status() const override { return video_->get_scaled_scan_status(); } + void set_display_type(Outputs::Display::DisplayType display_type) override { + video_->set_display_type(display_type); + } + // MARK: - BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { @@ -279,6 +284,40 @@ template class ConcreteMachine: return !media.tapes.empty(); } + // MARK: - Tape control + + void set_use_automatic_tape_motor_control(bool enabled) { + use_automatic_tape_motor_control_ = enabled; + if(!enabled) { + tape_player_.set_motor_control(false); + } + } + + void set_tape_is_playing(bool is_playing) final { + tape_player_.set_motor_control(is_playing); + } + + bool get_tape_is_playing() final { + return tape_player_.get_motor_control(); + } + + // MARK: - Configuration options. + + std::unique_ptr get_options() override { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional. + options->automatic_tape_motor_control = use_automatic_tape_motor_control_; + options->quickload = allow_fast_tape_hack_; + return options; + } + + void set_options(const std::unique_ptr &str) override { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); + set_use_automatic_tape_motor_control(options->automatic_tape_motor_control); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape(); + } + private: CPU::Z80::Processor z80_; @@ -381,6 +420,14 @@ template class ConcreteMachine: // MARK: - Tape and disc. Storage::Tape::BinaryTapePlayer tape_player_; + + bool use_automatic_tape_motor_control_ = false; + HalfCycles cycles_since_tape_input_read_; + + bool allow_fast_tape_hack_ = false; + void set_use_fast_tape() { + + } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp index 9a294b95d..451dd0e82 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp @@ -22,8 +22,28 @@ namespace ZXSpectrum { class Machine { public: virtual ~Machine(); - static Machine *ZXSpectrum(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + virtual void set_tape_is_playing(bool is_playing) = 0; + virtual bool get_tape_is_playing() = 0; + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption, public Configurable::QuickloadOption { + friend Configurable::DisplayOption; + friend Configurable::QuickloadOption; + public: + bool automatic_tape_motor_control; + + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour), + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) + { + if(needs_declare()) { + DeclareField(automatic_tape_motor_control); + declare_display_option(); + declare_quickload_option(); + } + } + }; }; diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index f716635f1..9632f0a84 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -39,6 +39,7 @@ #include "../../Analyser/Static/Oric/Target.hpp" #include "../../Analyser/Static/Sega/Target.hpp" #include "../../Analyser/Static/ZX8081/Target.hpp" +#include "../../Analyser/Static/ZXSpectrum/Target.hpp" #include "../../Analyser/Dynamic/MultiMachine/MultiMachine.hpp" #include "TypedDynamicMachine.hpp" @@ -132,6 +133,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) case Analyser::Machine::Oric: return "Oric"; case Analyser::Machine::Vic20: return "Vic20"; case Analyser::Machine::ZX8081: return "ZX8081"; + case Analyser::Machine::ZXSpectrum: return "ZXSpectrum"; default: return ""; } @@ -152,6 +154,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { case Analyser::Machine::Oric: return "Oric"; case Analyser::Machine::Vic20: return "Vic 20"; case Analyser::Machine::ZX8081: return "ZX80/81"; + case Analyser::Machine::ZXSpectrum: return "ZX Spectrum"; default: return ""; } @@ -203,6 +206,7 @@ std::map> Machine::AllOptionsBy Emplace(Oric, Oric::Machine); Emplace(Vic20, Commodore::Vic20::Machine); Emplace(ZX8081, Sinclair::ZX8081::Machine); + Emplace(ZXSpectrum, Sinclair::ZXSpectrum::Machine); #undef Emplace @@ -226,6 +230,7 @@ std::map> Machine::Target Add(Oric); AddMapped(Vic20, Commodore); Add(ZX8081); + Add(ZXSpectrum); if(!meaningful_without_media_only) { Add(Atari2600); @@ -234,7 +239,7 @@ std::map> Machine::Target } #undef Add -#undef AddTwo +#undef AddMapped return options; } diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 48950edca..1b087451f 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -263,6 +263,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K case Analyser::Machine::Oric: return @"OricOptions"; case Analyser::Machine::Vic20: return @"QuickLoadCompositeOptions"; case Analyser::Machine::ZX8081: return @"ZX8081Options"; + case Analyser::Machine::ZXSpectrum: return @"QuickLoadCompositeOptions"; // TODO: @"ZXSpectrumOptions"; default: return nil; } } From 7d59ff6d8f1e19a5cd008caffd8ec8c2bb13fe65 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 22:25:37 -0400 Subject: [PATCH 40/76] Builds in a colour burst, producing colour composite. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 37 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index d96d72c45..5bfcd341c 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -81,6 +81,8 @@ template class Video { constexpr int first_line = timings.first_delay / timings.cycles_per_line; constexpr int sync_position = 166 * 2; constexpr int sync_length = 14 * 2; + constexpr int burst_position = sync_position + 40; + constexpr int burst_length = 17; int cycles_remaining = duration.as(); while(cycles_remaining) { @@ -106,13 +108,25 @@ template class Video { offset += border_duration; } - if(offset >= sync_position && offset < sync_position+sync_length && end_offset > offset) { + if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) { const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; crt_.output_sync(sync_duration); offset += sync_duration; } - if(offset >= sync_position + sync_length && end_offset > offset) { + if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) { + const int blank_duration = std::min(burst_position, end_offset) - offset; + crt_.output_blank(blank_duration); + offset += blank_duration; + } + + if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { + const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; + crt_.output_default_colour_burst(burst_duration); + offset += burst_duration; + } + + if(offset >= burst_position+burst_length && end_offset > offset) { const int border_duration = end_offset - offset; output_border(border_duration); } @@ -177,7 +191,19 @@ template class Video { offset += sync_duration; } - if(offset >= sync_position + sync_length && end_offset > offset) { + if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) { + const int blank_duration = std::min(burst_position, end_offset) - offset; + crt_.output_blank(blank_duration); + offset += blank_duration; + } + + if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { + const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; + crt_.output_default_colour_burst(burst_duration); + offset += burst_duration; + } + + if(offset >= burst_position + burst_length && end_offset > offset) { const int border_duration = end_offset - offset; output_border(border_duration); } @@ -255,6 +281,11 @@ template class Video { return crt_.get_scaled_scan_status(); } + /*! Sets the type of display the CRT will request. */ + void set_display_type(Outputs::Display::DisplayType type) { + crt_.set_display_type(type); + } + private: int time_since_interrupt_ = 0; Outputs::CRT::CRT crt_; From 7729f1f3d00a3816f78f4c44790e0206422e8c75 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 22:43:48 -0400 Subject: [PATCH 41/76] Attempts automatic Spectrum tape control. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 36 ++++++++++++++++++--- Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp | 3 +- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index ff7b46ecc..84d5bd632 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -73,9 +73,6 @@ template class ConcreteMachine: // Insert media. insert_media(target.media); - - // TODO: intelligent motor control (?) - tape_player_.set_motor_control(true); } ~ConcreteMachine() { @@ -217,6 +214,23 @@ template class ConcreteMachine: *cycle.value &= keyboard_.read(address); *cycle.value &= tape_player_.get_input() ? 0xbf : 0xff; + + // If this read is within 200 cycles of the previous, + // count it as an adjacent hit; if 20 of those have + // occurred then start the tape motor. + if(use_automatic_tape_motor_control_) { + if(cycles_since_tape_input_read_ < HalfCycles(400)) { + ++recent_tape_hits_; + + if(recent_tape_hits_ == 20) { + tape_player_.set_motor_control(true); + } + } else { + recent_tape_hits_ = 0; + } + + cycles_since_tape_input_read_ = HalfCycles(0); + } } switch(address) { @@ -248,6 +262,17 @@ template class ConcreteMachine: // TODO: sleeping support here. tape_player_.run_for(duration.as_integral()); + + // Update automatic tape motor control, if enabled; if it's been + // 3 seconds since software last possibly polled the tape, stop it. + if(use_automatic_tape_motor_control_ && cycles_since_tape_input_read_ < HalfCycles(clock_rate() * 6)) { + cycles_since_tape_input_read_ += duration; + + if(cycles_since_tape_input_read_ >= HalfCycles(clock_rate() * 6)) { + tape_player_.set_motor_control(false); + recent_tape_hits_ = 0; + } + } } public: @@ -421,12 +446,13 @@ template class ConcreteMachine: // MARK: - Tape and disc. Storage::Tape::BinaryTapePlayer tape_player_; - bool use_automatic_tape_motor_control_ = false; + bool use_automatic_tape_motor_control_ = true; HalfCycles cycles_since_tape_input_read_; + int recent_tape_hits_ = 0; bool allow_fast_tape_hack_ = false; void set_use_fast_tape() { - + // TODO. } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp index 451dd0e82..693cc3164 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.hpp @@ -35,7 +35,8 @@ class Machine { Options(Configurable::OptionsType type) : Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour), - Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly), + automatic_tape_motor_control(type == Configurable::OptionsType::UserFriendly) { if(needs_declare()) { DeclareField(automatic_tape_motor_control); From 2ad2b4384b1b3e72fee9b14cf411b32b42a4d70b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 23:01:49 -0400 Subject: [PATCH 42/76] Introduces a container for ZX Spectrum-style TAPs. --- Analyser/Static/StaticAnalyser.cpp | 2 + .../Clock Signal.xcodeproj/project.pbxproj | 8 +++ Storage/Tape/Formats/ZXSpectrumTAP.cpp | 47 ++++++++++++++++ Storage/Tape/Formats/ZXSpectrumTAP.hpp | 53 +++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 Storage/Tape/Formats/ZXSpectrumTAP.cpp create mode 100644 Storage/Tape/Formats/ZXSpectrumTAP.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index b81b4a9df..28cfc894e 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -66,6 +66,7 @@ #include "../../Storage/Tape/Formats/TapeUEF.hpp" #include "../../Storage/Tape/Formats/TZX.hpp" #include "../../Storage/Tape/Formats/ZX80O81P.hpp" +#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp" // Target Platform Types #include "../../Storage/TargetPlatforms.hpp" @@ -177,6 +178,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("stx", result.disks, Disk::DiskImageHolder, TargetPlatform::AtariST) // STX Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) + Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum) Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 98375593d..ebbf7a82c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -132,6 +132,8 @@ 4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */; }; 4B0F1C1C2604EA1000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */; }; 4B0F1C1D2604EA1000B85C66 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */; }; + 4B0F1C232605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */; }; + 4B0F1C242605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; @@ -1071,6 +1073,8 @@ 4B0F1C092603BA5F00B85C66 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0F1C1A2604EA1000B85C66 /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; 4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; + 4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrumTAP.cpp; sourceTree = ""; }; + 4B0F1C222605996900B85C66 /* ZXSpectrumTAP.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrumTAP.hpp; sourceTree = ""; }; 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; @@ -2862,6 +2866,7 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */, 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */, 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */, + 4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */, 4B0E04E91FC9E5DA00F43484 /* CAS.hpp */, 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, 4B3BF5AF1F146264005B6C36 /* CSW.hpp */, @@ -2870,6 +2875,7 @@ 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, 4B448E801F1C45A00009ABD6 /* TZX.hpp */, 4B1497871EE4A1DA00CE2596 /* ZX80O81P.hpp */, + 4B0F1C222605996900B85C66 /* ZXSpectrumTAP.hpp */, 4B69FB451C4D950F00B5F0AA /* libz.tbd */, ); path = Formats; @@ -5269,6 +5275,7 @@ 4BEDA3C125B25563000C2DBD /* Decoder.cpp in Sources */, 4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */, 4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, + 4B0F1C242605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B302185208A550100773308 /* DiskII.cpp in Sources */, 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */, @@ -5376,6 +5383,7 @@ 4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */, 4B2E86B725D7490E0024F1E9 /* ReactiveDevice.cpp in Sources */, 4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, + 4B0F1C232605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */, 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B1B58FF246E19FD009C171E /* State.cpp in Sources */, diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.cpp b/Storage/Tape/Formats/ZXSpectrumTAP.cpp new file mode 100644 index 000000000..549c9ba2b --- /dev/null +++ b/Storage/Tape/Formats/ZXSpectrumTAP.cpp @@ -0,0 +1,47 @@ +// +// SpectrumTAP.cpp +// Clock Signal +// +// Created by Thomas Harte on 19/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "ZXSpectrumTAP.hpp" + +using namespace Storage::Tape; + +ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : + file_(file_name) +{ + // Check for a continuous series of blocks through to + // file end, alternating header and data. + uint8_t next_block_type = 0x00; + while(true) { + const uint16_t block_length = file_.get16le(); + const uint8_t block_type = file_.get8(); + if(file_.eof()) throw ErrorNotZXSpectrumTAP; + + if(block_type != next_block_type) { + throw ErrorNotZXSpectrumTAP; + } + next_block_type ^= 0xff; + + file_.seek(block_length - 1, SEEK_CUR); + if(file_.tell() == file_.stats().st_size) break; + } + + virtual_reset(); +} + +bool ZXSpectrumTAP::is_at_end() { + return false; +} + +void ZXSpectrumTAP::virtual_reset() { + file_.seek(0, SEEK_SET); + block_length_ = file_.get16le(); +} + +Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() { + return Pulse(); +} diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.hpp b/Storage/Tape/Formats/ZXSpectrumTAP.hpp new file mode 100644 index 000000000..5a84d909c --- /dev/null +++ b/Storage/Tape/Formats/ZXSpectrumTAP.hpp @@ -0,0 +1,53 @@ +// +// SpectrumTAP.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/03/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef SpectrumTAP_hpp +#define SpectrumTAP_hpp + +#include "../Tape.hpp" +#include "../../FileHolder.hpp" + +#include +#include + +namespace Storage { +namespace Tape { + +/*! + Provides a @c Tape containing an Spectrum-format tape image, which contains a series of + header and data blocks. +*/ +class ZXSpectrumTAP: public Tape { + public: + /*! + Constructs a @c ZXSpectrumTAP containing content from the file with name @c file_name. + + @throws ErrorNotZXSpectrumTAP if this file could not be opened and recognised as a valid Spectrum-format TAP. + */ + ZXSpectrumTAP(const std::string &file_name); + + enum { + ErrorNotZXSpectrumTAP + }; + + private: + Storage::FileHolder file_; + + uint16_t block_length_ = 0; + + // Implemented to satisfy @c Tape. + bool is_at_end() override; + void virtual_reset() override; + Pulse virtual_get_next_pulse() override; +}; + + +} +} + +#endif /* SpectrumTAP_hpp */ From 09a6a1905bbca0dd3ff891d4a97ed4a545ff88d1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 23:29:09 -0400 Subject: [PATCH 43/76] Implements TAP support. --- Storage/Tape/Formats/ZXSpectrumTAP.cpp | 85 +++++++++++++++++++++++++- Storage/Tape/Formats/ZXSpectrumTAP.hpp | 9 +++ 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.cpp b/Storage/Tape/Formats/ZXSpectrumTAP.cpp index 549c9ba2b..d97341ad2 100644 --- a/Storage/Tape/Formats/ZXSpectrumTAP.cpp +++ b/Storage/Tape/Formats/ZXSpectrumTAP.cpp @@ -10,6 +10,14 @@ using namespace Storage::Tape; +/* + The understanding of idiomatic Spectrum data encoding below + is taken from the TZX specifications at + https://worldofspectrum.net/features/TZXformat.html ; + specifics of the TAP encoding were gained from + https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format +*/ + ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : file_(file_name) { @@ -34,14 +42,85 @@ ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : } bool ZXSpectrumTAP::is_at_end() { - return false; + return file_.tell() == file_.stats().st_size; } void ZXSpectrumTAP::virtual_reset() { file_.seek(0, SEEK_SET); - block_length_ = file_.get16le(); + read_next_block(); } Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() { - return Pulse(); + // Adopt a general pattern of high then low. + Pulse pulse; + pulse.type = (distance_into_phase_ & 1) ? Pulse::Type::High : Pulse::Type::Low; + + switch(phase_) { + default: break; + + case Phase::PilotTone: { + // Output: pulses of length 2168; + // 8063 pulses if block type is 0, otherwise 3223; + // then a 667-length pulse followed by a 735-length pulse. + + pulse.length = Time(271, 437'500); // i.e. 2168 / 3'500'000 + ++distance_into_phase_; + + // Check whether in the last two. + if(distance_into_phase_ >= (block_type_ ? 8063 : 3223)) { + pulse.length = (distance_into_phase_ & 1) ? Time(667, 3'500'000) : Time(735, 3'500'000); + + // Check whether this is the last one. + if(distance_into_phase_ == (block_type_ ? 8064 : 3224)) { + distance_into_phase_ = 0; + phase_ = Phase::Data; + } + } + } break; + + case Phase::Data: { + // Output two pulses of length 855 for a 0; two of length 1710 for a 1, + // from MSB to LSB. + pulse.length = (data_byte_ & 0x80) ? Time(1710, 3'500'000) : Time(855, 3'500'000); + ++distance_into_phase_; + + if(!(distance_into_phase_ & 1)) { + data_byte_ <<= 1; + } + + if(!(distance_into_phase_ & 15)) { + if((distance_into_phase_ >> 4) == block_length_) { + if(block_type_) { + distance_into_phase_ = 0; + phase_ = Phase::Gap; + } else { + read_next_block(); + } + } else { + data_byte_ = file_.get8(); + } + } + } break; + + case Phase::Gap: + Pulse gap; + gap.type = Pulse::Type::Zero; + gap.length = Time(1); + + read_next_block(); + return gap; + } + + return pulse; +} + +void ZXSpectrumTAP::read_next_block() { + if(is_at_end()) { + phase_ = Phase::Gap; + } else { + block_length_ = file_.get16le(); + data_byte_ = block_type_ = file_.get8(); + phase_ = Phase::PilotTone; + } + distance_into_phase_ = 0; } diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.hpp b/Storage/Tape/Formats/ZXSpectrumTAP.hpp index 5a84d909c..ec1cc035c 100644 --- a/Storage/Tape/Formats/ZXSpectrumTAP.hpp +++ b/Storage/Tape/Formats/ZXSpectrumTAP.hpp @@ -39,6 +39,15 @@ class ZXSpectrumTAP: public Tape { Storage::FileHolder file_; uint16_t block_length_ = 0; + uint8_t block_type_ = 0; + uint8_t data_byte_ = 0; + enum Phase { + PilotTone, + Data, + Gap + } phase_ = Phase::PilotTone; + int distance_into_phase_ = 0; + void read_next_block(); // Implemented to satisfy @c Tape. bool is_at_end() override; From cf9a5d595bd5fa7516311e19f07a31ff4ac5ebea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Mar 2021 23:33:46 -0400 Subject: [PATCH 44/76] Completes piping of audio. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 84d5bd632..2ea43ca06 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -41,6 +41,7 @@ using Model = Analyser::Static::ZXSpectrum::Target::Model; template class ConcreteMachine: public Configurable::Device, public Machine, + public MachineTypes::AudioProducer, public MachineTypes::MappedKeyboardMachine, public MachineTypes::MediaTarget, public MachineTypes::ScanProducer, @@ -343,6 +344,12 @@ template class ConcreteMachine: set_use_fast_tape(); } + // MARK: - AudioProducer. + + Outputs::Speaker::Speaker *get_speaker() override { + return &speaker_; + } + private: CPU::Z80::Processor z80_; From 26911a16e8ba61c6caad6c9419839d5534e64aec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Mar 2021 10:38:21 -0400 Subject: [PATCH 45/76] Lengthens sync, better to conform to PAL; experiments with fixed-phase colour burst. I need to get hold of real documentation here. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 5bfcd341c..f1e2eb863 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -80,7 +80,7 @@ template class Video { constexpr auto timings = get_timings(); constexpr int first_line = timings.first_delay / timings.cycles_per_line; constexpr int sync_position = 166 * 2; - constexpr int sync_length = 14 * 2; + constexpr int sync_length = 17 * 2; constexpr int burst_position = sync_position + 40; constexpr int burst_length = 17; @@ -122,7 +122,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_default_colour_burst(burst_duration); + crt_.output_colour_burst(burst_duration, 0); offset += burst_duration; } @@ -199,7 +199,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_default_colour_burst(burst_duration); + crt_.output_colour_burst(burst_duration, 0); offset += burst_duration; } From 07a63d62dd70b16f9b9a1320ff314858408f5ec3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Mar 2021 11:19:44 -0400 Subject: [PATCH 46/76] Adds some quick arithmetic on the clock speed. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 2ea43ca06..ba63515f0 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -82,7 +82,32 @@ template class ConcreteMachine: static constexpr unsigned int clock_rate() { // constexpr unsigned int ClockRate = 3'500'000; - constexpr unsigned int Plus3ClockRate = 3'546'900; + constexpr unsigned int Plus3ClockRate = 3'546'875; // See notes below; this is a guess. + + // Notes on timing for the +2a and +3: + // + // Standard PAL produces 283.7516 colour cycles per line, each line being 64µs. + // The oft-quoted 3.5469 Mhz would seem to imply 227.0016 clock cycles per line. + // Since those Spectrums actually produce 228 cycles per line, but software like + // Chromatrons seems to assume a fixed phase relationship, I guess that the real + // clock speed is whatever gives: + // + // 228 / [cycles per line] * 283.7516 = [an integer]. + // + // i.e. 228 * 283.7516 = [an integer] * [cycles per line], such that cycles per line ~= 227 + // ... which would imply that 'an integer' is probably 285, i.e. + // + // 228 / [cycles per line] * 283.7516 = 285 + // => 227.00128 = [cycles per line] + // => clock rate = 3.546895 Mhz? + // + // That is... unless I'm mistaken about the PAL colour subcarrier and it's actually 283.75, + // which would give exactly 227 cycles/line and therefore 3.546875 Mhz. + // + // A real TV would be likely to accept either, I guess. But it does seem like + // the Spectrum is a PAL machine with a fixed colour phase relationship. For + // this emulator's world, that's a first! + return Plus3ClockRate; } From 986c4006a664b6d05ad334ec33387a68cdfabf58 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Mar 2021 23:45:49 -0400 Subject: [PATCH 47/76] Corrected: PAL machines can now be overt in terms of odd/even colour burst. --- Outputs/CRT/CRT.cpp | 9 +++++---- Outputs/CRT/CRT.hpp | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c72d98a39..ad1d3cf1c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -32,7 +32,7 @@ void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Di phase_numerator_ = 0; colour_cycle_numerator_ = int64_t(colour_cycle_numerator); phase_alternates_ = should_alternate; - is_alernate_line_ &= phase_alternates_; + should_be_alternate_line_ &= phase_alternates_; cycles_per_line_ = cycles_per_line; const int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; @@ -275,7 +275,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // If retrace is starting, update phase if required and mark no colour burst spotted yet. if(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { - is_alernate_line_ ^= phase_alternates_; + should_be_alternate_line_ ^= phase_alternates_; colour_burst_amplitude_ = 0; } } @@ -408,18 +408,19 @@ void CRT::output_level(int number_of_cycles) { output_scan(&scan); } -void CRT::output_colour_burst(int number_of_cycles, uint8_t phase, uint8_t amplitude) { +void CRT::output_colour_burst(int number_of_cycles, uint8_t phase, bool is_alternate_line, uint8_t amplitude) { Scan scan; scan.type = Scan::Type::ColourBurst; scan.number_of_cycles = number_of_cycles; scan.phase = phase; scan.amplitude = amplitude >> 1; + is_alernate_line_ = is_alternate_line; output_scan(&scan); } void CRT::output_default_colour_burst(int number_of_cycles, uint8_t amplitude) { // TODO: avoid applying a rounding error here? - output_colour_burst(number_of_cycles, uint8_t((phase_numerator_ * 256) / phase_denominator_), amplitude); + output_colour_burst(number_of_cycles, uint8_t((phase_numerator_ * 256) / phase_denominator_), should_be_alternate_line_, amplitude); } void CRT::set_immediate_default_phase(float phase) { diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 7ab9dbcf5..9749d5011 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -61,7 +61,7 @@ class CRT { int64_t phase_denominator_ = 1; int64_t phase_numerator_ = 0; int64_t colour_cycle_numerator_ = 1; - bool is_alernate_line_ = false, phase_alternates_ = false; + bool is_alernate_line_ = false, phase_alternates_ = false, should_be_alternate_line_ = false; void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples); Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced); @@ -207,7 +207,7 @@ class CRT { @param amplitude The amplitude of the colour burst in 1/255ths of the amplitude of the positive portion of the wave. */ - void output_colour_burst(int number_of_cycles, uint8_t phase, uint8_t amplitude = DefaultAmplitude); + void output_colour_burst(int number_of_cycles, uint8_t phase, bool is_alternate_line = false, uint8_t amplitude = DefaultAmplitude); /*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude. From 42bfabbe8cec0039b5630a1dde668b0a886468f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Mar 2021 23:46:13 -0400 Subject: [PATCH 48/76] The implication seems to be of a fixed phase swing. I'm enquiring further. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index f1e2eb863..29368bc81 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -122,7 +122,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_colour_burst(burst_duration, 0); + crt_.output_colour_burst(burst_duration, (line&1) * 128); offset += burst_duration; } @@ -199,7 +199,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_colour_burst(burst_duration, 0); + crt_.output_colour_burst(burst_duration, (line&1) * 128); offset += burst_duration; } From 1b0f45649ee79ee4f72a55b0f12482ef61504b04 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 00:00:18 -0400 Subject: [PATCH 49/76] Improves contended timing. Still not quite on the money, but this was an overt bug. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 29368bc81..86921fa71 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -264,7 +264,7 @@ template class Video { const int line_position = time_since % timings.cycles_per_line; if(line_position >= timings.first_border - timings.first_delay) return 0; - return timings.delays[line_position & 7]; + return timings.delays[line_position & 15]; } void set_border_colour(uint8_t colour) { From 58be770eaa15af2246d3c4044b2614762e96d1ba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 00:14:48 -0400 Subject: [PATCH 50/76] Factors out some boilerplate. When I'm confident this is correct, I can fix up the other call sites. --- Components/AY38910/AY38910.hpp | 25 +++++++++++++++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 12 +++------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 34a8302fd..4f7c226f0 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -164,6 +164,31 @@ template class AY38910: public ::Outputs::Speaker::SampleSource uint8_t c_left_ = 255, c_right_ = 255; }; +/*! + Provides helper code, to provide something closer to the interface exposed by many + AY-deploying machines of the era. +*/ +struct Utility { + template static void select_register(AY &ay, uint8_t reg) { + ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | GI::AY38910::BC1)); + ay.set_data_input(reg); + ay.set_control_lines(GI::AY38910::ControlLines(0)); + } + + template static void write_data(AY &ay, uint8_t reg) { + ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2)); + ay.set_data_input(reg); + ay.set_control_lines(GI::AY38910::ControlLines(0)); + } + + template static uint8_t read_data(AY &ay) { + ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); + const uint8_t result = ay.get_data_output(); + ay.set_control_lines(GI::AY38910::ControlLines(0)); + return result; + } + +}; } } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index ba63515f0..eecf0a86a 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -213,17 +213,13 @@ template class ConcreteMachine: case 0xfffd: // Select AY register. update_audio(); - ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | GI::AY38910::BC1)); - ay_.set_data_input(*cycle.value); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); + GI::AY38910::Utility::select_register(ay_, *cycle.value); break; case 0xbffd: // Write to AY register. update_audio(); - ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2)); - ay_.set_data_input(*cycle.value); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); + GI::AY38910::Utility::write_data(ay_, *cycle.value); break; } break; @@ -265,9 +261,7 @@ template class ConcreteMachine: case 0xfffd: // Read from AY register. update_audio(); - ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); - *cycle.value &= ay_.get_data_output(); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); + *cycle.value &= GI::AY38910::Utility::read_data(ay_); break; } break; From 064667c0c37027ca1c0283046fdea6bdc6692b37 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 20:22:27 -0400 Subject: [PATCH 51/76] Corrects asymmetrical flash, ensures consistent burst phase. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 86921fa71..891c8bee0 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -91,10 +91,13 @@ template class Video { const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset); const int end_offset = offset + cycles_this_line; - if(!line && !offset) { - ++flash_counter_; - flash_mask_ = uint8_t(flash_counter_ >> 4); - flash_counter_ &= 31; + if(!offset) { + is_alternate_line_ ^= true; + + if(!line) { + flash_counter_ = (flash_counter_ + 1) & 31; + flash_mask_ = uint8_t(flash_counter_ >> 4); + } } if(line < 3) { @@ -122,7 +125,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_colour_burst(burst_duration, (line&1) * 128); + crt_.output_colour_burst(burst_duration, line, is_alternate_line_); offset += burst_duration; } @@ -199,7 +202,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_colour_burst(burst_duration, (line&1) * 128); + crt_.output_colour_burst(burst_duration, 0, is_alternate_line_); offset += burst_duration; } @@ -298,6 +301,7 @@ template class Video { uint8_t flash_mask_ = 0; int flash_counter_ = 0; + bool is_alternate_line_ = false; #define RGB(r, g, b) (r << 4) | (g << 2) | b static constexpr uint8_t palette[] = { From 9ce1dbaebb59b73dcd9f83acbfb2667c3f0552a1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 20:23:00 -0400 Subject: [PATCH 52/76] Switches to partial decoding for paging registers; permits video address changes after paging is locked. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 44 ++++++++++----------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index eecf0a86a..5448a54bf 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -70,6 +70,7 @@ template class ConcreteMachine: // Set up initial memory map. update_memory_map(); + set_video_address(); Memory::Fuzz(ram_); // Insert media. @@ -186,30 +187,26 @@ template class ConcreteMachine: // b4: tape and speaker output } - // Test for classic 128kb paging. -// if(!(address&0x8002)) { -// } + // Test for classic 128kb paging register. + if((address & 0xc002) == 0x4000) { + disable_paging_ |= *cycle.value & 0x20; + + // Set the proper video base pointer. + set_video_address(); + + port7ffd_ = *cycle.value; + update_memory_map(); + } // Test for +2a/+3 paging. -// if((address & 0xc002) == 0x4000) { -// } + if((address & 0xf002) == 0x1000) { + port1ffd_ = *cycle.value; + update_memory_map(); + } switch(address) { default: break; - case 0x1ffd: - // Write to +2a/+3 paging register. - port1ffd_ = *cycle.value; - update_memory_map(); - break; - - case 0x7ffd: - // Write to classic 128kb paging register. - disable_paging_ |= *cycle.value & 0x20; - port7ffd_ = *cycle.value; - update_memory_map(); - break; - case 0xfffd: // Select AY register. update_audio(); @@ -391,14 +388,11 @@ template class ConcreteMachine: return; } - // Set the proper video base pointer. - video_->set_video_source(&ram_[((port7ffd_ & 0x08) ? 7 : 5) * 16384]); - - if(port1ffd_ & 1) { + if(port1ffd_ & 0x01) { // "Special paging mode", i.e. one of four fixed // RAM configurations, port 7ffd doesn't matter. - switch(port1ffd_ & 0x6) { + switch(port1ffd_ & 0x06) { default: case 0x00: set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384], false); @@ -449,6 +443,10 @@ template class ConcreteMachine: write_pointers_[bank] = (write ? write : scratch_.data()) - bank*16384; } + void set_video_address() { + video_->set_video_source(&ram_[((port7ffd_ & 0x08) ? 7 : 5) * 16384]); + } + // MARK: - Audio. Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; From 388b13698094ab0d58e8bcd758e8a33375211acb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 20:31:09 -0400 Subject: [PATCH 53/76] Relaxes test for a valid TAP. --- Storage/Tape/Formats/ZXSpectrumTAP.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.cpp b/Storage/Tape/Formats/ZXSpectrumTAP.cpp index d97341ad2..080dcba11 100644 --- a/Storage/Tape/Formats/ZXSpectrumTAP.cpp +++ b/Storage/Tape/Formats/ZXSpectrumTAP.cpp @@ -22,19 +22,15 @@ ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : file_(file_name) { // Check for a continuous series of blocks through to - // file end, alternating header and data. - uint8_t next_block_type = 0x00; + // exactly file end. + // + // To consider: could also check those blocks of type 0 + // and type ff for valid checksums? while(true) { const uint16_t block_length = file_.get16le(); - const uint8_t block_type = file_.get8(); if(file_.eof()) throw ErrorNotZXSpectrumTAP; - if(block_type != next_block_type) { - throw ErrorNotZXSpectrumTAP; - } - next_block_type ^= 0xff; - - file_.seek(block_length - 1, SEEK_CUR); + file_.seek(block_length, SEEK_CUR); if(file_.tell() == file_.stats().st_size) break; } From 6482303063455129047ec206dc2bcfc33a761385 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 20:34:58 -0400 Subject: [PATCH 54/76] Reduces code duplication slightly. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 127 +++++++++++-------------- 1 file changed, 54 insertions(+), 73 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 891c8bee0..78e7978b5 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -103,92 +103,73 @@ template class Video { if(line < 3) { // Output sync line. crt_.output_sync(cycles_this_line); - } else if((line < first_line) || (line >= first_line+192)) { - // Output plain border line. - if(offset < sync_position) { - const int border_duration = std::min(sync_position, end_offset) - offset; - output_border(border_duration); - offset += border_duration; - } - - if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) { - const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; - crt_.output_sync(sync_duration); - offset += sync_duration; - } - - if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) { - const int blank_duration = std::min(burst_position, end_offset) - offset; - crt_.output_blank(blank_duration); - offset += blank_duration; - } - - if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { - const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - crt_.output_colour_burst(burst_duration, line, is_alternate_line_); - offset += burst_duration; - } - - if(offset >= burst_position+burst_length && end_offset > offset) { - const int border_duration = end_offset - offset; - output_border(border_duration); - } } else { - // Output pixel line. - if(offset < 256) { - const int pixel_duration = std::min(256, end_offset) - offset; - - if(!offset) { - const int pixel_line = line - first_line; - - pixel_target_ = crt_.begin_data(256); - attribute_address_ = ((pixel_line / 8) * 32) + 6144; - pixel_address_ = ((pixel_line & 0x07) << 8) | ((pixel_line&0x38) << 2) | ((pixel_line&0xc0) << 5); + if((line < first_line) || (line >= first_line+192)) { + // Output plain border line. + if(offset < sync_position) { + const int border_duration = std::min(sync_position, end_offset) - offset; + output_border(border_duration); + offset += border_duration; } + } else { + // Output pixel line. + if(offset < 256) { + const int pixel_duration = std::min(256, end_offset) - offset; - if(pixel_target_) { - const int start_column = offset >> 3; - const int end_column = (offset + pixel_duration) >> 3; - for(int column = start_column; column < end_column; column++) { - const uint8_t attributes = memory_[attribute_address_]; + if(!offset) { + const int pixel_line = line - first_line; - constexpr uint8_t masks[] = {0, 0xff}; - const uint8_t pixels = memory_[pixel_address_] ^ masks[flash_mask_ & (attributes >> 7)]; + pixel_target_ = crt_.begin_data(256); + attribute_address_ = ((pixel_line / 8) * 32) + 6144; + pixel_address_ = ((pixel_line & 0x07) << 8) | ((pixel_line&0x38) << 2) | ((pixel_line&0xc0) << 5); + } - const uint8_t colours[2] = { - palette[(attributes & 0x78) >> 3], - palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], - }; + if(pixel_target_) { + const int start_column = offset >> 3; + const int end_column = (offset + pixel_duration) >> 3; + for(int column = start_column; column < end_column; column++) { + const uint8_t attributes = memory_[attribute_address_]; - pixel_target_[0] = colours[(pixels >> 7) & 1]; - pixel_target_[1] = colours[(pixels >> 6) & 1]; - pixel_target_[2] = colours[(pixels >> 5) & 1]; - pixel_target_[3] = colours[(pixels >> 4) & 1]; - pixel_target_[4] = colours[(pixels >> 3) & 1]; - pixel_target_[5] = colours[(pixels >> 2) & 1]; - pixel_target_[6] = colours[(pixels >> 1) & 1]; - pixel_target_[7] = colours[(pixels >> 0) & 1]; - pixel_target_ += 8; + constexpr uint8_t masks[] = {0, 0xff}; + const uint8_t pixels = memory_[pixel_address_] ^ masks[flash_mask_ & (attributes >> 7)]; - ++pixel_address_; - ++attribute_address_; + const uint8_t colours[2] = { + palette[(attributes & 0x78) >> 3], + palette[((attributes & 0x40) >> 3) | (attributes & 0x07)], + }; + + pixel_target_[0] = colours[(pixels >> 7) & 1]; + pixel_target_[1] = colours[(pixels >> 6) & 1]; + pixel_target_[2] = colours[(pixels >> 5) & 1]; + pixel_target_[3] = colours[(pixels >> 4) & 1]; + pixel_target_[4] = colours[(pixels >> 3) & 1]; + pixel_target_[5] = colours[(pixels >> 2) & 1]; + pixel_target_[6] = colours[(pixels >> 1) & 1]; + pixel_target_[7] = colours[(pixels >> 0) & 1]; + pixel_target_ += 8; + + ++pixel_address_; + ++attribute_address_; + } + } + + offset += pixel_duration; + if(offset == 256) { + crt_.output_data(256); + pixel_target_ = nullptr; } } - offset += pixel_duration; - if(offset == 256) { - crt_.output_data(256); - pixel_target_ = nullptr; + if(offset >= 256 && offset < sync_position && end_offset > offset) { + const int border_duration = std::min(sync_position, end_offset) - offset; + output_border(border_duration); + offset += border_duration; } } - if(offset >= 256 && offset < sync_position && end_offset > offset) { - const int border_duration = std::min(sync_position, end_offset) - offset; - output_border(border_duration); - offset += border_duration; - } + // Output the common tail to border and pixel lines: sync, blank, colour burst, border. - if(offset >= sync_position && offset < sync_position+sync_length && end_offset > offset) { + if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) { const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; crt_.output_sync(sync_duration); offset += sync_duration; @@ -206,7 +187,7 @@ template class Video { offset += burst_duration; } - if(offset >= burst_position + burst_length && end_offset > offset) { + if(offset >= burst_position+burst_length && end_offset > offset) { const int border_duration = end_offset - offset; output_border(border_duration); } From 3925eee575da244003908662622686f8d47758ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 21:03:35 -0400 Subject: [PATCH 55/76] Attempts more relaxed decoding of AY accesses. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 34 ++++++++------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 5448a54bf..264bc9df8 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -204,20 +204,16 @@ template class ConcreteMachine: update_memory_map(); } - switch(address) { - default: break; + if((address & 0xc002) == 0xc000) { + // Select AY register. + update_audio(); + GI::AY38910::Utility::select_register(ay_, *cycle.value); + } - case 0xfffd: - // Select AY register. - update_audio(); - GI::AY38910::Utility::select_register(ay_, *cycle.value); - break; - - case 0xbffd: - // Write to AY register. - update_audio(); - GI::AY38910::Utility::write_data(ay_, *cycle.value); - break; + if((address & 0xc002) == 0x8000) { + // Write to AY register. + update_audio(); + GI::AY38910::Utility::write_data(ay_, *cycle.value); } break; @@ -252,14 +248,10 @@ template class ConcreteMachine: } } - switch(address) { - default: break; - - case 0xfffd: - // Read from AY register. - update_audio(); - *cycle.value &= GI::AY38910::Utility::read_data(ay_); - break; + if((address & 0xc002) == 0xc000) { + // Read from AY register. + update_audio(); + *cycle.value &= GI::AY38910::Utility::read_data(ay_); } break; } From 75629177402846bbd2020000871c4822842cbd5b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Mar 2021 21:50:50 -0400 Subject: [PATCH 56/76] Adds the Spectrum to the macOS New... menu. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 6 +- .../Machine/StaticAnalyser/CSStaticAnalyser.h | 6 ++ .../StaticAnalyser/CSStaticAnalyser.mm | 15 +++++ .../Base.lproj/MachinePicker.xib | 56 ++++++++++++++++--- .../MachinePicker/MachinePicker.swift | 23 +++++++- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 264bc9df8..a16e36129 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -189,13 +189,15 @@ template class ConcreteMachine: // Test for classic 128kb paging register. if((address & 0xc002) == 0x4000) { - disable_paging_ |= *cycle.value & 0x20; - // Set the proper video base pointer. set_video_address(); port7ffd_ = *cycle.value; update_memory_map(); + + // Potentially lock paging, _after_ the current + // port values have taken effect. + disable_paging_ |= *cycle.value & 0x20; } // Test for +2a/+3 paging. diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 964397320..1193281f4 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -62,6 +62,11 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) { CSMachineOricDiskInterfaceBD500 }; +typedef NS_ENUM(NSInteger, CSMachineSpectrumModel) { + CSMachineSpectrumModelPlus2a, + CSMachineSpectrumModelPlus3, +}; + typedef NS_ENUM(NSInteger, CSMachineVic20Region) { CSMachineVic20RegionAmerican, CSMachineVic20RegionEuropean, @@ -90,6 +95,7 @@ typedef int Kilobytes; - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model; - (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive; - (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface; +- (instancetype)initWithSpectrumModel:(CSMachineSpectrumModel)model; - (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540; - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 1b087451f..993a5c047 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -23,6 +23,7 @@ #include "../../../../../Analyser/Static/MSX/Target.hpp" #include "../../../../../Analyser/Static/Oric/Target.hpp" #include "../../../../../Analyser/Static/ZX8081/Target.hpp" +#include "../../../../../Analyser/Static/ZXSpectrum/Target.hpp" #import "Clock_Signal-Swift.h" @@ -187,6 +188,20 @@ return self; } +- (instancetype)initWithSpectrumModel:(CSMachineSpectrumModel)model { + self = [super init]; + if(self) { + using Target = Analyser::Static::ZXSpectrum::Target; + auto target = std::make_unique(); + switch(model) { + case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break; + case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break; + } + _targets.push_back(std::move(target)); + } + return self; +} + - (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540 { self = [super init]; if(self) { diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index e8579466d..b1a4f0f05 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -17,14 +17,14 @@ - - + + - +