From df01c7803917e761611c18ba9d53d9b47775ff43 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 07:39:48 -0400 Subject: [PATCH 01/60] It's a bit of a mess but this is probably close to appropriate for Oric TAP files. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 + OSBindings/Mac/Clock Signal/Info.plist | 2 +- .../StaticAnalyser/CSStaticAnalyser.mm | 4 +- StaticAnalyser/StaticAnalyser.cpp | 7 +- StaticAnalyser/StaticAnalyser.hpp | 3 +- Storage/Tape/Formats/OricTAP.cpp | 171 ++++++++++++++++++ Storage/Tape/Formats/OricTAP.hpp | 59 ++++++ 7 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 Storage/Tape/Formats/OricTAP.cpp create mode 100644 Storage/Tape/Formats/OricTAP.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 984fb1149..29149a3e4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; + 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; @@ -462,6 +463,8 @@ 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = ""; }; + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = ""; }; 4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = ""; }; 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = ""; }; 4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = ""; }; @@ -1138,6 +1141,8 @@ 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */, 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */, + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */, + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */, ); path = Formats; sourceTree = ""; @@ -2160,6 +2165,7 @@ 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */, + 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 81a0c5b10..bdc20fa2b 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -94,7 +94,7 @@ tap CFBundleTypeName - Commodore Tape Image + Tape Image CFBundleTypeRole Viewer LSTypeIsPackage diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 1cf33e3ee..beb380dea 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -41,9 +41,8 @@ case StaticAnalyser::Target::Electron: return @"ElectronOptions"; case StaticAnalyser::Target::Vic20: return @"Vic20Options"; case StaticAnalyser::Target::Atari2600: return @"Atari2600Options"; + default: return nil; } - - return nil; } - (CSMachine *)newMachine @@ -53,6 +52,7 @@ case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; + default: return nil; } } diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 78647447e..9abbe86b1 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -27,6 +27,7 @@ // Tapes #include "../Storage/Tape/Formats/CommodoreTAP.hpp" +#include "../Storage/Tape/Formats/OricTAP.hpp" #include "../Storage/Tape/Formats/TapePRG.hpp" #include "../Storage/Tape/Formats/TapeUEF.hpp" @@ -34,7 +35,8 @@ typedef int TargetPlatformType; enum class TargetPlatform: TargetPlatformType { Acorn = 1 << 0, Atari2600 = 1 << 1, - Commodore = 1 << 2 + Commodore = 1 << 2, + Oric = 1 << 3 }; using namespace StaticAnalyser; @@ -104,7 +106,8 @@ std::list StaticAnalyser::GetTargets(const char *file_name) Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD - Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP + Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) + Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) #undef Format diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 94df52560..ca20b271a 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -33,7 +33,8 @@ struct Target { enum { Atari2600, Electron, - Vic20 + Vic20, + Oric } machine; float probability; diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp new file mode 100644 index 000000000..5a247f536 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -0,0 +1,171 @@ +// +// OricTAP.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "OricTAP.hpp" + +#include + +using namespace Storage::Tape; + +OricTAP::OricTAP(const char *file_name) : _file(NULL) +{ + struct stat file_stats; + stat(file_name, &file_stats); + _file_length = (size_t)file_stats.st_size; + + _file = fopen(file_name, "rb"); + + if(!_file) + throw ErrorNotOricTAP; + + // read and check the file signature + uint8_t signature[4]; + if(fread(signature, 1, 4, _file) != 4) + throw ErrorNotOricTAP; + + if(signature[0] != 0x16 || signature[1] != 0x16 || signature[2] != 0x16 || signature[3] != 0x24) + throw ErrorNotOricTAP; + + // then rewind and start again + virtual_reset(); +} + +OricTAP::~OricTAP() +{ + if(_file) fclose(_file); +} + +void OricTAP::virtual_reset() +{ + fseek(_file, 0, SEEK_SET); + _bit_count = 13; + _phase = LeadIn; + _phase_counter = 0; + _pulse_counter = 0; +} + +Tape::Pulse OricTAP::virtual_get_next_pulse() +{ + // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s. + if(_bit_count == 13) + { + if(_next_phase != _phase) + { + _phase = _next_phase; + _phase_counter = 0; + } + + _bit_count = 0; + uint8_t next_byte = 0; + switch(_phase) + { + case LeadIn: + next_byte = 0x16; + _phase_counter++; + if(_phase_counter == 259) // TODO + { + _next_phase = Header; + } + break; + + case Header: + next_byte = (uint8_t)fgetc(_file); + + // TODO + if(_phase_counter == 4) _body_length = next_byte; + if(_phase_counter == 6) _body_length |= (uint16_t)next_byte << 8; + + _phase_counter++; + if(_phase_counter == 10) // TODO + { + _next_phase = Pause; + } + break; + + case Data: + next_byte = (uint8_t)fgetc(_file); + _phase_counter++; + if(_phase_counter == _body_length) + { + _phase_counter = 0; + if((size_t)ftell(_file) == _file_length) + { + _next_phase = End; + } + else + { + _next_phase = LeadIn; + } + } + break; + + case Pause: + _phase_counter++; + if(_phase_counter == 2) + { + _phase_counter = 0; + _next_phase = Data; + } + break; + + case End: + break; + } + + // TODO: which way round are bytes streamed? + uint8_t parity = next_byte; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); // TODO: parity odd or even? + _current_value = (uint16_t)(((uint16_t)next_byte << 1) | (7 << 10) | ((parity&1) << 9)); + } + + // In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz. + // In fast mode, a 1 is a single period of 2400 Hz, a 0 is a 2400 Hz pulse followed by a 1200 Hz pulse. + // This code models fast mode. + Tape::Pulse pulse; + pulse.length.clock_rate = 4800; + + switch(_phase) + { + case Pause: + pulse.type = Pulse::High; // TODO + pulse.length.length = 20; // TODO + _bit_count = 13; + return pulse; + + case End: + pulse.type = Pulse::Zero; + pulse.length.length = 4800; + return pulse; + + default: + if(_current_value & 1) + { + pulse.length.length = 1; + } + else + { + pulse.length.length = _pulse_counter ? 2 : 1; + } + pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO + + _pulse_counter ^= 1; + if(!_pulse_counter) + { + _current_value >>= 1; + _bit_count++; + } + return pulse; + } +} + +bool OricTAP::is_at_end() +{ + return _phase == End; +} diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp new file mode 100644 index 000000000..a45153044 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -0,0 +1,59 @@ +// +// OricTAP.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef OricTAP_hpp +#define OricTAP_hpp + +#include "../Tape.hpp" +#include + +namespace Storage { +namespace Tape { + +/*! + Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. +*/ +class OricTAP: public Tape { + public: + /*! + Constructs an @c OricTAP containing content from the file with name @c file_name. + + @throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP. + */ + OricTAP(const char *file_name); + ~OricTAP(); + + enum { + ErrorNotOricTAP + }; + + // implemented to satisfy @c Tape + bool is_at_end(); + + private: + void virtual_reset(); + Pulse virtual_get_next_pulse(); + + FILE *_file; + size_t _file_length; + + uint16_t _current_value; + int _bit_count; + int _pulse_counter; + int _phase_counter; + + enum Phase { + LeadIn, Header, Pause, Data, End + } _phase, _next_phase; + uint16_t _body_length; +}; + +} +} + +#endif /* OricTAP_hpp */ From 70f004efbbd6fb33d661c43b5f1cf7863ec049a6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 07:57:10 -0400 Subject: [PATCH 02/60] This may be feeding bits in the wrong direction or calculting the wrong parity or doing something else amiss but should now be correct as to bytes. --- Storage/Tape/Formats/OricTAP.cpp | 40 ++++++++++++++------------------ Storage/Tape/Formats/OricTAP.hpp | 4 ++-- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 5a247f536..650580204 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -67,30 +67,41 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() case LeadIn: next_byte = 0x16; _phase_counter++; - if(_phase_counter == 259) // TODO + if(_phase_counter == 256) // 256 artificial bytes plus the three in the file = 259 { _next_phase = Header; } break; case Header: + // Counts are relative to: + // [0, 2]: value 0x16 + // 3: value '$' + // [4, 5]: "two bytes unused" (on the Oric 1) + // 6: program type + // 7: auto indicator + // [8, 9]: end address of data + // [10, 11]: start address of data + // 12: "unused" (on the Oric 1) + // [13...]: filename, up to NULL byte next_byte = (uint8_t)fgetc(_file); - // TODO - if(_phase_counter == 4) _body_length = next_byte; - if(_phase_counter == 6) _body_length |= (uint16_t)next_byte << 8; + if(_phase_counter == 8) _data_end_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 9) _data_end_address |= next_byte; + if(_phase_counter == 10) _data_start_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 11) _data_start_address |= next_byte; _phase_counter++; - if(_phase_counter == 10) // TODO + if(_phase_counter > 12 && !next_byte) // advance after the filename-ending NULL byte { - _next_phase = Pause; + _next_phase = Data; } break; case Data: next_byte = (uint8_t)fgetc(_file); _phase_counter++; - if(_phase_counter == _body_length) + if(_phase_counter == (_data_end_address - _data_start_address)) { _phase_counter = 0; if((size_t)ftell(_file) == _file_length) @@ -104,15 +115,6 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() } break; - case Pause: - _phase_counter++; - if(_phase_counter == 2) - { - _phase_counter = 0; - _next_phase = Data; - } - break; - case End: break; } @@ -133,12 +135,6 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() switch(_phase) { - case Pause: - pulse.type = Pulse::High; // TODO - pulse.length.length = 20; // TODO - _bit_count = 13; - return pulse; - case End: pulse.type = Pulse::Zero; pulse.length.length = 4800; diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp index a45153044..803643926 100644 --- a/Storage/Tape/Formats/OricTAP.hpp +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -48,9 +48,9 @@ class OricTAP: public Tape { int _phase_counter; enum Phase { - LeadIn, Header, Pause, Data, End + LeadIn, Header, Data, End } _phase, _next_phase; - uint16_t _body_length; + uint16_t _data_end_address, _data_start_address; }; } From 4f78d693e926fc33a3c788e892ae614e576586c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 08:07:51 -0400 Subject: [PATCH 03/60] Reintroduced gap as a string of 1s, made an attempt to look up bit ordering. Still unclear on high/low versus low/high. --- Storage/Tape/Formats/OricTAP.cpp | 55 ++++++++++++++++++++------------ Storage/Tape/Formats/OricTAP.hpp | 2 +- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 650580204..92058bf70 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -93,6 +93,14 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() _phase_counter++; if(_phase_counter > 12 && !next_byte) // advance after the filename-ending NULL byte + { + _next_phase = Gap; + } + break; + + case Gap: + _phase_counter++; + if(_phase_counter == 8) { _next_phase = Data; } @@ -119,12 +127,11 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() break; } - // TODO: which way round are bytes streamed? uint8_t parity = next_byte; parity ^= (parity >> 4); parity ^= (parity >> 2); - parity ^= (parity >> 1); // TODO: parity odd or even? - _current_value = (uint16_t)(((uint16_t)next_byte << 1) | (7 << 10) | ((parity&1) << 9)); + parity ^= (parity >> 1); + _current_value = (uint16_t)(((uint16_t)next_byte << 1) | ((parity&1) << 9) | (7 << 10)); } // In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz. @@ -132,6 +139,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() // This code models fast mode. Tape::Pulse pulse; pulse.length.clock_rate = 4800; + int next_bit; switch(_phase) { @@ -140,25 +148,32 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() pulse.length.length = 4800; return pulse; - default: - if(_current_value & 1) - { - pulse.length.length = 1; - } - else - { - pulse.length.length = _pulse_counter ? 2 : 1; - } - pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO + case Gap: + next_bit = 1; + break; - _pulse_counter ^= 1; - if(!_pulse_counter) - { - _current_value >>= 1; - _bit_count++; - } - return pulse; + default: + next_bit = _current_value & 1; + break; } + + if(next_bit) + { + pulse.length.length = 1; + } + else + { + pulse.length.length = _pulse_counter ? 2 : 1; + } + pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO + + _pulse_counter ^= 1; + if(!_pulse_counter) + { + _current_value >>= 1; + _bit_count++; + } + return pulse; } bool OricTAP::is_at_end() diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp index 803643926..af3eaf7f4 100644 --- a/Storage/Tape/Formats/OricTAP.hpp +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -48,7 +48,7 @@ class OricTAP: public Tape { int _phase_counter; enum Phase { - LeadIn, Header, Data, End + LeadIn, Header, Data, Gap, End } _phase, _next_phase; uint16_t _data_end_address, _data_start_address; }; From cbc3d28217ee5514ebcda6fcd75ccd53d5052e29 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 21:03:01 -0400 Subject: [PATCH 04/60] Ensured an exception is thrown if no machine to run a file is found. E.g. right now if you tried to open a ZX Spectrum .tap. --- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index d709fef5b..a29f1fad4 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -120,6 +120,8 @@ class MachineDocument: if let analyser = CSStaticAnalyser(fileAt: url) { self.displayName = analyser.displayName self.configureAs(analyser) + } else { + throw NSError(domain: "MachineDocument", code: -1, userInfo: nil) } } From 4a062c616f2b757a862d8d990dd5a2d7565d4380 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 22:20:13 -0400 Subject: [PATCH 05/60] Added enough wiring to get Oric TAPs through to a completely unimplemented Oric emulation. --- Machines/Electron/Electron.hpp | 4 +- Machines/Oric/Oric.cpp | 43 +++++++++++++++ Machines/Oric/Oric.hpp | 52 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 34 ++++++++++++ .../ClockSignal-Bridging-Header.h | 1 + .../StaticAnalyser/CSStaticAnalyser.mm | 12 ++++- .../Machine/Wrappers/CSElectron.mm | 1 - .../Clock Signal/Machine/Wrappers/CSOric.h | 13 +++++ .../Clock Signal/Machine/Wrappers/CSOric.mm | 26 ++++++++++ StaticAnalyser/Oric/StaticAnalyser.cpp | 28 ++++++++++ StaticAnalyser/Oric/StaticAnalyser.hpp | 28 ++++++++++ StaticAnalyser/StaticAnalyser.cpp | 4 +- 12 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 Machines/Oric/Oric.cpp create mode 100644 Machines/Oric/Oric.hpp create mode 100644 OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h create mode 100644 OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm create mode 100644 StaticAnalyser/Oric/StaticAnalyser.cpp create mode 100644 StaticAnalyser/Oric/StaticAnalyser.hpp diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index e2659146e..12c8fbd24 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -148,13 +148,15 @@ class Machine: Machine(); void set_rom(ROMSlot slot, std::vector data, bool is_writeable); - void configure_as_target(const StaticAnalyser::Target &target); void set_key_state(Key key, bool isPressed); void clear_all_keys(); inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } + // to satisfy ConfigurationTarget::Machine + void configure_as_target(const StaticAnalyser::Target &target); + // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void synchronise(); diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp new file mode 100644 index 000000000..7038c66b1 --- /dev/null +++ b/Machines/Oric/Oric.cpp @@ -0,0 +1,43 @@ +// +// Oric.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Oric.hpp" + +using namespace Oric; + +Machine::Machine() +{ + set_clock_rate(1000000); +} + +void Machine::configure_as_target(const StaticAnalyser::Target &target) +{ +} + +unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) +{ + return 1; +} + +void Machine::setup_output(float aspect_ratio) +{ + // TODO: this is a copy and paste from the Electron; correct. + + _crt.reset(new Outputs::CRT::CRT(256, 8, Outputs::CRT::DisplayType::PAL50, 1)); + _crt->set_rgb_sampling_function( + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "uint texValue = texture(sampler, coordinate).r;" + "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" + "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" + "}"); +} + +void Machine::close_output() +{ +} diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp new file mode 100644 index 000000000..10ecfddff --- /dev/null +++ b/Machines/Oric/Oric.hpp @@ -0,0 +1,52 @@ +// +// Oric.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Oric_hpp +#define Oric_hpp + +#include "../../Processors/6502/CPU6502.hpp" +#include "../../Storage/Tape/Tape.hpp" + +#include "../ConfigurationTarget.hpp" +#include "../CRTMachine.hpp" +#include "../Typer.hpp" + +#include +#include + +namespace Oric { + +class Machine: + public CPU6502::Processor, + public CRTMachine::Machine, + public ConfigurationTarget::Machine { + + public: + Machine(); + + // to satisfy ConfigurationTarget::Machine + void configure_as_target(const StaticAnalyser::Target &target); + + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); +// void synchronise(); + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual std::shared_ptr get_crt() { return _crt; } + virtual std::shared_ptr get_speaker() { return nullptr; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + + private: + // Outputs + std::shared_ptr _crt; +}; + +} +#endif /* Oric_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 29149a3e4..a3c40011a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -357,6 +357,9 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */; }; + 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; + 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */; }; + 4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; }; 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; }; 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; @@ -823,6 +826,12 @@ 4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = ""; }; 4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = ""; }; 4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = ""; }; + 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = ""; }; + 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = ""; }; + 4BCF1FA61DADC5250039D2E7 /* CSOric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOric.h; sourceTree = ""; }; + 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSOric.mm; sourceTree = ""; }; + 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.cpp; sourceTree = ""; }; + 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = ""; }; 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = ""; }; @@ -972,6 +981,8 @@ 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, 4B2A539B1D117D36003C6002 /* CSElectron.h */, 4B2A539C1D117D36003C6002 /* CSElectron.mm */, + 4BCF1FA61DADC5250039D2E7 /* CSOric.h */, + 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */, 4B2A539D1D117D36003C6002 /* CSVic20.h */, 4B2A539E1D117D36003C6002 /* CSVic20.mm */, ); @@ -1575,6 +1586,7 @@ 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4BCF1FA51DADC3E10039D2E7 /* Oric */, ); name = Machines; path = ../../Machines; @@ -1674,6 +1686,24 @@ name = Encodings; sourceTree = ""; }; + 4BCF1FA51DADC3E10039D2E7 /* Oric */ = { + isa = PBXGroup; + children = ( + 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */, + 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */, + ); + name = Oric; + sourceTree = ""; + }; + 4BCF1FAC1DADD41F0039D2E7 /* Oric */ = { + isa = PBXGroup; + children = ( + 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */, + 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */, + ); + name = Oric; + sourceTree = ""; + }; 4BD14B121D7462810088EAD6 /* Acorn */ = { isa = PBXGroup; children = ( @@ -1746,6 +1776,7 @@ 4BD14B121D7462810088EAD6 /* Acorn */, 4BA799961D8B65730045123D /* Atari */, 4BC830D21D6E7C6D0000A26F /* Commodore */, + 4BCF1FAC1DADD41F0039D2E7 /* Oric */, ); name = StaticAnalyser; sourceTree = ""; @@ -2170,6 +2201,7 @@ 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, + 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, @@ -2198,6 +2230,7 @@ 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, + 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */, 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, @@ -2228,6 +2261,7 @@ 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, + 4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */, 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 61839333c..cd7a1da3a 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -8,6 +8,7 @@ #import "CSAtari2600.h" #import "CSElectron.h" +#import "CSOric.h" #import "CSVic20.h" #import "CSStaticAnalyser.h" diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index beb380dea..aa538059b 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -10,10 +10,17 @@ #import "CSMachine.h" #import "CSMachine+Target.h" -#import "Clock_Signal-Swift.h" -#include "StaticAnalyser.hpp" #import "CSMachine+Subclassing.h" +#include "StaticAnalyser.hpp" + +#import "CSAtari2600.h" +#import "CSElectron.h" +#import "CSOric.h" +#import "CSVic20.h" + +#import "Clock_Signal-Swift.h" + @implementation CSStaticAnalyser { StaticAnalyser::Target _target; @@ -52,6 +59,7 @@ case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; + case StaticAnalyser::Target::Oric: return [[CSOric alloc] init]; default: return nil; } } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index 93a398b33..493b4836c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -10,7 +10,6 @@ #include "Electron.hpp" #include "StaticAnalyser.hpp" -#include "TapeUEF.hpp" #import "CSMachine+Subclassing.h" #import "NSData+StdVector.h" diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h new file mode 100644 index 000000000..a88e872c7 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h @@ -0,0 +1,13 @@ +// +// CSOric.h +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" + +@interface CSOric : CSMachine + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm new file mode 100644 index 000000000..3e8105847 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -0,0 +1,26 @@ +// +// CSOric.m +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSOric.h" + +#include "Oric.hpp" +#include "StaticAnalyser.hpp" + +#import "CSMachine+Subclassing.h" +#import "NSData+StdVector.h" +#import "NSBundle+DataResource.h" + +@implementation CSOric { + Oric::Machine _oric; +} + +- (CRTMachine::Machine * const)machine { + return &_oric; +} + +@end diff --git a/StaticAnalyser/Oric/StaticAnalyser.cpp b/StaticAnalyser/Oric/StaticAnalyser.cpp new file mode 100644 index 000000000..e99798690 --- /dev/null +++ b/StaticAnalyser/Oric/StaticAnalyser.cpp @@ -0,0 +1,28 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +using namespace StaticAnalyser::Oric; + +void StaticAnalyser::Oric::AddTargets( + const std::list> &disks, + const std::list> &tapes, + const std::list> &cartridges, + std::list &destination) +{ + // TODO: any sort of sanity checking at all; at the minute just trust the file type + // approximation already performed. + Target target; + target.machine = Target::Oric; + target.probability = 1.0; + target.disks = disks; + target.tapes = tapes; + target.cartridges = cartridges; + destination.push_back(target); +} diff --git a/StaticAnalyser/Oric/StaticAnalyser.hpp b/StaticAnalyser/Oric/StaticAnalyser.hpp new file mode 100644 index 000000000..275278089 --- /dev/null +++ b/StaticAnalyser/Oric/StaticAnalyser.hpp @@ -0,0 +1,28 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp +#define StaticAnalyser_Oric_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" + +namespace StaticAnalyser { +namespace Oric { + +void AddTargets( + const std::list> &disks, + const std::list> &tapes, + const std::list> &cartridges, + std::list &destination +); + +} +} + + +#endif /* StaticAnalyser_hpp */ diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 9abbe86b1..aaabb819e 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -14,6 +14,7 @@ #include "Acorn/StaticAnalyser.hpp" #include "Atari/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" +#include "Oric/StaticAnalyser.hpp" // Cartridges #include "../Storage/Cartridge/Formats/BinaryDump.hpp" @@ -116,8 +117,9 @@ std::list StaticAnalyser::GetTargets(const char *file_name) // Hand off to platform-specific determination of whether these things are actually compatible and, // if so, how to load them. (TODO) if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets); - if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets); free(lowercase_extension); return targets; From f7d2e988b6d6ae20c1b1e193d10fbf30c68806df Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 22:22:53 -0400 Subject: [PATCH 06/60] Mildly enhanced unit test, while I'm curious. --- OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift index 7fd637992..f07de2fa4 100644 --- a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift +++ b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift @@ -42,7 +42,9 @@ class MOS6502InterruptTests: XCTestCase { XCTAssert(machine.value(for: .programCounter) == 0x4004, "No interrupt should have occurred from interrupt raised between instructions") // run for a further 7 cycles, confirm that the IRQ vector was jumped to - machine.runForNumber(ofCycles: 7) + machine.runForNumber(ofCycles: 6) + XCTAssert(machine.value(for: .programCounter) != 0x1234, "Interrupt routine should not yet have begun") + machine.runForNumber(ofCycles: 1) XCTAssert(machine.value(for: .programCounter) == 0x1234, "Interrupt routine should just have begun") } From e6937d8003ec29e2c40eabd3b915feca84ba2ce0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 18:51:02 -0400 Subject: [PATCH 07/60] Ensured that the ROM gets installed. So next for some video action? --- Machines/Oric/Oric.cpp | 17 +++++++++++++++++ Machines/Oric/Oric.hpp | 5 +++++ .../Mac/Clock Signal/Machine/Wrappers/CSOric.mm | 15 +++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 7038c66b1..54f8cf6f1 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -19,8 +19,25 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) { } +void Machine::set_rom(std::vector data) +{ + memcpy(_rom, data.data(), std::min(data.size(), sizeof(_rom))); +} + unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { + if(address > 0xc000) + { + if(isReadOperation(operation)) *value = _rom[address&16383]; + } + else + { + if(isReadOperation(operation)) + *value = _ram[address]; + else + _ram[address] = *value; + } + return 1; } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 10ecfddff..c0ca4e757 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -29,6 +29,8 @@ class Machine: public: Machine(); + void set_rom(std::vector data); + // to satisfy ConfigurationTarget::Machine void configure_as_target(const StaticAnalyser::Target &target); @@ -44,6 +46,9 @@ class Machine: virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } private: + // RAM and ROM + uint8_t _ram[65536], _rom[16384]; + // Outputs std::shared_ptr _crt; }; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index 3e8105847..6ce2be0de 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -19,6 +19,21 @@ Oric::Machine _oric; } +- (instancetype)init { + self = [super init]; + if(self) + { + NSData *rom = [self rom:@"basic10"]; + if(rom) _oric.set_rom(rom.stdVector8); + } + return self; +} + +- (NSData *)rom:(NSString *)name +{ + return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; +} + - (CRTMachine::Machine * const)machine { return &_oric; } From 8c8a71107e20f8b3df1652c7d0f683491e2f05ae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 19:20:23 -0400 Subject: [PATCH 08/60] Added just enough wiring to add something that will generate the video, one day. --- Machines/Oric/Oric.cpp | 15 +++++++++- Machines/Oric/Oric.hpp | 7 ++++- Machines/Oric/Video.cpp | 23 +++++++++++++++ Machines/Oric/Video.hpp | 28 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 6 ++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 Machines/Oric/Video.cpp create mode 100644 Machines/Oric/Video.hpp diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 54f8cf6f1..c3205580d 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -10,7 +10,7 @@ using namespace Oric; -Machine::Machine() +Machine::Machine() : _cycles_since_video_update(0) { set_clock_rate(1000000); } @@ -35,12 +35,22 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(isReadOperation(operation)) *value = _ram[address]; else + { + if(address >= 0xa000) update_video(); _ram[address] = *value; + } } + _cycles_since_video_update++; return 1; } +void Machine::update_video() +{ + _videoOutput->run_for_cycles(_cycles_since_video_update); + _cycles_since_video_update = 0; +} + void Machine::setup_output(float aspect_ratio) { // TODO: this is a copy and paste from the Electron; correct. @@ -53,6 +63,9 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); + + _videoOutput.reset(new VideoOutput(_ram)); + _videoOutput->set_crt(_crt); } void Machine::close_output() diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index c0ca4e757..ccf2ffca1 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -16,6 +16,8 @@ #include "../CRTMachine.hpp" #include "../Typer.hpp" +#include "Video.hpp" + #include #include @@ -36,7 +38,7 @@ class Machine: // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); -// void synchronise(); + void synchronise() { update_video(); } // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); @@ -48,9 +50,12 @@ class Machine: private: // RAM and ROM uint8_t _ram[65536], _rom[16384]; + int _cycles_since_video_update; + inline void update_video(); // Outputs std::shared_ptr _crt; + std::unique_ptr _videoOutput; }; } diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp new file mode 100644 index 000000000..3c106a9f0 --- /dev/null +++ b/Machines/Oric/Video.cpp @@ -0,0 +1,23 @@ +// +// Video.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Video.hpp" + +using namespace Oric; + +VideoOutput::VideoOutput(uint8_t *memory) : _ram(memory) +{ +} + +void VideoOutput::set_crt(std::shared_ptr crt) +{ +} + +void VideoOutput::run_for_cycles(int number_of_cycles) +{ +} diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp new file mode 100644 index 000000000..cf5a49564 --- /dev/null +++ b/Machines/Oric/Video.hpp @@ -0,0 +1,28 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Video_hpp +#define Video_hpp + +#include "../../Outputs/CRT/CRT.hpp" + +namespace Oric { + +class VideoOutput { + public: + VideoOutput(uint8_t *memory); + void set_crt(std::shared_ptr crt); + void run_for_cycles(int number_of_cycles); + + private: + uint8_t *_ram; +}; + +} + +#endif /* Video_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a3c40011a..ecd5e255f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; + 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; }; @@ -432,6 +433,8 @@ 4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; + 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = ""; }; + 4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; @@ -1691,6 +1694,8 @@ children = ( 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */, 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */, + 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */, + 4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */, ); name = Oric; sourceTree = ""; @@ -2205,6 +2210,7 @@ 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, + 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, From 304f0999cdfffad56566a3abb648820a5c5b8a03 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 21:29:21 -0400 Subject: [PATCH 09/60] Made a faulty but hopefully creditable attempt to demarcate an Oric frame. --- Machines/Oric/Oric.cpp | 18 +++++---------- Machines/Oric/Oric.hpp | 5 ++-- Machines/Oric/Video.cpp | 51 +++++++++++++++++++++++++++++++++++++++-- Machines/Oric/Video.hpp | 16 ++++++++++++- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index c3205580d..9a1ac6494 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -45,6 +45,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin return 1; } +void Machine::synchronise() +{ + update_video(); +} + void Machine::update_video() { _videoOutput->run_for_cycles(_cycles_since_video_update); @@ -53,21 +58,10 @@ void Machine::update_video() void Machine::setup_output(float aspect_ratio) { - // TODO: this is a copy and paste from the Electron; correct. - - _crt.reset(new Outputs::CRT::CRT(256, 8, Outputs::CRT::DisplayType::PAL50, 1)); - _crt->set_rgb_sampling_function( - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" - "{" - "uint texValue = texture(sampler, coordinate).r;" - "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" - "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" - "}"); - _videoOutput.reset(new VideoOutput(_ram)); - _videoOutput->set_crt(_crt); } void Machine::close_output() { + _videoOutput.reset(); } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index ccf2ffca1..2fbededf9 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -38,12 +38,12 @@ class Machine: // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void synchronise() { update_video(); } + void synchronise(); // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); virtual void close_output(); - virtual std::shared_ptr get_crt() { return _crt; } + virtual std::shared_ptr get_crt() { return _videoOutput->get_crt(); } virtual std::shared_ptr get_speaker() { return nullptr; } virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } @@ -54,7 +54,6 @@ class Machine: inline void update_video(); // Outputs - std::shared_ptr _crt; std::unique_ptr _videoOutput; }; diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 3c106a9f0..7ddd97686 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -10,14 +10,61 @@ using namespace Oric; -VideoOutput::VideoOutput(uint8_t *memory) : _ram(memory) +VideoOutput::VideoOutput(uint8_t *memory) : + _ram(memory), + _frame_counter(0), _counter(0), + _state(Sync), _cycles_in_state(0) { + _crt.reset(new Outputs::CRT::CRT(384, 6, Outputs::CRT::DisplayType::PAL50, 1)); + + // TODO: this is a copy and paste from the Electron; factor out. + _crt->set_rgb_sampling_function( + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "uint texValue = texture(sampler, coordinate).r;" + "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" + "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" + "}"); } -void VideoOutput::set_crt(std::shared_ptr crt) +std::shared_ptr VideoOutput::get_crt() { + return _crt; } void VideoOutput::run_for_cycles(int number_of_cycles) { + // Vertical: 0–39: pixels; otherwise blank; 48–53 sync + // Horizontal: 0–223: pixels; otherwise blank; 256–259 sync + + while(number_of_cycles--) + { + _counter = (_counter + 1)%(312 * 64); // TODO: NTSC + + State new_state = Blank; + if((_counter & 63) >= 48 && (_counter & 63) <= 53) new_state = Sync; + else if(_counter >= 256*312 && _counter <= 259*312) new_state = Sync; + else if(_counter < 224 && ((_counter&63) < 40)) new_state = Pixels; + + if(_state != new_state) + { + switch(_state) + { + case Sync: _crt->output_sync(_cycles_in_state * 6); break; + case Blank: _crt->output_blank(_cycles_in_state * 6); break; + case Pixels: _crt->output_data(_cycles_in_state * 6, 2); break; + } + _state = new_state; + _cycles_in_state = 0; + if(_state == Pixels) _pixel_target = _crt->allocate_write_area(120); + } + else _cycles_in_state++; + + if(new_state == Pixels) { + _pixel_target[0] = 0x70; + _pixel_target[1] = 0x14; + _pixel_target[2] = 0x23; + _pixel_target += 3; + } + } } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index cf5a49564..a16ed54bc 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -16,11 +16,25 @@ namespace Oric { class VideoOutput { public: VideoOutput(uint8_t *memory); - void set_crt(std::shared_ptr crt); + std::shared_ptr get_crt(); void run_for_cycles(int number_of_cycles); private: uint8_t *_ram; + std::shared_ptr _crt; + + // Counters + int _counter, _frame_counter; + + // Output state + enum State { + Blank, Sync, Pixels + } _state; + unsigned int _cycles_in_state; + uint8_t *_pixel_target; + + // Registers + uint8_t _ink, _style, _paper; }; } From 1f857c619b1e288c6f7aa10c4bf9e06a07c67335 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 21:42:36 -0400 Subject: [PATCH 10/60] Fixed timing issues. Static box of frame achieved! --- Machines/Oric/Video.cpp | 12 +++++++----- Outputs/CRT/CRT.hpp | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 7ddd97686..146d96f89 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -15,7 +15,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : _frame_counter(0), _counter(0), _state(Sync), _cycles_in_state(0) { - _crt.reset(new Outputs::CRT::CRT(384, 6, Outputs::CRT::DisplayType::PAL50, 1)); + _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); // TODO: this is a copy and paste from the Electron; factor out. _crt->set_rgb_sampling_function( @@ -42,9 +42,11 @@ void VideoOutput::run_for_cycles(int number_of_cycles) _counter = (_counter + 1)%(312 * 64); // TODO: NTSC State new_state = Blank; - if((_counter & 63) >= 48 && (_counter & 63) <= 53) new_state = Sync; - else if(_counter >= 256*312 && _counter <= 259*312) new_state = Sync; - else if(_counter < 224 && ((_counter&63) < 40)) new_state = Pixels; + int h_counter =_counter & 63; + if( + (h_counter >= 48 && h_counter <= 53) || + (_counter >= 256*64 && _counter <= 259*64)) new_state = Sync; + else if(_counter < 224*64 && h_counter < 40) new_state = Pixels; if(_state != new_state) { @@ -58,7 +60,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) _cycles_in_state = 0; if(_state == Pixels) _pixel_target = _crt->allocate_write_area(120); } - else _cycles_in_state++; + _cycles_in_state++; if(new_state == Pixels) { _pixel_target[0] = 0x70; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index a38e63795..59d4ca962 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -103,7 +103,7 @@ class CRT { machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve internal precision. - @param height_of_dispaly The number of lines that nominally form one field of the display, rounded + @param height_of_display The number of lines that nominally form one field of the display, rounded up to the next whole integer. @param colour_cycle_numerator Specifies the numerator for the per-line frequency of the colour subcarrier. From 120b2d9e336b8b5e16af953fb2329c6775ea8abe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 21:52:47 -0400 Subject: [PATCH 11/60] Switched to using a diagnostic ROM for now, as it'll definitely boot without initially requiring either a 6522 or AY. Have some forms appearing which imply I'm not doing badly, at least up to not knowing where I'm supposed to get character pixels from. --- Machines/Oric/Video.cpp | 20 +++++++++++++++---- Machines/Oric/Video.hpp | 1 + .../Clock Signal/Machine/Wrappers/CSOric.mm | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 146d96f89..3a2a0d4b7 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -13,7 +13,8 @@ using namespace Oric; VideoOutput::VideoOutput(uint8_t *memory) : _ram(memory), _frame_counter(0), _counter(0), - _state(Sync), _cycles_in_state(0) + _state(Sync), _cycles_in_state(0), + _is_graphics_mode(false) { _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); @@ -63,9 +64,20 @@ void VideoOutput::run_for_cycles(int number_of_cycles) _cycles_in_state++; if(new_state == Pixels) { - _pixel_target[0] = 0x70; - _pixel_target[1] = 0x14; - _pixel_target[2] = 0x23; + uint8_t pixels; + if(_is_graphics_mode) + { + // TODO + } + else + { + int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; + uint8_t character = _ram[address]; + pixels = character;//_ram[character * 8 + ((_counter >> 6) & 7)]; + } + _pixel_target[0] = ((pixels & 0x01) ? 0xf : 0x0) | ((pixels & 0x02) ? 0xf0 : 0x00); + _pixel_target[1] = ((pixels & 0x04) ? 0xf : 0x0) | ((pixels & 0x08) ? 0xf0 : 0x00); + _pixel_target[2] = ((pixels & 0x10) ? 0xf : 0x0) | ((pixels & 0x20) ? 0xf0 : 0x00); _pixel_target += 3; } } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index a16ed54bc..f7b8b2925 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -35,6 +35,7 @@ class VideoOutput { // Registers uint8_t _ink, _style, _paper; + bool _is_graphics_mode; }; } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index 6ce2be0de..ecdbd4d2b 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -23,7 +23,7 @@ self = [super init]; if(self) { - NSData *rom = [self rom:@"basic10"]; + NSData *rom = [self rom:@"test108j"]; if(rom) _oric.set_rom(rom.stdVector8); } return self; From ae48ad1bb4fd38535d3653cfc3a52ab2efc231a9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 12 Oct 2016 22:03:54 -0400 Subject: [PATCH 12/60] Meaningful text! Albeit with the initial 'O' of 'Oric' missing. I guess control codes and NMIs next? --- Machines/Oric/Video.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 3a2a0d4b7..e0a647d98 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -73,11 +73,11 @@ void VideoOutput::run_for_cycles(int number_of_cycles) { int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; uint8_t character = _ram[address]; - pixels = character;//_ram[character * 8 + ((_counter >> 6) & 7)]; + pixels = _ram[0xb400 + character * 8 + ((_counter >> 6) & 7)]; } - _pixel_target[0] = ((pixels & 0x01) ? 0xf : 0x0) | ((pixels & 0x02) ? 0xf0 : 0x00); + _pixel_target[0] = ((pixels & 0x10) ? 0xf : 0x0) | ((pixels & 0x20) ? 0xf0 : 0x00); _pixel_target[1] = ((pixels & 0x04) ? 0xf : 0x0) | ((pixels & 0x08) ? 0xf0 : 0x00); - _pixel_target[2] = ((pixels & 0x10) ? 0xf : 0x0) | ((pixels & 0x20) ? 0xf0 : 0x00); + _pixel_target[2] = ((pixels & 0x01) ? 0xf : 0x0) | ((pixels & 0x02) ? 0xf0 : 0x00); _pixel_target += 3; } } From 69920a49790239e72064a7462504269da73bb186 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 13 Oct 2016 07:59:11 -0400 Subject: [PATCH 13/60] Made some basic first attempt at parsing video attributes; ensured lowest byte of ROM is readable. --- Machines/Oric/Oric.cpp | 2 +- Machines/Oric/Video.cpp | 60 ++++++++++++++++++++++++++++++++++------- Machines/Oric/Video.hpp | 1 + 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 9a1ac6494..7265828dc 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -26,7 +26,7 @@ void Machine::set_rom(std::vector data) unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { - if(address > 0xc000) + if(address >= 0xc000) { if(isReadOperation(operation)) *value = _rom[address&16383]; } diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index e0a647d98..aa724e503 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -14,7 +14,8 @@ VideoOutput::VideoOutput(uint8_t *memory) : _ram(memory), _frame_counter(0), _counter(0), _state(Sync), _cycles_in_state(0), - _is_graphics_mode(false) + _is_graphics_mode(false), + _character_set_base_address(0xb400) { _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); @@ -41,9 +42,16 @@ void VideoOutput::run_for_cycles(int number_of_cycles) while(number_of_cycles--) { _counter = (_counter + 1)%(312 * 64); // TODO: NTSC + int h_counter =_counter & 63; + + if(!h_counter) + { + _ink = 0xff; + _paper = 0x00; + _style = 0x00; // TODO: this means standard-size standard charset? + } State new_state = Blank; - int h_counter =_counter & 63; if( (h_counter >= 48 && h_counter <= 53) || (_counter >= 256*64 && _counter <= 259*64)) new_state = Sync; @@ -64,20 +72,54 @@ void VideoOutput::run_for_cycles(int number_of_cycles) _cycles_in_state++; if(new_state == Pixels) { - uint8_t pixels; + uint8_t pixels, control_byte; + if(_is_graphics_mode) { - // TODO + control_byte = pixels = _ram[0xa000 + (_counter >> 6) * 40 + h_counter]; } else { int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; - uint8_t character = _ram[address]; - pixels = _ram[0xb400 + character * 8 + ((_counter >> 6) & 7)]; + control_byte = _ram[address]; + pixels = _ram[_character_set_base_address + control_byte * 8 + ((_counter >> 6) & 7)]; + } + + if((control_byte & 0x7f) >= 32) + { + uint8_t colours[2] = { + (control_byte & 0x80) ? _ink : _paper, + (control_byte & 0x80) ? _paper : _ink, + }; + + _pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0); + _pixel_target[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0); + _pixel_target[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0); + } + else + { + switch(control_byte & 0x7f) + { + case 0x00: _ink = 0x00; break; + case 0x01: _ink = 0x44; break; + case 0x02: _ink = 0x22; break; + case 0x03: _ink = 0x66; break; + case 0x04: _ink = 0x11; break; + case 0x05: _ink = 0x55; break; + case 0x06: _ink = 0x33; break; + case 0x07: _ink = 0x77; break; + case 0x10: _paper = 0x00; break; + case 0x11: _paper = 0x44; break; + case 0x12: _paper = 0x22; break; + case 0x13: _paper = 0x66; break; + case 0x14: _paper = 0x11; break; + case 0x15: _paper = 0x55; break; + case 0x16: _paper = 0x33; break; + case 0x17: _paper = 0x77; break; + default: break; + } + _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = _paper; } - _pixel_target[0] = ((pixels & 0x10) ? 0xf : 0x0) | ((pixels & 0x20) ? 0xf0 : 0x00); - _pixel_target[1] = ((pixels & 0x04) ? 0xf : 0x0) | ((pixels & 0x08) ? 0xf0 : 0x00); - _pixel_target[2] = ((pixels & 0x01) ? 0xf : 0x0) | ((pixels & 0x02) ? 0xf0 : 0x00); _pixel_target += 3; } } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index f7b8b2925..300ded3f0 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -35,6 +35,7 @@ class VideoOutput { // Registers uint8_t _ink, _style, _paper; + int _character_set_base_address; bool _is_graphics_mode; }; From 0ca383ecd16a8d849ad710e3fe6ef542c473838d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 13 Oct 2016 18:56:55 -0400 Subject: [PATCH 14/60] Set every single key to be NMI, in order to be able to progress with the diagnostics cartridge. --- .../Clock Signal/Machine/Wrappers/CSOric.h | 3 ++- .../Clock Signal/Machine/Wrappers/CSOric.mm | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h index a88e872c7..285ac9d04 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h @@ -7,7 +7,8 @@ // #import "CSMachine.h" +#import "CSKeyboardMachine.h" -@interface CSOric : CSMachine +@interface CSOric : CSMachine @end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index ecdbd4d2b..a86b90b8f 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -15,11 +15,13 @@ #import "NSData+StdVector.h" #import "NSBundle+DataResource.h" -@implementation CSOric { +@implementation CSOric +{ Oric::Machine _oric; } -- (instancetype)init { +- (instancetype)init +{ self = [super init]; if(self) { @@ -34,8 +36,20 @@ return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; } -- (CRTMachine::Machine * const)machine { +- (CRTMachine::Machine * const)machine +{ return &_oric; } +#pragma mark - CSKeyboardMachine + +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed +{ + _oric.set_nmi_line(isPressed); +} + +- (void)clearAllKeys +{ +} + @end From c9962f650210357755123a743618704e09f0522d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 13 Oct 2016 19:34:29 -0400 Subject: [PATCH 15/60] Made an almost complete implementation of video. Just one row of the diagnostics cartridge seems to be off, showing thw wrong character set. --- Machines/Oric/Oric.cpp | 2 +- Machines/Oric/Video.cpp | 35 +++++++++++++++++++++++++++++------ Machines/Oric/Video.hpp | 13 ++++++++++++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 7265828dc..e80d3a4d8 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -36,7 +36,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin *value = _ram[address]; else { - if(address >= 0xa000) update_video(); + if(address >= 0x9800) update_video(); _ram[address] = *value; } } diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index aa724e503..6f5c7f43a 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -48,7 +48,10 @@ void VideoOutput::run_for_cycles(int number_of_cycles) { _ink = 0xff; _paper = 0x00; - _style = 0x00; // TODO: this means standard-size standard charset? + _use_alternative_character_set = _use_double_height_characters = _blink_text = false; + set_character_set_base_address(); + + if(!_counter) _frame_counter++; } State new_state = Blank; @@ -74,7 +77,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) if(new_state == Pixels) { uint8_t pixels, control_byte; - if(_is_graphics_mode) + if(_is_graphics_mode && _counter < 200*64) { control_byte = pixels = _ram[0xa000 + (_counter >> 6) * 40 + h_counter]; } @@ -82,14 +85,18 @@ void VideoOutput::run_for_cycles(int number_of_cycles) { int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; control_byte = _ram[address]; - pixels = _ram[_character_set_base_address + control_byte * 8 + ((_counter >> 6) & 7)]; + int line = _use_double_height_characters ? ((_counter >> 7) & 7) : ((_counter >> 6) & 7); + pixels = _ram[_character_set_base_address + control_byte * 8 + line]; } + uint8_t inverse_mask = (control_byte & 0x80) ? 0x77 : 0x00; + if(_blink_text) inverse_mask ^= (_frame_counter&32) ? 0x77 : 0x00; + if((control_byte & 0x7f) >= 32) { uint8_t colours[2] = { - (control_byte & 0x80) ? _ink : _paper, - (control_byte & 0x80) ? _paper : _ink, + (uint8_t)(_paper ^ inverse_mask), + (uint8_t)(_ink ^ inverse_mask), }; _pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0); @@ -108,6 +115,15 @@ void VideoOutput::run_for_cycles(int number_of_cycles) case 0x05: _ink = 0x55; break; case 0x06: _ink = 0x33; break; case 0x07: _ink = 0x77; break; + + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + _use_alternative_character_set = (control_byte&1); + _use_double_height_characters = (control_byte&2); + _blink_text = (control_byte&4); + set_character_set_base_address(); + break; + case 0x10: _paper = 0x00; break; case 0x11: _paper = 0x44; break; case 0x12: _paper = 0x22; break; @@ -116,9 +132,16 @@ void VideoOutput::run_for_cycles(int number_of_cycles) case 0x15: _paper = 0x55; break; case 0x16: _paper = 0x33; break; case 0x17: _paper = 0x77; break; + + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + _is_graphics_mode = (control_byte & 4); + _is_sixty_hertz = !(control_byte & 2); + break; + default: break; } - _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = _paper; + _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = (uint8_t)(_paper ^ inverse_mask); } _pixel_target += 3; } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index 300ded3f0..d0e313825 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -34,9 +34,20 @@ class VideoOutput { uint8_t *_pixel_target; // Registers - uint8_t _ink, _style, _paper; + uint8_t _ink, _paper; + int _character_set_base_address; + inline void set_character_set_base_address() + { + if(_is_graphics_mode) _character_set_base_address = _use_alternative_character_set ? 0x9c00 : 0x9800; + else _character_set_base_address = _use_alternative_character_set ? 0xb800 : 0xb400; + } + bool _is_graphics_mode; + bool _is_sixty_hertz; + bool _use_alternative_character_set; + bool _use_double_height_characters; + bool _blink_text; }; } From 41e7eff6c8b494c26315f35d1d27f0eeeef64f10 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 13 Oct 2016 20:50:55 -0400 Subject: [PATCH 16/60] Added a VIA. Now it's time to find out how poor my 6522 emulation is. --- Machines/Oric/Oric.cpp | 23 +++++++++++++++++++---- Machines/Oric/Oric.hpp | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index e80d3a4d8..91a5879a8 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -13,6 +13,7 @@ using namespace Oric; Machine::Machine() : _cycles_since_video_update(0) { set_clock_rate(1000000); + _via.set_interrupt_delegate(this); } void Machine::configure_as_target(const StaticAnalyser::Target &target) @@ -32,15 +33,24 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(isReadOperation(operation)) - *value = _ram[address]; + if((address & 0xff00) == 0x0300) + { + if(isReadOperation(operation)) *value = _via.get_register(address); + else _via.set_register(address, *value); + } else { - if(address >= 0x9800) update_video(); - _ram[address] = *value; + if(isReadOperation(operation)) + *value = _ram[address]; + else + { + if(address >= 0x9800) update_video(); + _ram[address] = *value; + } } } + _via.run_for_half_cycles(2); _cycles_since_video_update++; return 1; } @@ -65,3 +75,8 @@ void Machine::close_output() { _videoOutput.reset(); } + +void Machine::mos6522_did_change_interrupt_status(void *mos6522) +{ + set_irq_line(_via.get_interrupt_line()); +} diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 2fbededf9..93698d3fa 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -10,6 +10,7 @@ #define Oric_hpp #include "../../Processors/6502/CPU6502.hpp" +#include "../../Components/6522/6522.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../ConfigurationTarget.hpp" @@ -23,10 +24,16 @@ namespace Oric { +class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + public: + using MOS6522IRQDelegate::set_interrupt_status; +}; + class Machine: public CPU6502::Processor, public CRTMachine::Machine, - public ConfigurationTarget::Machine { + public ConfigurationTarget::Machine, + public MOS::MOS6522IRQDelegate::Delegate { public: Machine(); @@ -47,6 +54,9 @@ class Machine: virtual std::shared_ptr get_speaker() { return nullptr; } virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + // to satisfy MOS::MOS6522IRQDelegate::Delegate + void mos6522_did_change_interrupt_status(void *mos6522); + private: // RAM and ROM uint8_t _ram[65536], _rom[16384]; @@ -55,6 +65,9 @@ class Machine: // Outputs std::unique_ptr _videoOutput; + + // + VIA _via; }; } From 8867ad647c69603b9cde49287a5a1b7f8ff132bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 13 Oct 2016 21:47:12 -0400 Subject: [PATCH 17/60] Made an attempt at implementing the loopbacks necessary for diagnostics. --- Machines/Oric/Oric.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 93698d3fa..d9b9cdbec 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -27,6 +27,21 @@ namespace Oric { class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: using MOS6522IRQDelegate::set_interrupt_status; + + void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { + port_outputs[port] = value; + if(port) + set_control_line_input(port, Line::One, (value << 1)&value&128); + } + uint8_t get_port_input(Port port) { + if(port) + { + return port_outputs[0]; + } + else + return (uint8_t)((port_outputs[port] >> 4) | (port_outputs[port] << 4)); + } + uint8_t port_outputs[2]; }; class Machine: From d8e4c488c27628055e5912df2a2b6be33f32ffbd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 14 Oct 2016 21:18:03 -0400 Subject: [PATCH 18/60] Started iterating towards having an AY and a fully-working keyboard. --- Components/AY38910/AY38910.cpp | 23 ++++++ Components/AY38910/AY38910.hpp | 35 +++++++++ Machines/Oric/Oric.cpp | 23 ++++++ Machines/Oric/Oric.hpp | 66 ++++++++++++----- .../Clock Signal.xcodeproj/project.pbxproj | 16 +++- .../Clock Signal/Machine/Wrappers/CSOric.mm | 73 ++++++++++++++++++- 6 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 Components/AY38910/AY38910.cpp create mode 100644 Components/AY38910/AY38910.hpp diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp new file mode 100644 index 000000000..0e4ba8684 --- /dev/null +++ b/Components/AY38910/AY38910.cpp @@ -0,0 +1,23 @@ +// +// AY-3-8910.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "AY38910.hpp" + +using namespace GI; + +AY38910::AY38910() +{ +} + +void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) +{ +} + +void AY38910::skip_samples(unsigned int number_of_samples) +{ +} diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp new file mode 100644 index 000000000..217a58607 --- /dev/null +++ b/Components/AY38910/AY38910.hpp @@ -0,0 +1,35 @@ +// +// AY-3-8910.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef AY_3_8910_hpp +#define AY_3_8910_hpp + +#include "../../Outputs/Speaker.hpp" + +namespace GI { + +class AY38910: public ::Outputs::Filter { + public: + AY38910(); + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + void select_register(uint8_t r); + void set_register_value(uint8_t value); + uint8_t get_register_value(); + + private: + int _selected_register; + uint8_t _registers[16]; + +}; + +}; + +#endif /* AY_3_8910_hpp */ diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 91a5879a8..ce77d6ce8 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -14,6 +14,8 @@ Machine::Machine() : _cycles_since_video_update(0) { set_clock_rate(1000000); _via.set_interrupt_delegate(this); + _keyboard.reset(new Keyboard); + clear_all_keys(); } void Machine::configure_as_target(const StaticAnalyser::Target &target) @@ -69,6 +71,7 @@ void Machine::update_video() void Machine::setup_output(float aspect_ratio) { _videoOutput.reset(new VideoOutput(_ram)); + _via.ay8910.reset(new GI::AY38910()); } void Machine::close_output() @@ -80,3 +83,23 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522) { set_irq_line(_via.get_interrupt_line()); } + +void Machine::set_key_state(Key key, bool isPressed) +{ + if(key == KeyNMI) + { + set_nmi_line(isPressed); + } + else + { + if(isPressed) + _keyboard->rows[key >> 8] |= (key & 0xff); + else + _keyboard->rows[key >> 8] &= ~(key & 0xff); + } +} + +void Machine::clear_all_keys() +{ + memset(_keyboard->rows, 0, sizeof(_keyboard->rows)); +} diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index d9b9cdbec..4e80501f2 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -11,6 +11,7 @@ #include "../../Processors/6502/CPU6502.hpp" #include "../../Components/6522/6522.hpp" +#include "../../Components/AY38910/AY38910.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../ConfigurationTarget.hpp" @@ -24,24 +25,25 @@ namespace Oric { -class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { - public: - using MOS6522IRQDelegate::set_interrupt_status; +enum Key: uint16_t { + Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20, + KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01, + KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20, + KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01, + KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10, + Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01, + KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40, + KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01, + KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10, + KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01, + KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10, + KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01, + KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20, + KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01, + KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10, + KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01, - void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { - port_outputs[port] = value; - if(port) - set_control_line_input(port, Line::One, (value << 1)&value&128); - } - uint8_t get_port_input(Port port) { - if(port) - { - return port_outputs[0]; - } - else - return (uint8_t)((port_outputs[port] >> 4) | (port_outputs[port] << 4)); - } - uint8_t port_outputs[2]; + KeyNMI = 0xffff, }; class Machine: @@ -54,6 +56,8 @@ class Machine: Machine(); void set_rom(std::vector data); + void set_key_state(Key key, bool isPressed); + void clear_all_keys(); // to satisfy ConfigurationTarget::Machine void configure_as_target(const StaticAnalyser::Target &target); @@ -82,7 +86,35 @@ class Machine: std::unique_ptr _videoOutput; // + class Keyboard { + public: + uint8_t row, column; + uint8_t rows[8]; + }; + class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + public: + using MOS6522IRQDelegate::set_interrupt_status; + + void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { + port_outputs[port] = value; + if(port) + set_control_line_input(port, Line::One, (value << 1)&value&128); + } + uint8_t get_port_input(Port port) { + if(port) + { + return port_outputs[0]; + } + else + return (uint8_t)((port_outputs[port] >> 4) | (port_outputs[port] << 4)); + } + uint8_t port_outputs[2]; + + std::unique_ptr ay8910; + std::shared_ptr keyboard; + }; VIA _via; + std::shared_ptr _keyboard; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ecd5e255f..95ce34e21 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; }; 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; }; 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; }; + 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; 4B4C83701D4F623200CD541F /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C836E1D4F623200CD541F /* D64.cpp */; }; 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; }; @@ -458,6 +459,8 @@ 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = ""; }; 4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = ""; }; + 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = ""; }; + 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = ""; }; 4B4C836E1D4F623200CD541F /* D64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = D64.cpp; sourceTree = ""; }; 4B4C836F1D4F623200CD541F /* D64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = D64.hpp; sourceTree = ""; }; 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = ""; }; @@ -1050,6 +1053,15 @@ path = Bridges; sourceTree = ""; }; + 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { + isa = PBXGroup; + children = ( + 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */, + 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */, + ); + name = AY38910; + sourceTree = ""; + }; 4B4DC81D1D2C2425003C5BF8 /* Commodore */ = { isa = PBXGroup; children = ( @@ -1654,10 +1666,11 @@ 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( + 4BD468F81D8DF4290084958B /* 1770 */, 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, - 4BD468F81D8DF4290084958B /* 1770 */, + 4B4A762D1DB1A35C007AAE2E /* AY38910 */, ); name = Components; path = ../../Components; @@ -2250,6 +2263,7 @@ 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */, + 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B8FE2201DA19D7C0090D3CE /* Atari2600OptionsPanel.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index a86b90b8f..1c58b76c9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -45,7 +45,78 @@ - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { - _oric.set_nmi_line(isPressed); + @synchronized(self) { + switch(key) + { +/* case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break; + case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break; + case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break; + case VK_ANSI_3: _oric.set_key_state(Oric::Key::Key3, isPressed); break; + case VK_ANSI_4: _oric.set_key_state(Oric::Key::Key4, isPressed); break; + case VK_ANSI_5: _oric.set_key_state(Oric::Key::Key5, isPressed); break; + case VK_ANSI_6: _oric.set_key_state(Oric::Key::Key6, isPressed); break; + case VK_ANSI_7: _oric.set_key_state(Oric::Key::Key7, isPressed); break; + case VK_ANSI_8: _oric.set_key_state(Oric::Key::Key8, isPressed); break; + case VK_ANSI_9: _oric.set_key_state(Oric::Key::Key9, isPressed); break; + + case VK_ANSI_Q: _oric.set_key_state(Oric::Key::KeyQ, isPressed); break; + case VK_ANSI_W: _oric.set_key_state(Oric::Key::KeyW, isPressed); break; + case VK_ANSI_E: _oric.set_key_state(Oric::Key::KeyE, isPressed); break; + case VK_ANSI_R: _oric.set_key_state(Oric::Key::KeyR, isPressed); break; + case VK_ANSI_T: _oric.set_key_state(Oric::Key::KeyT, isPressed); break; + case VK_ANSI_Y: _oric.set_key_state(Oric::Key::KeyY, isPressed); break; + case VK_ANSI_U: _oric.set_key_state(Oric::Key::KeyU, isPressed); break; + case VK_ANSI_I: _oric.set_key_state(Oric::Key::KeyI, isPressed); break; + case VK_ANSI_O: _oric.set_key_state(Oric::Key::KeyO, isPressed); break; + case VK_ANSI_P: _oric.set_key_state(Oric::Key::KeyP, isPressed); break; + case VK_ANSI_A: _oric.set_key_state(Oric::Key::KeyA, isPressed); break; + case VK_ANSI_S: _oric.set_key_state(Oric::Key::KeyS, isPressed); break; + case VK_ANSI_D: _oric.set_key_state(Oric::Key::KeyD, isPressed); break; + case VK_ANSI_F: _oric.set_key_state(Oric::Key::KeyF, isPressed); break; + case VK_ANSI_G: _oric.set_key_state(Oric::Key::KeyG, isPressed); break; + case VK_ANSI_H: _oric.set_key_state(Oric::Key::KeyH, isPressed); break; + case VK_ANSI_J: _oric.set_key_state(Oric::Key::KeyJ, isPressed); break; + case VK_ANSI_K: _oric.set_key_state(Oric::Key::KeyK, isPressed); break; + case VK_ANSI_L: _oric.set_key_state(Oric::Key::KeyL, isPressed); break; + case VK_ANSI_Z: _oric.set_key_state(Oric::Key::KeyZ, isPressed); break; + case VK_ANSI_X: _oric.set_key_state(Oric::Key::KeyX, isPressed); break; + case VK_ANSI_C: _oric.set_key_state(Oric::Key::KeyC, isPressed); break; + case VK_ANSI_V: _oric.set_key_state(Oric::Key::KeyV, isPressed); break; + case VK_ANSI_B: _oric.set_key_state(Oric::Key::KeyB, isPressed); break; + case VK_ANSI_N: _oric.set_key_state(Oric::Key::KeyN, isPressed); break; + case VK_ANSI_M: _oric.set_key_state(Oric::Key::KeyM, isPressed); break; + + case VK_Space: _oric.set_key_state(Oric::Key::KeySpace, isPressed); break; + case VK_Return: _oric.set_key_state(Oric::Key::KeyReturn, isPressed); break; + case VK_ANSI_Minus: _oric.set_key_state(Oric::Key::KeyMinus, isPressed); break; + + case VK_RightArrow: _oric.set_key_state(Oric::Key::KeyRight, isPressed); break; + case VK_LeftArrow: _oric.set_key_state(Oric::Key::KeyLeft, isPressed); break; + case VK_DownArrow: _oric.set_key_state(Oric::Key::KeyDown, isPressed); break; + case VK_UpArrow: _oric.set_key_state(Oric::Key::KeyUp, isPressed); break; + + case VK_Delete: _oric.set_key_state(Oric::Key::KeyDelete, isPressed); break; + case VK_Escape: _oric.set_key_state(Oric::Key::KeyEscape, isPressed); break; + + case VK_ANSI_Comma: _oric.set_key_state(Oric::Key::KeyComma, isPressed); break; + case VK_ANSI_Period: _oric.set_key_state(Oric::Key::KeyFullStop, isPressed); break; + + case VK_ANSI_Semicolon: + _oric.set_key_state(Oric::Key::KeySemiColon, isPressed); break; + + case VK_Shift: _oric.set_key_state(Oric::Key::KeyLeftShift, isPressed); break; + case VK_RightShift: _oric.set_key_state(Oric::Key::KeyRightShift, isPressed); break; + case VK_Control: _oric.set_key_state(Oric::Key::KeyControl, isPressed); break; + case VK_Command:*/ + + case VK_ANSI_Grave: + case VK_F12: _oric.set_key_state(Oric::Key::KeyNMI, isPressed); break; + + default: + printf("%02x\n", key); + break; + } + } } - (void)clearAllKeys From 138eabcff4736da0d9b1149dcea0f928e0b4d25a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 14 Oct 2016 21:35:15 -0400 Subject: [PATCH 19/60] Continued in my effort to wire up a keyboard. Will need further to continue. --- Components/AY38910/AY38910.cpp | 15 ++++++ Machines/Oric/Oric.cpp | 1 + Machines/Oric/Oric.hpp | 47 ++++++++++++++++--- .../Clock Signal/Machine/Wrappers/CSOric.mm | 5 +- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 0e4ba8684..245dfa516 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -21,3 +21,18 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) void AY38910::skip_samples(unsigned int number_of_samples) { } + +void AY38910::select_register(uint8_t r) +{ + printf("sel %d\n", r); +} + +void AY38910::set_register_value(uint8_t value) +{ + printf("val %d\n", value); +} + +uint8_t AY38910::get_register_value() +{ + return 0; +} diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index ce77d6ce8..bfa0bf06a 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -15,6 +15,7 @@ Machine::Machine() : _cycles_since_video_update(0) set_clock_rate(1000000); _via.set_interrupt_delegate(this); _keyboard.reset(new Keyboard); + _via.keyboard = _keyboard; clear_all_keys(); } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 4e80501f2..b8cddaf09 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -95,23 +95,56 @@ class Machine: public: using MOS6522IRQDelegate::set_interrupt_status; - void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { - port_outputs[port] = value; - if(port) - set_control_line_input(port, Line::One, (value << 1)&value&128); + void set_control_line_output(Port port, Line line, bool value) + { + if(line) + { + if(port) _ay_bdir = value; else _ay_bc1 = value; + update_ay(); + } } + + void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { + if(port) + { + keyboard->row = value; + } + else + { + _port_a = value; + update_ay(); + } + } + uint8_t get_port_input(Port port) { if(port) { - return port_outputs[0]; + return (keyboard->rows[keyboard->row & 7] & keyboard->column) ? 0x08 : 0x00; } else - return (uint8_t)((port_outputs[port] >> 4) | (port_outputs[port] << 4)); + { + return _port_a; + } } - uint8_t port_outputs[2]; std::unique_ptr ay8910; std::shared_ptr keyboard; + + private: + void update_ay() + { + if(_ay_bdir) + { + if(_ay_bc1) ay8910->select_register(_port_a); + else ay8910->set_register_value(_port_a); + } + else + { + if(_ay_bc1) _port_a = ay8910->get_register_value(); + } + } + uint8_t _port_a; + bool _ay_bdir, _ay_bc1; }; VIA _via; std::shared_ptr _keyboard; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index 1c58b76c9..21afc3a53 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -25,7 +25,7 @@ self = [super init]; if(self) { - NSData *rom = [self rom:@"test108j"]; + NSData *rom = [self rom:@"basic11"]; // test108j if(rom) _oric.set_rom(rom.stdVector8); } return self; @@ -48,7 +48,7 @@ @synchronized(self) { switch(key) { -/* case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break; + case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break; case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break; case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break; case VK_ANSI_3: _oric.set_key_state(Oric::Key::Key3, isPressed); break; @@ -107,7 +107,6 @@ case VK_Shift: _oric.set_key_state(Oric::Key::KeyLeftShift, isPressed); break; case VK_RightShift: _oric.set_key_state(Oric::Key::KeyRightShift, isPressed); break; case VK_Control: _oric.set_key_state(Oric::Key::KeyControl, isPressed); break; - case VK_Command:*/ case VK_ANSI_Grave: case VK_F12: _oric.set_key_state(Oric::Key::KeyNMI, isPressed); break; From 3dbb6024195f2555ae4b68a4686990806c8932ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 14 Oct 2016 21:36:59 -0400 Subject: [PATCH 20/60] This seems to be a bit more likely. --- Machines/Oric/Oric.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index b8cddaf09..9c988924b 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -111,7 +111,7 @@ class Machine: } else { - _port_a = value; + _port_a_output = value; update_ay(); } } @@ -123,7 +123,7 @@ class Machine: } else { - return _port_a; + return _port_a_input; } } @@ -135,15 +135,15 @@ class Machine: { if(_ay_bdir) { - if(_ay_bc1) ay8910->select_register(_port_a); - else ay8910->set_register_value(_port_a); + if(_ay_bc1) ay8910->select_register(_port_a_output); + else ay8910->set_register_value(_port_a_output); } else { - if(_ay_bc1) _port_a = ay8910->get_register_value(); + if(_ay_bc1) _port_a_input = ay8910->get_register_value(); } } - uint8_t _port_a; + uint8_t _port_a_output, _port_a_input; bool _ay_bdir, _ay_bc1; }; VIA _via; From 288d10c25349f10894d699c8ed36e2ffd1fc1d22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 14 Oct 2016 21:44:15 -0400 Subject: [PATCH 21/60] Got some keyboard reaction. --- Components/AY38910/AY38910.cpp | 13 +++++++++---- Components/AY38910/AY38910.hpp | 2 ++ Machines/Oric/Oric.hpp | 5 +++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 245dfa516..04be00902 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -10,7 +10,7 @@ using namespace GI; -AY38910::AY38910() +AY38910::AY38910() : _selected_register(0) { } @@ -24,15 +24,20 @@ void AY38910::skip_samples(unsigned int number_of_samples) void AY38910::select_register(uint8_t r) { - printf("sel %d\n", r); + _selected_register = r & 0xf; } void AY38910::set_register_value(uint8_t value) { - printf("val %d\n", value); + _registers[_selected_register] = value; } uint8_t AY38910::get_register_value() { - return 0; + return _registers[_selected_register]; +} + +uint8_t AY38910::get_port_output(bool port_b) +{ + return _registers[port_b ? 15 : 14]; } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 217a58607..b71eb3bde 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -24,6 +24,8 @@ class AY38910: public ::Outputs::Filter { void set_register_value(uint8_t value); uint8_t get_register_value(); + uint8_t get_port_output(bool port_b); + private: int _selected_register; uint8_t _registers[16]; diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 9c988924b..af9ce4a47 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -88,7 +88,7 @@ class Machine: // class Keyboard { public: - uint8_t row, column; + uint8_t row; uint8_t rows[8]; }; class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { @@ -119,7 +119,8 @@ class Machine: uint8_t get_port_input(Port port) { if(port) { - return (keyboard->rows[keyboard->row & 7] & keyboard->column) ? 0x08 : 0x00; + uint8_t column = ay8910->get_port_output(false) ^ 0xff; + return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; } else { From da9c9ad51a44aeb37f50f0484e7d0ed7165c8ed9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 14 Oct 2016 22:39:27 -0400 Subject: [PATCH 22/60] Added in the missing keys; added variable phase to the video. --- Machines/Oric/Video.cpp | 14 ++++++++++---- Machines/Oric/Video.hpp | 4 +++- .../Mac/Clock Signal/Machine/Wrappers/CSOric.mm | 10 ++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 6f5c7f43a..6f8ec7388 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -15,7 +15,8 @@ VideoOutput::VideoOutput(uint8_t *memory) : _frame_counter(0), _counter(0), _state(Sync), _cycles_in_state(0), _is_graphics_mode(false), - _character_set_base_address(0xb400) + _character_set_base_address(0xb400), + _phase(0) { _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); @@ -27,6 +28,8 @@ VideoOutput::VideoOutput(uint8_t *memory) : "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); + + _crt->set_output_device(Outputs::CRT::Television); } std::shared_ptr VideoOutput::get_crt() @@ -50,6 +53,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) _paper = 0x00; _use_alternative_character_set = _use_double_height_characters = _blink_text = false; set_character_set_base_address(); + _phase += 64; if(!_counter) _frame_counter++; } @@ -58,15 +62,17 @@ void VideoOutput::run_for_cycles(int number_of_cycles) if( (h_counter >= 48 && h_counter <= 53) || (_counter >= 256*64 && _counter <= 259*64)) new_state = Sync; + else if(h_counter >= 54 && h_counter <= 56) new_state = ColourBurst; else if(_counter < 224*64 && h_counter < 40) new_state = Pixels; if(_state != new_state) { switch(_state) { - case Sync: _crt->output_sync(_cycles_in_state * 6); break; - case Blank: _crt->output_blank(_cycles_in_state * 6); break; - case Pixels: _crt->output_data(_cycles_in_state * 6, 2); break; + case ColourBurst: _crt->output_colour_burst(_cycles_in_state * 6, _phase, 128); break; + case Sync: _crt->output_sync(_cycles_in_state * 6); break; + case Blank: _crt->output_blank(_cycles_in_state * 6); break; + case Pixels: _crt->output_data(_cycles_in_state * 6, 2); break; } _state = new_state; _cycles_in_state = 0; diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index d0e313825..77d31821d 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -28,7 +28,7 @@ class VideoOutput { // Output state enum State { - Blank, Sync, Pixels + Blank, Sync, Pixels, ColourBurst } _state; unsigned int _cycles_in_state; uint8_t *_pixel_target; @@ -48,6 +48,8 @@ class VideoOutput { bool _use_alternative_character_set; bool _use_double_height_characters; bool _blink_text; + + uint8_t _phase; }; } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index 21afc3a53..1a726c4df 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -89,6 +89,16 @@ case VK_Space: _oric.set_key_state(Oric::Key::KeySpace, isPressed); break; case VK_Return: _oric.set_key_state(Oric::Key::KeyReturn, isPressed); break; case VK_ANSI_Minus: _oric.set_key_state(Oric::Key::KeyMinus, isPressed); break; + case VK_ANSI_Equal: _oric.set_key_state(Oric::Key::KeyEquals, isPressed); break; + case VK_ANSI_Backslash: + _oric.set_key_state(Oric::Key::KeyBackSlash, isPressed); break; + case VK_ANSI_Slash: _oric.set_key_state(Oric::Key::KeyForwardSlash, isPressed); break; + + case VK_ANSI_LeftBracket: + _oric.set_key_state(Oric::Key::KeyOpenSquare, isPressed); break; + case VK_ANSI_RightBracket: + _oric.set_key_state(Oric::Key::KeyCloseSquare, isPressed); break; + case VK_ANSI_Quote: _oric.set_key_state(Oric::Key::KeyQuote, isPressed); break; case VK_RightArrow: _oric.set_key_state(Oric::Key::KeyRight, isPressed); break; case VK_LeftArrow: _oric.set_key_state(Oric::Key::KeyLeft, isPressed); break; From 51bdac27ae5162303025e5cc73173d2e4c5c5144 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 17:45:39 -0400 Subject: [PATCH 23/60] Made some AY advances; it's now being polled for samples and collecting more information on what it needs to output. --- Components/AY38910/AY38910.cpp | 37 ++++++++++++++++++++++++++++++++++ Components/AY38910/AY38910.hpp | 5 ++++- Machines/Oric/Oric.cpp | 2 ++ Machines/Oric/Oric.hpp | 14 +++++++++++-- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 04be00902..313a3391c 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -14,8 +14,18 @@ AY38910::AY38910() : _selected_register(0) { } +void AY38910::set_clock_rate(double clock_rate) +{ + set_input_rate((float)clock_rate); +} + void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { + printf("%d\n", number_of_samples); + for(int c = 0; c < number_of_samples; c++) + { + *target++ = (c & 64) * 64; + } } void AY38910::skip_samples(unsigned int number_of_samples) @@ -30,6 +40,33 @@ void AY38910::select_register(uint8_t r) void AY38910::set_register_value(uint8_t value) { _registers[_selected_register] = value; + if(value < 14) + { + int selected_register = _selected_register; + enqueue([=] () { + _output_registers[selected_register] = value; + switch(selected_register) + { + case 0: case 2: case 4: + _tone_generator_controls[selected_register >> 1] = + (_tone_generator_controls[selected_register >> 1] & ~0xff) | value; + break; + + case 1: case 3: case 5: + _tone_generator_controls[selected_register >> 1] = + (_tone_generator_controls[selected_register >> 1] & 0xff) | (uint16_t)((value&0xf) << 8); + break; + + case 11: + _envelope_period = (_envelope_period & ~0xff) | value; + break; + + case 12: + _envelope_period = (_envelope_period & 0xff) | (uint16_t)(value << 8); + break; + } + }); + } } uint8_t AY38910::get_register_value() diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index b71eb3bde..888a3763a 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -16,6 +16,7 @@ namespace GI { class AY38910: public ::Outputs::Filter { public: AY38910(); + void set_clock_rate(double clock_rate); void get_samples(unsigned int number_of_samples, int16_t *target); void skip_samples(unsigned int number_of_samples); @@ -28,8 +29,10 @@ class AY38910: public ::Outputs::Filter { private: int _selected_register; - uint8_t _registers[16]; + uint8_t _registers[16], _output_registers[16]; + uint16_t _tone_generator_controls[3]; + uint16_t _envelope_period; }; }; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index bfa0bf06a..4a5cc855c 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -73,11 +73,13 @@ void Machine::setup_output(float aspect_ratio) { _videoOutput.reset(new VideoOutput(_ram)); _via.ay8910.reset(new GI::AY38910()); + _via.ay8910->set_clock_rate(1000000); } void Machine::close_output() { _videoOutput.reset(); + _via.ay8910.reset(); } void Machine::mos6522_did_change_interrupt_status(void *mos6522) diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index af9ce4a47..c7315fb81 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -22,6 +22,7 @@ #include #include +#include namespace Oric { @@ -70,7 +71,7 @@ class Machine: virtual void setup_output(float aspect_ratio); virtual void close_output(); virtual std::shared_ptr get_crt() { return _videoOutput->get_crt(); } - virtual std::shared_ptr get_speaker() { return nullptr; } + virtual std::shared_ptr get_speaker() { return _via.ay8910; } virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } // to satisfy MOS::MOS6522IRQDelegate::Delegate @@ -128,12 +129,20 @@ class Machine: } } - std::unique_ptr ay8910; + inline void run_for_half_cycles(unsigned int number_of_cycles) + { + _half_cycles_since_ay_update += number_of_cycles; + MOS::MOS6522::run_for_half_cycles(number_of_cycles); + } + + std::shared_ptr ay8910; std::shared_ptr keyboard; private: void update_ay() { + ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); + _half_cycles_since_ay_update &= 1; if(_ay_bdir) { if(_ay_bc1) ay8910->select_register(_port_a_output); @@ -146,6 +155,7 @@ class Machine: } uint8_t _port_a_output, _port_a_input; bool _ay_bdir, _ay_bc1; + unsigned int _half_cycles_since_ay_update; }; VIA _via; std::shared_ptr _keyboard; From 9730e8247f6fa2c70fa8fa74293a56454eab6b3c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:04:21 -0400 Subject: [PATCH 24/60] Ensured propagation of synchronise messages, added enough to do plain tone. Probably. So: noise and envelopes missing. And it's all far too quiet. --- Components/AY38910/AY38910.cpp | 29 ++++++++++++++++++++++++++--- Components/AY38910/AY38910.hpp | 4 ++++ Machines/Oric/Oric.cpp | 1 + Machines/Oric/Oric.hpp | 2 ++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 313a3391c..08a8dacac 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -10,8 +10,9 @@ using namespace GI; -AY38910::AY38910() : _selected_register(0) +AY38910::AY38910() : _selected_register(0), _channel_ouput{0, 0, 0} { + _output_registers[8] = _output_registers[9] = _output_registers[10] = 0; } void AY38910::set_clock_rate(double clock_rate) @@ -19,17 +20,39 @@ void AY38910::set_clock_rate(double clock_rate) set_input_rate((float)clock_rate); } +#define step(c) \ + _channel_dividers[c] -= resulting_steps; \ + if(!_channel_dividers[c]) \ + { \ + _channel_dividers[c] = (int)_tone_generator_controls[c] + 1; \ + _channel_ouput[c] ^= 1; \ + } + void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { - printf("%d\n", number_of_samples); for(int c = 0; c < number_of_samples; c++) { - *target++ = (c & 64) * 64; + // a master divider divides the clock by 16 + int former_master_divider = _master_divider; + _master_divider++; + int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; + + // from that the three channels count down + step(0); + step(1); + step(2); + + *target++ = (int16_t)(( + ((_output_registers[8]&0xf) * _channel_ouput[0]) + + ((_output_registers[9]&0xf) * _channel_ouput[1]) + + ((_output_registers[10]&0xf) * _channel_ouput[2]) + ) * 512); } } void AY38910::skip_samples(unsigned int number_of_samples) { + // TODO } void AY38910::select_register(uint8_t r) diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 888a3763a..ddef6e134 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -33,6 +33,10 @@ class AY38910: public ::Outputs::Filter { uint16_t _tone_generator_controls[3]; uint16_t _envelope_period; + + int _master_divider; + int _channel_dividers[3]; + int _channel_ouput[3]; }; }; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 4a5cc855c..bf172d8ac 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -61,6 +61,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin void Machine::synchronise() { update_video(); + _via.synchronise(); } void Machine::update_video() diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index c7315fb81..132f09d14 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -138,6 +138,8 @@ class Machine: std::shared_ptr ay8910; std::shared_ptr keyboard; + inline void synchronise() { update_ay(); } + private: void update_ay() { From 6d7c3f6ac2b4489a7eef99549631b19ce78c85a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:21:18 -0400 Subject: [PATCH 25/60] Factored out the now-sampling binary-level tape player from the Vic and connected it up to the Oric. --- Machines/Commodore/Vic-20/Vic20.cpp | 45 ++--------------------------- Machines/Commodore/Vic-20/Vic20.hpp | 29 ++----------------- Machines/Oric/Oric.cpp | 8 ++++- Machines/Oric/Oric.hpp | 13 +++++++-- Storage/Tape/Tape.hpp | 30 +++++++++++++++++++ 5 files changed, 54 insertions(+), 71 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 844b85f9c..78a63c13d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -16,7 +16,8 @@ using namespace Commodore::Vic20; Machine::Machine() : _rom(nullptr), - _is_running_at_zero_cost(false) + _is_running_at_zero_cost(false), + _tape(1022727) { // create 6522s, serial port and bus _userPortVIA.reset(new UserPortVIA); @@ -350,36 +351,13 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) } } -//void Machine::set_tape(std::shared_ptr tape) -//{ -// _tape.set_tape(tape); -// if(_should_automatically_load_media) set_typer_for_string("LOAD\nRUN\n"); -//} - -void Machine::tape_did_change_input(Tape *tape) +void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { _keyboardVIA->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input()); } #pragma mark - Disc -/*void Machine::set_disk(std::shared_ptr disk) -{ - // construct the 1540 - _c1540.reset(new ::Commodore::C1540::Machine); - - // attach it to the serial bus - _c1540->set_serial_bus(_serialBus); - - // hand it the disk - _c1540->set_disk(disk); - - // install the ROM if it was previously set - install_disk_rom(); - - if(_should_automatically_load_media) set_typer_for_string("LOAD\"*\",8,1\nRUN\n"); -}*/ - void Machine::install_disk_rom() { if(_driveROM && _c1540) @@ -496,20 +474,3 @@ bool Machine::typer_set_next_character(::Utility::Typer *typer, char character, return true; } -#pragma mark - Tape - -Tape::Tape() : TapePlayer(1022727) {} - -void Tape::set_motor_control(bool enabled) {} -void Tape::set_tape_output(bool set) {} - -void Tape::process_input_pulse(Storage::Tape::PRG::Pulse pulse) -{ - bool new_input_level = pulse.type == Storage::Tape::PRG::Pulse::Low; - if(_input_level != new_input_level) - { - _input_level = new_input_level; - if(_delegate) _delegate->tape_did_change_input(this); - } -} - diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index eb2bcabb5..f652bbdbe 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -214,29 +214,6 @@ class SerialPort : public ::Commodore::Serial::Port { std::weak_ptr _userPortVIA; }; -class Tape: public Storage::Tape::TapePlayer { - public: - Tape(); - - void set_motor_control(bool enabled); - void set_tape_output(bool set); - inline bool get_input() { return _input_level; } - - class Delegate { - public: - virtual void tape_did_change_input(Tape *tape) = 0; - }; - void set_delegate(Delegate *delegate) - { - _delegate = delegate; - } - - private: - Delegate *_delegate; - virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse); - bool _input_level; -}; - class Vic6560: public MOS::MOS6560 { public: inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) @@ -254,7 +231,7 @@ class Machine: public CRTMachine::Machine, public MOS::MOS6522IRQDelegate::Delegate, public Utility::TypeRecipient, - public Tape::Delegate, + public Storage::Tape::BinaryTapePlayer::Delegate, public ConfigurationTarget::Machine { public: @@ -301,7 +278,7 @@ class Machine: virtual bool typer_set_next_character(Utility::Typer *typer, char character, int phase); // for Tape::Delegate - virtual void tape_did_change_input(Tape *tape); + virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape); private: uint8_t _characterROM[0x1000]; @@ -332,7 +309,7 @@ class Machine: // std::shared_ptr<::Commodore::Serial::DebugPort> _debugPort; // Tape - Tape _tape; + Storage::Tape::BinaryTapePlayer _tape; bool _use_fast_tape_hack, _should_automatically_load_media; bool _is_running_at_zero_cost; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index bf172d8ac..9307ec3d3 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -10,7 +10,7 @@ using namespace Oric; -Machine::Machine() : _cycles_since_video_update(0) +Machine::Machine() : _cycles_since_video_update(0), _tape(1000000) { set_clock_rate(1000000); _via.set_interrupt_delegate(this); @@ -107,3 +107,9 @@ void Machine::clear_all_keys() { memset(_keyboard->rows, 0, sizeof(_keyboard->rows)); } + +void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) +{ + // set CB1 + _via.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input()); +} diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 132f09d14..10fae88b7 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -51,7 +51,8 @@ class Machine: public CPU6502::Processor, public CRTMachine::Machine, public ConfigurationTarget::Machine, - public MOS::MOS6522IRQDelegate::Delegate { + public MOS::MOS6522IRQDelegate::Delegate, + public Storage::Tape::BinaryTapePlayer::Delegate { public: Machine(); @@ -77,6 +78,9 @@ class Machine: // to satisfy MOS::MOS6522IRQDelegate::Delegate void mos6522_did_change_interrupt_status(void *mos6522); + // to satisfy Storage::Tape::BinaryTapePlayer::Delegate + void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player); + private: // RAM and ROM uint8_t _ram[65536], _rom[16384]; @@ -86,12 +90,17 @@ class Machine: // Outputs std::unique_ptr _videoOutput; - // + // Keyboard class Keyboard { public: uint8_t row; uint8_t rows[8]; }; + + // Tape player + Storage::Tape::BinaryTapePlayer _tape; + + // VIA class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: using MOS6522IRQDelegate::set_interrupt_status; diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index cbf330e49..dc45e6e9c 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -94,6 +94,36 @@ class TapePlayer: public TimedEventLoop { Tape::Pulse _current_pulse; }; +class BinaryTapePlayer: public TapePlayer { + public: + BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate) {} + void set_motor_control(bool enabled) {} // TODO + void set_tape_output(bool set) {} // TODO + inline bool get_input() { return _input_level; } + + class Delegate { + public: + virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0; + }; + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + + private: + Delegate *_delegate; + virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse) + { + bool new_input_level = pulse.type == Tape::Pulse::Low; + if(_input_level != new_input_level) + { + _input_level = new_input_level; + if(_delegate) _delegate->tape_did_change_input(this); + } + } + bool _input_level; +}; + } } From a608bbebfbbe3d5e00e7739a9529c2ed98cce408 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:32:59 -0400 Subject: [PATCH 26/60] Performed enough wiring to put the onus back onto OricTAP to do appropriate things. --- Machines/Oric/Oric.cpp | 9 ++++++++- Machines/Oric/Oric.hpp | 7 +++---- Storage/Tape/Tape.hpp | 11 +++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 9307ec3d3..1297e0e07 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -10,17 +10,23 @@ using namespace Oric; -Machine::Machine() : _cycles_since_video_update(0), _tape(1000000) +Machine::Machine() : _cycles_since_video_update(0) { set_clock_rate(1000000); + _via.tape.reset(new Storage::Tape::BinaryTapePlayer(1000000)); _via.set_interrupt_delegate(this); _keyboard.reset(new Keyboard); _via.keyboard = _keyboard; clear_all_keys(); + _via.tape->set_delegate(this); } void Machine::configure_as_target(const StaticAnalyser::Target &target) { + if(target.tapes.size()) + { + _via.tape->set_tape(target.tapes.front()); + } } void Machine::set_rom(std::vector data) @@ -54,6 +60,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } _via.run_for_half_cycles(2); + _via.tape->run_for_cycles(1); _cycles_since_video_update++; return 1; } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 10fae88b7..baf37008f 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -97,10 +97,7 @@ class Machine: uint8_t rows[8]; }; - // Tape player - Storage::Tape::BinaryTapePlayer _tape; - - // VIA + // VIA (which owns the tape and the AY) class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: using MOS6522IRQDelegate::set_interrupt_status; @@ -118,6 +115,7 @@ class Machine: if(port) { keyboard->row = value; + tape->set_motor_control(value & 0x40); } else { @@ -145,6 +143,7 @@ class Machine: } std::shared_ptr ay8910; + std::shared_ptr tape; std::shared_ptr keyboard; inline void synchronise() { update_ay(); } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index dc45e6e9c..fcf2b3d2a 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -96,11 +96,17 @@ class TapePlayer: public TimedEventLoop { class BinaryTapePlayer: public TapePlayer { public: - BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate) {} - void set_motor_control(bool enabled) {} // TODO + BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate), _motor_is_running(false) {} + void set_motor_control(bool enabled) { _motor_is_running = enabled; } void set_tape_output(bool set) {} // TODO inline bool get_input() { return _input_level; } + void run_for_cycles(int number_of_cycles) { + if(_motor_is_running) { + TapePlayer::run_for_cycles(number_of_cycles); + } + } + class Delegate { public: virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0; @@ -122,6 +128,7 @@ class BinaryTapePlayer: public TapePlayer { } } bool _input_level; + bool _motor_is_running; }; } From 952a24f769eba1b967e937eb86a94892407b87bf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:39:53 -0400 Subject: [PATCH 27/60] A quick hard-wiring of the OricTAP code to get the first file in a .tap correct, damn the rest, and I'm getting some on-screen feedback. Hooray! --- Storage/Tape/Formats/OricTAP.cpp | 34 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 92058bf70..1d60529ff 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -42,9 +42,9 @@ OricTAP::~OricTAP() void OricTAP::virtual_reset() { - fseek(_file, 0, SEEK_SET); +// fseek(_file, 0, SEEK_SET); _bit_count = 13; - _phase = LeadIn; + _phase = _next_phase = LeadIn; _phase_counter = 0; _pulse_counter = 0; } @@ -65,9 +65,9 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() switch(_phase) { case LeadIn: - next_byte = 0x16; + next_byte = _phase_counter < 256 ? 0x16 : 0x24; _phase_counter++; - if(_phase_counter == 256) // 256 artificial bytes plus the three in the file = 259 + if(_phase_counter == 259) // 256 artificial bytes plus the three in the file = 259 { _next_phase = Header; } @@ -75,24 +75,22 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() case Header: // Counts are relative to: - // [0, 2]: value 0x16 - // 3: value '$' - // [4, 5]: "two bytes unused" (on the Oric 1) - // 6: program type - // 7: auto indicator - // [8, 9]: end address of data - // [10, 11]: start address of data - // 12: "unused" (on the Oric 1) - // [13...]: filename, up to NULL byte + // [0, 1]: "two bytes unused" (on the Oric 1) + // 2: program type + // 3: auto indicator + // [4, 5]: end address of data + // [6, 7]: start address of data + // 8: "unused" (on the Oric 1) + // [9...]: filename, up to NULL byte next_byte = (uint8_t)fgetc(_file); - if(_phase_counter == 8) _data_end_address = (uint16_t)(next_byte << 8); - if(_phase_counter == 9) _data_end_address |= next_byte; - if(_phase_counter == 10) _data_start_address = (uint16_t)(next_byte << 8); - if(_phase_counter == 11) _data_start_address |= next_byte; + if(_phase_counter == 4) _data_end_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 5) _data_end_address |= next_byte; + if(_phase_counter == 6) _data_start_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 7) _data_start_address |= next_byte; _phase_counter++; - if(_phase_counter > 12 && !next_byte) // advance after the filename-ending NULL byte + if(_phase_counter >= 9 && !next_byte) // advance after the filename-ending NULL byte { _next_phase = Gap; } From a67afb7efa4091481695de144a8709b5c0ebecf2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:43:46 -0400 Subject: [PATCH 28/60] Switched to a tight crop on the pixel part of the display. --- Machines/Oric/Video.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 6f8ec7388..8353e596d 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -30,6 +30,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : "}"); _crt->set_output_device(Outputs::CRT::Television); + _crt->set_visible_area(_crt->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); } std::shared_ptr VideoOutput::get_crt() From fae1bb0db90b7407286db5fe607fa81d31c36616 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Oct 2016 21:49:41 -0400 Subject: [PATCH 29/60] First successful game loaded! It turns out exactly one '$' is correct. Probably. --- Storage/Tape/Formats/OricTAP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 1d60529ff..6857fd06a 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -65,7 +65,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() switch(_phase) { case LeadIn: - next_byte = _phase_counter < 256 ? 0x16 : 0x24; + next_byte = _phase_counter < 258 ? 0x16 : 0x24; _phase_counter++; if(_phase_counter == 259) // 256 artificial bytes plus the three in the file = 259 { From 61ad0f8bdcef688ec09332041a6a2aed8895c9f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 16 Oct 2016 22:14:01 -0400 Subject: [PATCH 30/60] Fixed inverse characters, added an extra per-frame phase change, based on empirical observation, ensured header guard won't become ambiguous. --- Machines/Oric/Video.cpp | 8 ++++++-- Machines/Oric/Video.hpp | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 8353e596d..ac81f6dd6 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -56,7 +56,11 @@ void VideoOutput::run_for_cycles(int number_of_cycles) set_character_set_base_address(); _phase += 64; - if(!_counter) _frame_counter++; + if(!_counter) + { + _phase += 64; + _frame_counter++; + } } State new_state = Blank; @@ -93,7 +97,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; control_byte = _ram[address]; int line = _use_double_height_characters ? ((_counter >> 7) & 7) : ((_counter >> 6) & 7); - pixels = _ram[_character_set_base_address + control_byte * 8 + line]; + pixels = _ram[_character_set_base_address + (control_byte&127) * 8 + line]; } uint8_t inverse_mask = (control_byte & 0x80) ? 0x77 : 0x00; diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index 77d31821d..e3cea4ee5 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -6,8 +6,8 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef Video_hpp -#define Video_hpp +#ifndef Machines_Oric_Video_hpp +#define Machines_Oric_Video_hpp #include "../../Outputs/CRT/CRT.hpp" From b274d7008cb37c033de6fcfa8e92f6fe16ed2113 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 16 Oct 2016 22:14:47 -0400 Subject: [PATCH 31/60] Added precaution to make sure best-effort updaters aren't mid-update during document destruction. --- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 1 + OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h | 1 + OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index a29f1fad4..028372e2e 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -92,6 +92,7 @@ class MachineDocument: } override func close() { + bestEffortUpdater.flush() actionLock.lock() drawLock.lock() openGLView.invalidate() diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h index 2ed7a43bc..7130f3636 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h @@ -25,5 +25,6 @@ @property (nonatomic, weak) id delegate; - (void)update; +- (void)flush; @end diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m index 10c0a133d..309324f42 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m @@ -63,4 +63,9 @@ } } +- (void)flush +{ + dispatch_sync(_serialDispatchQueue, ^{}); +} + @end From cc0b70828bc5e929cafdff04e0c0933ff9d466c6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 16 Oct 2016 22:15:24 -0400 Subject: [PATCH 32/60] Removed attempt at multiple-file logic, at least for the time being. Starting to wonder whether I actually need anything beyond a literal streaming of bytes? --- Storage/Tape/Formats/OricTAP.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 6857fd06a..f36a8cca6 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -106,19 +106,20 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() case Data: next_byte = (uint8_t)fgetc(_file); - _phase_counter++; - if(_phase_counter == (_data_end_address - _data_start_address)) - { - _phase_counter = 0; - if((size_t)ftell(_file) == _file_length) - { - _next_phase = End; - } - else - { - _next_phase = LeadIn; - } - } + if(feof(_file)) _phase = End; +// _phase_counter++; +// if(_phase_counter == (_data_end_address - _data_start_address)+1) +// { +// _phase_counter = 0; +// if((size_t)ftell(_file) == _file_length) +// { +// _next_phase = End; +// } +// else +// { +// _next_phase = LeadIn; +// } +// } break; case End: From 43612e1ca2113ddbc1c0b28c815795da4749c60e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:03:38 -0400 Subject: [PATCH 33/60] Made an attempt to eliminate conditionals (running before I can walk?) and started edging towards an envelope generator. --- Components/AY38910/AY38910.cpp | 23 +++++++++++++++-------- Components/AY38910/AY38910.hpp | 2 ++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 08a8dacac..7756cf908 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -10,7 +10,7 @@ using namespace GI; -AY38910::AY38910() : _selected_register(0), _channel_ouput{0, 0, 0} +AY38910::AY38910() : _selected_register(0), _channel_ouput{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0} { _output_registers[8] = _output_registers[9] = _output_registers[10] = 0; } @@ -20,13 +20,6 @@ void AY38910::set_clock_rate(double clock_rate) set_input_rate((float)clock_rate); } -#define step(c) \ - _channel_dividers[c] -= resulting_steps; \ - if(!_channel_dividers[c]) \ - { \ - _channel_dividers[c] = (int)_tone_generator_controls[c] + 1; \ - _channel_ouput[c] ^= 1; \ - } void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { @@ -38,10 +31,24 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; // from that the three channels count down +#define step(c) \ + _channel_dividers[c] -= resulting_steps; \ + _channel_ouput[c] ^= (_channel_dividers[c] >> 15); \ + _channel_dividers[c] = ((_channel_dividers[c] >> 15) * _tone_generator_controls[c]) + ((_channel_dividers[c] >> 15)^1) * _channel_dividers[c]; + step(0); step(1); step(2); +#undef step + + // ... as does the envelope generator + _envelope_divider -= resulting_steps; + if(!_envelope_divider) + { + _envelope_divider = _envelope_period; + } + *target++ = (int16_t)(( ((_output_registers[8]&0xf) * _channel_ouput[0]) + ((_output_registers[9]&0xf) * _channel_ouput[1]) + diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index ddef6e134..3f0348982 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -36,6 +36,8 @@ class AY38910: public ::Outputs::Filter { int _master_divider; int _channel_dividers[3]; + int _envelope_divider; + int _evelope_volume; int _channel_ouput[3]; }; From 1a57e89ff0df62b0cf07734a0915de0d14d4de91 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:04:15 -0400 Subject: [PATCH 34/60] Altered phase so that it now merely accounts for accumulated error across a frame. Can probably do better. --- Machines/Oric/Video.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index ac81f6dd6..68c456cca 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -58,7 +58,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) if(!_counter) { - _phase += 64; + _phase += 128; _frame_counter++; } } From c105f2acd997231ac3f95397558e3cf773777de2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:04:36 -0400 Subject: [PATCH 35/60] Sought to reduce chattiness. --- OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h | 3 ++- OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h index e17fb372f..106454f99 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h @@ -11,7 +11,8 @@ @class CSAudioQueue; @protocol CSAudioQueueDelegate -- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue; +- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue; // TODO: rename this to audioQueueNeedsData or something, to indicate that + // it means something more along the lines of 'may run out soon' @end /*! diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index d7fd0b598..4771ae381 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -14,13 +14,15 @@ @implementation CSAudioQueue { AudioQueueRef _audioQueue; + size_t _queuedSamples; } #pragma mark - AudioQueue callbacks - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { - [self.delegate audioQueueDidCompleteBuffer:self]; + _queuedSamples -= (size_t)(buffer->mAudioDataByteSize / sizeof(int16_t)); + if(_queuedSamples < 128) [self.delegate audioQueueDidCompleteBuffer:self]; AudioQueueFreeBuffer(_audioQueue, buffer); } @@ -97,6 +99,7 @@ static void audioOutputCallback( { AudioQueueBufferRef newBuffer; size_t bufferBytes = lengthInSamples * sizeof(int16_t); + _queuedSamples += lengthInSamples; AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer); memcpy(newBuffer->mAudioData, buffer, bufferBytes); From c628b7c5c5676c6258613d0d0ab55d61d48d64b2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:05:02 -0400 Subject: [PATCH 36/60] This is the real PAL frequency, I think. --- Outputs/CRT/CRT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b151f627c..9d1b9436f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -53,7 +53,7 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display switch(displayType) { case DisplayType::PAL50: - set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 1135, 4); + set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500); // i.e. 283.7516 break; case DisplayType::NTSC60: From 988bbb5ab167ae0e486f95f3fad5794f229a9f62 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:05:57 -0400 Subject: [PATCH 37/60] Ensured AY registers aren't rewritten just because of a synchronise event. A stall prior to figuring out proper bus logic, clearly. --- Machines/Oric/Oric.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index baf37008f..06dfc3f2d 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -146,7 +146,7 @@ class Machine: std::shared_ptr tape; std::shared_ptr keyboard; - inline void synchronise() { update_ay(); } + inline void synchronise() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); } private: void update_ay() From f6b6ec7009a6498207f3068a06108961f7fd0ca8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Oct 2016 08:18:32 -0400 Subject: [PATCH 38/60] Cemented new meaningof the audio queue delegate callout. --- OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h | 3 +-- OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m | 6 ++++-- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h index 106454f99..5bdf71d4a 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h @@ -11,8 +11,7 @@ @class CSAudioQueue; @protocol CSAudioQueueDelegate -- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue; // TODO: rename this to audioQueueNeedsData or something, to indicate that - // it means something more along the lines of 'may run out soon' +- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue; @end /*! diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index 4771ae381..2e1ef29e1 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -21,8 +21,10 @@ - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { - _queuedSamples -= (size_t)(buffer->mAudioDataByteSize / sizeof(int16_t)); - if(_queuedSamples < 128) [self.delegate audioQueueDidCompleteBuffer:self]; + size_t samplesInBuffer = (size_t)(buffer->mAudioDataByteSize / sizeof(int16_t)); + if(_queuedSamples >= 128 && _queuedSamples - samplesInBuffer < 128) [self.delegate audioQueueIsRunningDry:self]; + _queuedSamples -= samplesInBuffer; + AudioQueueFreeBuffer(_audioQueue, buffer); } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 028372e2e..8f54d6884 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -148,7 +148,7 @@ class MachineDocument: } // MARK: CSAudioQueueDelegate - final func audioQueueDidCompleteBuffer(_ audioQueue: CSAudioQueue) { + final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) { bestEffortUpdater.update() } From 9669a5ec9be7e582f39f91b1acefb597c6dbbc2c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 18 Oct 2016 19:32:15 -0400 Subject: [PATCH 39/60] Switched to a more authentic interfacing to the AY. --- Components/AY38910/AY38910.cpp | 38 ++++++++++++++++++++++++++++++++++ Components/AY38910/AY38910.hpp | 24 ++++++++++++++++++--- Machines/Oric/Oric.hpp | 17 +++------------ 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 7756cf908..239b8785d 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -108,3 +108,41 @@ uint8_t AY38910::get_port_output(bool port_b) { return _registers[port_b ? 15 : 14]; } + +void AY38910::set_data_input(uint8_t r) +{ + _data_input = r; +} + +uint8_t AY38910::get_data_output() +{ + return _data_output; +} + +void AY38910::set_control_lines(ControlLines control_lines) +{ + ControlState new_state; + switch((int)control_lines) + { + default: new_state = Inactive; break; + + case (int)(BCDIR | BC2 | BC1): + case BCDIR: + case BC1: new_state = LatchAddress; break; + + case (int)(BC2 | BC1): new_state = Read; break; + case (int)(BCDIR | BC2): new_state = Write; break; + } + + if(new_state != _control_state) + { + _control_state = new_state; + switch(new_state) + { + default: break; + case LatchAddress: select_register(_data_input); break; + case Write: set_register_value(_data_input); break; + case Read: _data_output = get_register_value(); break; + } + } +} diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 3f0348982..49080a482 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -21,9 +21,14 @@ class AY38910: public ::Outputs::Filter { void get_samples(unsigned int number_of_samples, int16_t *target); void skip_samples(unsigned int number_of_samples); - void select_register(uint8_t r); - void set_register_value(uint8_t value); - uint8_t get_register_value(); + enum ControlLines { + BC1 = (1 << 0), + BC2 = (1 << 1), + BCDIR = (1 << 2) + }; + void set_data_input(uint8_t r); + uint8_t get_data_output(); + void set_control_lines(ControlLines control_lines); uint8_t get_port_output(bool port_b); @@ -39,6 +44,19 @@ class AY38910: public ::Outputs::Filter { int _envelope_divider; int _evelope_volume; int _channel_ouput[3]; + + enum ControlState { + Inactive, + LatchAddress, + Read, + Write + } _control_state; + + void select_register(uint8_t r); + void set_register_value(uint8_t value); + uint8_t get_register_value(); + + uint8_t _data_input, _data_output; }; }; diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 06dfc3f2d..5c3167602 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -119,8 +119,7 @@ class Machine: } else { - _port_a_output = value; - update_ay(); + ay8910->set_data_input(value); } } @@ -132,7 +131,7 @@ class Machine: } else { - return _port_a_input; + return ay8910->get_data_output(); } } @@ -152,18 +151,8 @@ class Machine: void update_ay() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); - _half_cycles_since_ay_update &= 1; - if(_ay_bdir) - { - if(_ay_bc1) ay8910->select_register(_port_a_output); - else ay8910->set_register_value(_port_a_output); - } - else - { - if(_ay_bc1) _port_a_input = ay8910->get_register_value(); - } + ay8910->set_control_lines( (GI::AY38910::ControlLines)((_ay_bdir ? GI::AY38910::BCDIR : 0) | (_ay_bc1 ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); } - uint8_t _port_a_output, _port_a_input; bool _ay_bdir, _ay_bc1; unsigned int _half_cycles_since_ay_update; }; From b7609d2bb46c023dfd049ae197d5056ec0b568cf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 18 Oct 2016 19:32:38 -0400 Subject: [PATCH 40/60] Fixed a potential race condition and simplified generally. --- Concurrency/AsyncTaskQueue.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 6f9f40027..75ae57175 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -17,23 +17,25 @@ AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false) { std::function next_function; - queue_mutex_.lock(); + // Take lock, check for a new task + std::unique_lock lock(queue_mutex_); if(!pending_tasks_.empty()) { next_function = pending_tasks_.front(); pending_tasks_.pop_front(); } - queue_mutex_.unlock(); if(next_function) { + // If there is a task, release lock and perform it + lock.unlock(); next_function(); } else { - std::unique_lock lock(queue_mutex_); + // If there isn't a task, atomically block on the processing condition and release the lock + // until there's something pending (and then release it again via scope) processing_condition_.wait(lock); - lock.unlock(); } } })); @@ -44,15 +46,13 @@ AsyncTaskQueue::~AsyncTaskQueue() should_destruct_ = true; enqueue([](){}); thread_->join(); + thread_.reset( ); } void AsyncTaskQueue::enqueue(std::function function) { - queue_mutex_.lock(); - pending_tasks_.push_back(function); - queue_mutex_.unlock(); - std::lock_guard lock(queue_mutex_); + pending_tasks_.push_back(function); processing_condition_.notify_all(); } From bd6e6674a0b6d634cab6ef46ee827ca7c1fe739d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 18 Oct 2016 22:20:12 -0400 Subject: [PATCH 41/60] Fixed signed shift assumption and noise-related register test. --- Components/AY38910/AY38910.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 239b8785d..a26f1deb7 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -20,7 +20,6 @@ void AY38910::set_clock_rate(double clock_rate) set_input_rate((float)clock_rate); } - void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { for(int c = 0; c < number_of_samples; c++) @@ -31,10 +30,12 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; // from that the three channels count down -#define step(c) \ +#define step(c) {\ _channel_dividers[c] -= resulting_steps; \ - _channel_ouput[c] ^= (_channel_dividers[c] >> 15); \ - _channel_dividers[c] = ((_channel_dividers[c] >> 15) * _tone_generator_controls[c]) + ((_channel_dividers[c] >> 15)^1) * _channel_dividers[c]; + int did_underflow = (_channel_dividers[c] >> 15)&1; \ + _channel_ouput[c] ^= did_underflow; \ + _channel_dividers[c] = did_underflow * _tone_generator_controls[c] + (did_underflow^1) * _channel_dividers[c]; \ + } step(0); step(1); @@ -43,16 +44,18 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) #undef step // ... as does the envelope generator - _envelope_divider -= resulting_steps; - if(!_envelope_divider) - { - _envelope_divider = _envelope_period; - } +// _envelope_divider -= resulting_steps; +// if(!_envelope_divider) +// { +// _envelope_divider = _envelope_period; +// } - *target++ = (int16_t)(( - ((_output_registers[8]&0xf) * _channel_ouput[0]) + - ((_output_registers[9]&0xf) * _channel_ouput[1]) + - ((_output_registers[10]&0xf) * _channel_ouput[2]) +// if(_output_registers[9]) printf("%d %d / %d\n", _channel_ouput[1], _channel_dividers[1], _tone_generator_controls[1]); + + target[c] = (int16_t)(( + (_output_registers[8]&0xf) * _channel_ouput[0] + + (_output_registers[9]&0xf) * _channel_ouput[1] + + (_output_registers[10]&0xf) * _channel_ouput[2] ) * 512); } } @@ -60,6 +63,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) void AY38910::skip_samples(unsigned int number_of_samples) { // TODO +// printf("Skip %d\n", number_of_samples); } void AY38910::select_register(uint8_t r) @@ -70,11 +74,10 @@ void AY38910::select_register(uint8_t r) void AY38910::set_register_value(uint8_t value) { _registers[_selected_register] = value; - if(value < 14) + if(_selected_register < 14) { int selected_register = _selected_register; enqueue([=] () { - _output_registers[selected_register] = value; switch(selected_register) { case 0: case 2: case 4: @@ -95,6 +98,7 @@ void AY38910::set_register_value(uint8_t value) _envelope_period = (_envelope_period & 0xff) | (uint16_t)(value << 8); break; } + _output_registers[selected_register] = value; }); } } From ca28e3c64e3ac3600f1c41628d578a4f7dcefebd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 18 Oct 2016 22:21:06 -0400 Subject: [PATCH 42/60] Ensured the AY is pumped linearly, not exponentially. --- Machines/Oric/Oric.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 5c3167602..d37a9429d 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -145,12 +145,13 @@ class Machine: std::shared_ptr tape; std::shared_ptr keyboard; - inline void synchronise() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); } + inline void synchronise() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); _half_cycles_since_ay_update = 0; } private: void update_ay() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); + _half_cycles_since_ay_update = 0; ay8910->set_control_lines( (GI::AY38910::ControlLines)((_ay_bdir ? GI::AY38910::BCDIR : 0) | (_ay_bc1 ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); } bool _ay_bdir, _ay_bc1; From 5a808d789ac9901fce2d0651242138562bdad3fa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 18 Oct 2016 22:21:34 -0400 Subject: [PATCH 43/60] Added an upper threshold that must be crossed before a lower threshold warning is communicated. --- OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index 2e1ef29e1..a99fdf3b5 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -15,6 +15,7 @@ { AudioQueueRef _audioQueue; size_t _queuedSamples; + BOOL _hasHad256; } #pragma mark - AudioQueue callbacks @@ -22,7 +23,11 @@ - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { size_t samplesInBuffer = (size_t)(buffer->mAudioDataByteSize / sizeof(int16_t)); - if(_queuedSamples >= 128 && _queuedSamples - samplesInBuffer < 128) [self.delegate audioQueueIsRunningDry:self]; + if(_queuedSamples >= 128 && _queuedSamples - samplesInBuffer < 128 && _hasHad256) + { + _hasHad256 = NO; + [self.delegate audioQueueIsRunningDry:self]; + } _queuedSamples -= samplesInBuffer; AudioQueueFreeBuffer(_audioQueue, buffer); @@ -102,6 +107,7 @@ static void audioOutputCallback( AudioQueueBufferRef newBuffer; size_t bufferBytes = lengthInSamples * sizeof(int16_t); _queuedSamples += lengthInSamples; + _hasHad256 |= (_queuedSamples >= 256); AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer); memcpy(newBuffer->mAudioData, buffer, bufferBytes); From ada37abe233ded73ebd34a40a6ebec8cfb543065 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:13:22 -0400 Subject: [PATCH 44/60] Made an attempt to implement noise and envelopes. Not quite right yet. --- Components/AY38910/AY38910.cpp | 123 ++++++++++++++++++++++++++++----- Components/AY38910/AY38910.hpp | 17 +++-- 2 files changed, 120 insertions(+), 20 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index a26f1deb7..b10b73d4c 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -10,9 +10,59 @@ using namespace GI; -AY38910::AY38910() : _selected_register(0), _channel_ouput{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0} +AY38910::AY38910() : + _selected_register(0), + _channel_output{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0}, + _noise_shift_register(0xffff), _noise_divider(0), _noise_output(0), + _envelope_divider(0), _envelope_period(0) { _output_registers[8] = _output_registers[9] = _output_registers[10] = 0; + + // set up envelope lookup tables + for(int c = 0; c < 16; c++) + { + for(int p = 0; p < 32; p++) + { + switch(c) + { + case 0: case 1: case 2: case 3: case 9: + _envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0; + _envelope_overflow_masks[c] = 0x1f; + break; + case 4: case 5: case 6: case 7: case 15: + _envelope_shapes[c][p] = (p < 16) ? p : 0; + _envelope_overflow_masks[c] = 0x1f; + break; + + case 8: + _envelope_shapes[c][p] = (p & 0xf) ^ 0xf; + _envelope_overflow_masks[c] = 0x00; + break; + case 12: + _envelope_shapes[c][p] = (p & 0xf); + _envelope_overflow_masks[c] = 0x00; + break; + + case 10: + _envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0); + _envelope_overflow_masks[c] = 0x00; + break; + case 14: + _envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf); + _envelope_overflow_masks[c] = 0x00; + break; + + case 11: + _envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0xf; + _envelope_overflow_masks[c] = 0x1f; + break; + case 13: + _envelope_shapes[c][p] = (p < 16) ? p : 0xf; + _envelope_overflow_masks[c] = 0x1f; + break; + } + } + } } void AY38910::set_clock_rate(double clock_rate) @@ -30,12 +80,12 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; // from that the three channels count down -#define step(c) {\ + int did_underflow; +#define step(c) \ _channel_dividers[c] -= resulting_steps; \ - int did_underflow = (_channel_dividers[c] >> 15)&1; \ - _channel_ouput[c] ^= did_underflow; \ - _channel_dividers[c] = did_underflow * _tone_generator_controls[c] + (did_underflow^1) * _channel_dividers[c]; \ - } + did_underflow = (_channel_dividers[c] >> 15)&1; \ + _channel_output[c] ^= did_underflow; \ + _channel_dividers[c] = did_underflow * _tone_generator_controls[c] + (did_underflow^1) * _channel_dividers[c]; step(0); step(1); @@ -43,19 +93,51 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) #undef step - // ... as does the envelope generator -// _envelope_divider -= resulting_steps; -// if(!_envelope_divider) + // ... as does the noise generator + _noise_divider -= resulting_steps; + did_underflow = (_noise_divider >> 15)&1; + _noise_divider = did_underflow * (_output_registers[6]&0x1f) + (did_underflow^1) * _noise_divider; + _noise_output ^= did_underflow&_noise_shift_register&1; + _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; + _noise_shift_register >>= did_underflow; + + // ... and the envelope generator + _envelope_divider -= resulting_steps; + did_underflow = (_envelope_divider >> 15)&1; + _envelope_divider = did_underflow * _envelope_period + (did_underflow^1) * _envelope_divider; + _envelope_position += did_underflow; + +// if(_output_registers[13] == 13) // { -// _envelope_divider = _envelope_period; +// printf("[%d] %d", _envelope_divider, _envelope_position); // } -// if(_output_registers[9]) printf("%d %d / %d\n", _channel_ouput[1], _channel_dividers[1], _tone_generator_controls[1]); + int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5); + _envelope_position = (_envelope_position & 0x1f) | refill; + + int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position & 0xf]; + +// if(_output_registers[13] == 13) +// { +// printf(": %d\n", envelope_volume); +// } + + int channel_levels[3] = { + (((((_output_registers[7] >> 0)&1)^1) & _channel_output[0]) | ((((_output_registers[7] >> 1)&1)^1) & _noise_output)) ^ 1, + (((((_output_registers[7] >> 2)&1)^1) & _channel_output[1]) | ((((_output_registers[7] >> 3)&1)^1) & _noise_output)) ^ 1, + (((((_output_registers[7] >> 4)&1)^1) & _channel_output[2]) | ((((_output_registers[7] >> 5)&1)^1) & _noise_output)) ^ 1, + }; + + int volumes[3] = { + ((_output_registers[8] >> 4)&1) * envelope_volume + (((_output_registers[8] >> 4)&1)^1) * (_output_registers[8]&0x1f), + ((_output_registers[9] >> 4)&1) * envelope_volume + (((_output_registers[9] >> 4)&1)^1) * (_output_registers[9]&0x1f), + ((_output_registers[10] >> 4)&1) * envelope_volume + (((_output_registers[10] >> 4)&1)^1) * (_output_registers[10]&0x1f), + }; target[c] = (int16_t)(( - (_output_registers[8]&0xf) * _channel_ouput[0] + - (_output_registers[9]&0xf) * _channel_ouput[1] + - (_output_registers[10]&0xf) * _channel_ouput[2] + volumes[0] * channel_levels[0] + + volumes[1] * channel_levels[1] + + volumes[2] * channel_levels[2] ) * 512); } } @@ -78,6 +160,7 @@ void AY38910::set_register_value(uint8_t value) { int selected_register = _selected_register; enqueue([=] () { + uint8_t masked_value = value; switch(selected_register) { case 0: case 2: case 4: @@ -92,13 +175,21 @@ void AY38910::set_register_value(uint8_t value) case 11: _envelope_period = (_envelope_period & ~0xff) | value; +// printf("e: %d", _envelope_period); break; case 12: - _envelope_period = (_envelope_period & 0xff) | (uint16_t)(value << 8); + _envelope_period = (_envelope_period & 0xff) | (int)(value << 8); +// printf("e: %d", _envelope_period); + break; + + case 13: + masked_value &= 0xf; + _envelope_position = 0; +// printf("envelope %d\n", masked_value); break; } - _output_registers[selected_register] = value; + _output_registers[selected_register] = masked_value; }); } } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 49080a482..2e79163a9 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -37,13 +37,22 @@ class AY38910: public ::Outputs::Filter { uint8_t _registers[16], _output_registers[16]; uint16_t _tone_generator_controls[3]; - uint16_t _envelope_period; + int _channel_dividers[3]; + int _channel_output[3]; + int _master_divider; - int _channel_dividers[3]; + + int _noise_divider; + int _noise_shift_register; + int _noise_output; + + int _envelope_period; int _envelope_divider; - int _evelope_volume; - int _channel_ouput[3]; + + int _envelope_position; + int _envelope_shapes[16][32]; + int _envelope_overflow_masks[16]; enum ControlState { Inactive, From 42584013841751df69b0e97584b7b701e6e7ff6e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:15:04 -0400 Subject: [PATCH 45/60] Implemented `flush`, added a call to it from the filter speaker's destructor, to ensure no race conditions on accessing the various bits of instance state there and below. --- Concurrency/AsyncTaskQueue.cpp | 14 ++++++++++---- Concurrency/AsyncTaskQueue.hpp | 2 +- Outputs/Speaker.hpp | 20 ++++++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 75ae57175..1aa544c5e 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -46,7 +46,7 @@ AsyncTaskQueue::~AsyncTaskQueue() should_destruct_ = true; enqueue([](){}); thread_->join(); - thread_.reset( ); + thread_.reset(); } void AsyncTaskQueue::enqueue(std::function function) @@ -56,8 +56,14 @@ void AsyncTaskQueue::enqueue(std::function function) processing_condition_.notify_all(); } -void AsyncTaskQueue::synchronise() +void AsyncTaskQueue::flush() { - // TODO -// std::mutex + std::shared_ptr flush_mutex(new std::mutex); + std::shared_ptr flush_condition(new std::condition_variable); + std::unique_lock lock(*flush_mutex); + enqueue([=] () { + std::unique_lock inner_lock(*flush_mutex); + flush_condition->notify_all(); + }); + flush_condition->wait(lock); } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index 6d06f2264..77485265a 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -39,7 +39,7 @@ class AsyncTaskQueue { /*! Blocks the caller until all previously-enqueud functions have completed. */ - void synchronise(); + void flush(); private: std::unique_ptr thread_; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 04ff49c8f..918dbe4a6 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -26,7 +26,7 @@ namespace Outputs { Intended to be a parent class, allowing descendants to pick the strategy by which input samples are mapped to output samples. */ -class Speaker: public Concurrency::AsyncTaskQueue { +class Speaker { public: class Delegate { public: @@ -85,9 +85,18 @@ class Speaker: public Concurrency::AsyncTaskQueue { set_needs_updated_filter_coefficients(); } - Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0), _high_frequency_cut_off(-1.0) {} + Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0), _high_frequency_cut_off(-1.0), _queue(new Concurrency::AsyncTaskQueue) {} protected: + void enqueue(std::function function) + { + _queue->enqueue(function); + } + void flush() + { + _queue->flush(); + } + std::unique_ptr _buffer_in_progress; float _high_frequency_cut_off; int _buffer_size; @@ -109,6 +118,8 @@ class Speaker: public Concurrency::AsyncTaskQueue { int16_t throwaway_samples[quantity]; get_samples(quantity, throwaway_samples); } + + std::unique_ptr _queue; }; /*! @@ -123,6 +134,11 @@ class Speaker: public Concurrency::AsyncTaskQueue { */ template class Filter: public Speaker { public: + ~Filter() + { + flush(); + } + void run_for_cycles(unsigned int input_cycles) { enqueue([=]() { From 734b575d30b014d2d961ab1d2fa41480c8e8fba9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:17:27 -0400 Subject: [PATCH 46/60] Ensured no attempt to write to pixel storage if none was available. --- Machines/Oric/Video.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 68c456cca..64fb5ae7d 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -105,14 +105,17 @@ void VideoOutput::run_for_cycles(int number_of_cycles) if((control_byte & 0x7f) >= 32) { - uint8_t colours[2] = { - (uint8_t)(_paper ^ inverse_mask), - (uint8_t)(_ink ^ inverse_mask), - }; + if(_pixel_target) + { + uint8_t colours[2] = { + (uint8_t)(_paper ^ inverse_mask), + (uint8_t)(_ink ^ inverse_mask), + }; - _pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0); - _pixel_target[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0); - _pixel_target[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0); + _pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0); + _pixel_target[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0); + _pixel_target[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0); + } } else { @@ -152,9 +155,9 @@ void VideoOutput::run_for_cycles(int number_of_cycles) default: break; } - _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = (uint8_t)(_paper ^ inverse_mask); + if(_pixel_target) _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = (uint8_t)(_paper ^ inverse_mask); } - _pixel_target += 3; + if(_pixel_target) _pixel_target += 3; } } } From 5c697286259618cd8b6ab17e65cfcf334f5cda09 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:31:50 -0400 Subject: [PATCH 47/60] Introduced a memory fuzzer, and ensured the Oric uses it. --- Machines/MemoryFuzzer.cpp | 27 +++++++++++++++++++ Machines/MemoryFuzzer.hpp | 21 +++++++++++++++ Machines/Oric/Oric.cpp | 2 ++ .../Clock Signal.xcodeproj/project.pbxproj | 6 +++++ 4 files changed, 56 insertions(+) create mode 100644 Machines/MemoryFuzzer.cpp create mode 100644 Machines/MemoryFuzzer.hpp diff --git a/Machines/MemoryFuzzer.cpp b/Machines/MemoryFuzzer.cpp new file mode 100644 index 000000000..4c7934f2d --- /dev/null +++ b/Machines/MemoryFuzzer.cpp @@ -0,0 +1,27 @@ +// +// MemoryFuzzer.cpp +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "MemoryFuzzer.hpp" + +#include + +void Memory::Fuzz(uint8_t *buffer, size_t size) +{ + unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256; + unsigned int shift = 1, value = 1; + while(value < divider) + { + value <<= 1; + shift++; + } + + for(size_t c = 0; c < size; c++) + { + buffer[c] = (uint8_t)(rand() >> shift); + } +} diff --git a/Machines/MemoryFuzzer.hpp b/Machines/MemoryFuzzer.hpp new file mode 100644 index 000000000..648915033 --- /dev/null +++ b/Machines/MemoryFuzzer.hpp @@ -0,0 +1,21 @@ +// +// MemoryFuzzer.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef MemoryFuzzer_hpp +#define MemoryFuzzer_hpp + +#include +#include + +namespace Memory { + +void Fuzz(uint8_t *buffer, size_t size); + +} + +#endif /* MemoryFuzzer_hpp */ diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 1297e0e07..5f0becc8a 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -7,6 +7,7 @@ // #include "Oric.hpp" +#include "../MemoryFuzzer.hpp" using namespace Oric; @@ -19,6 +20,7 @@ Machine::Machine() : _cycles_since_video_update(0) _via.keyboard = _keyboard; clear_all_keys(); _via.tape->set_delegate(this); + Memory::Fuzz(_ram, sizeof(_ram)); } void Machine::configure_as_target(const StaticAnalyser::Target &target) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 95ce34e21..416ea7196 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; }; 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; + 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; }; 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; @@ -419,6 +420,8 @@ 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; + 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = ""; }; + 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = ""; }; 4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = ""; }; 4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = ""; }; 4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = ""; }; @@ -1602,6 +1605,8 @@ 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, + 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */, + 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */, ); name = Machines; path = ../../Machines; @@ -2234,6 +2239,7 @@ 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, + 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, From 5e2b0aa90191f58618438780d41276d89f0834e0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:32:12 -0400 Subject: [PATCH 48/60] Quick project clean up. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 416ea7196..22e4e5b1d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1597,16 +1597,16 @@ 4BB73EDC1B587CA500552FC2 /* Machines */ = { isa = PBXGroup; children = ( + 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */, 4B1E85731D170228001EF87D /* Typer.cpp */, 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, + 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */, 4B1E85741D170228001EF87D /* Typer.hpp */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, - 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */, - 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */, ); name = Machines; path = ../../Machines; From 101f168ea4d3e19a95842db0e8794e833f73bc74 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:43:18 -0400 Subject: [PATCH 49/60] Made an attempt to tidy up. --- Components/AY38910/AY38910.cpp | 74 ++++++++++++++++------------------ 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index b10b73d4c..d646a3d41 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -79,60 +79,56 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) _master_divider++; int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; - // from that the three channels count down int did_underflow; -#define step(c) \ - _channel_dividers[c] -= resulting_steps; \ - did_underflow = (_channel_dividers[c] >> 15)&1; \ - _channel_output[c] ^= did_underflow; \ - _channel_dividers[c] = did_underflow * _tone_generator_controls[c] + (did_underflow^1) * _channel_dividers[c]; +#define shift(x, r) \ + x -= resulting_steps; \ + did_underflow = (x >> 15)&1; \ + x = did_underflow * r + (did_underflow^1) * x; - step(0); - step(1); - step(2); +#define step_channel(c) \ + shift(_channel_dividers[c], _tone_generator_controls[c]); \ + _channel_output[c] ^= did_underflow; -#undef step + // update the tone channels + step_channel(0); + step_channel(1); + step_channel(2); - // ... as does the noise generator - _noise_divider -= resulting_steps; - did_underflow = (_noise_divider >> 15)&1; - _noise_divider = did_underflow * (_output_registers[6]&0x1f) + (did_underflow^1) * _noise_divider; + // ... the noise generator + shift(_noise_divider, _output_registers[6]&0x1f); _noise_output ^= did_underflow&_noise_shift_register&1; _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; _noise_shift_register >>= did_underflow; // ... and the envelope generator - _envelope_divider -= resulting_steps; - did_underflow = (_envelope_divider >> 15)&1; - _envelope_divider = did_underflow * _envelope_period + (did_underflow^1) * _envelope_divider; + shift(_envelope_divider, _envelope_period); _envelope_position += did_underflow; - -// if(_output_registers[13] == 13) -// { -// printf("[%d] %d", _envelope_divider, _envelope_position); -// } - int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5); _envelope_position = (_envelope_position & 0x1f) | refill; - int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position & 0xf]; -// if(_output_registers[13] == 13) -// { -// printf(": %d\n", envelope_volume); -// } +#undef step_channel +#undef shift + +#define level(c, tb, nb) \ + (((((_output_registers[7] >> tb)&1)^1) & _channel_output[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1 int channel_levels[3] = { - (((((_output_registers[7] >> 0)&1)^1) & _channel_output[0]) | ((((_output_registers[7] >> 1)&1)^1) & _noise_output)) ^ 1, - (((((_output_registers[7] >> 2)&1)^1) & _channel_output[1]) | ((((_output_registers[7] >> 3)&1)^1) & _noise_output)) ^ 1, - (((((_output_registers[7] >> 4)&1)^1) & _channel_output[2]) | ((((_output_registers[7] >> 5)&1)^1) & _noise_output)) ^ 1, + level(0, 0, 1), + level(1, 2, 3), + level(2, 4, 5), }; +#undef level + +#define channel_volume(c) \ + ((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0x1f) int volumes[3] = { - ((_output_registers[8] >> 4)&1) * envelope_volume + (((_output_registers[8] >> 4)&1)^1) * (_output_registers[8]&0x1f), - ((_output_registers[9] >> 4)&1) * envelope_volume + (((_output_registers[9] >> 4)&1)^1) * (_output_registers[9]&0x1f), - ((_output_registers[10] >> 4)&1) * envelope_volume + (((_output_registers[10] >> 4)&1)^1) * (_output_registers[10]&0x1f), + channel_volume(8), + channel_volume(9), + channel_volume(10) }; +#undef channel_volume target[c] = (int16_t)(( volumes[0] * channel_levels[0] + @@ -219,11 +215,11 @@ void AY38910::set_control_lines(ControlLines control_lines) ControlState new_state; switch((int)control_lines) { - default: new_state = Inactive; break; + default: new_state = Inactive; break; case (int)(BCDIR | BC2 | BC1): case BCDIR: - case BC1: new_state = LatchAddress; break; + case BC1: new_state = LatchAddress; break; case (int)(BC2 | BC1): new_state = Read; break; case (int)(BCDIR | BC2): new_state = Write; break; @@ -235,9 +231,9 @@ void AY38910::set_control_lines(ControlLines control_lines) switch(new_state) { default: break; - case LatchAddress: select_register(_data_input); break; - case Write: set_register_value(_data_input); break; - case Read: _data_output = get_register_value(); break; + case LatchAddress: select_register(_data_input); break; + case Write: set_register_value(_data_input); break; + case Read: _data_output = get_register_value(); break; } } } From 6073906c39cb7bb6aaa1eaaec4fbcbbb2dec5a5a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 21:51:35 -0400 Subject: [PATCH 50/60] Commented and fixed mistake that would equate any noise divider > 32768 with 0. --- Components/AY38910/AY38910.cpp | 23 +++++++++++++++++++---- Components/AY38910/AY38910.hpp | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index d646a3d41..bb40dd54a 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -74,15 +74,22 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { for(int c = 0; c < number_of_samples; c++) { - // a master divider divides the clock by 16 + // a master divider divides the clock by 16; + // resulting_steps will be 1 if a tick occurred, 0 otherwise int former_master_divider = _master_divider; _master_divider++; int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; + // Bluffer's guide to the stuff below: I wanted to avoid branches. If I avoid branches then + // I avoid stalls. + // + // Repeating patterns are: + // (1) decrement, then shift a high-order bit right and mask to get 1 for did underflow, 0 otherwise; + // (2) did_underflow * a + (did_underflow ^ 1) * b to pick between reloading and not reloading int did_underflow; #define shift(x, r) \ x -= resulting_steps; \ - did_underflow = (x >> 15)&1; \ + did_underflow = (x >> 16)&1; \ x = did_underflow * r + (did_underflow^1) * x; #define step_channel(c) \ @@ -94,13 +101,15 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) step_channel(1); step_channel(2); - // ... the noise generator + // ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting + // it into the official 17 upon divider underflow. shift(_noise_divider, _output_registers[6]&0x1f); _noise_output ^= did_underflow&_noise_shift_register&1; _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; _noise_shift_register >>= did_underflow; - // ... and the envelope generator + // ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of + // implementing non-repeating patterns by locking them to table position 0x1f. shift(_envelope_divider, _envelope_period); _envelope_position += did_underflow; int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5); @@ -110,6 +119,10 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) #undef step_channel #undef shift + // The output level for a channel is: + // 1 if neither tone nor noise is enabled; + // 0 if either tone or noise is enabled and its value is low. + // (which is implemented here with reverse logic, assuming _channel_output and _noise_output are already inverted) #define level(c, tb, nb) \ (((((_output_registers[7] >> tb)&1)^1) & _channel_output[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1 @@ -120,6 +133,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) }; #undef level + // Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits #define channel_volume(c) \ ((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0x1f) @@ -130,6 +144,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) }; #undef channel_volume + // Mix additively. TODO: non-linear volume. target[c] = (int16_t)(( volumes[0] * channel_levels[0] + volumes[1] * channel_levels[1] + diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 2e79163a9..a43b9b6f5 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -36,7 +36,7 @@ class AY38910: public ::Outputs::Filter { int _selected_register; uint8_t _registers[16], _output_registers[16]; - uint16_t _tone_generator_controls[3]; + int _tone_generator_controls[3]; int _channel_dividers[3]; int _channel_output[3]; From 319d7c2b12839b68a4ce973d25b89343daa0ec93 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 22:12:51 -0400 Subject: [PATCH 51/60] Fixed premature wrapping of the envelope, played about with whether that should be subject to a predivision by 256. It feels unlikely? --- Components/AY38910/AY38910.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index bb40dd54a..2a1018d0e 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -14,7 +14,8 @@ AY38910::AY38910() : _selected_register(0), _channel_output{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0}, _noise_shift_register(0xffff), _noise_divider(0), _noise_output(0), - _envelope_divider(0), _envelope_period(0) + _envelope_divider(0), _envelope_period(0), _envelope_position(0), + _output_registers{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} { _output_registers[8] = _output_registers[9] = _output_registers[10] = 0; @@ -87,13 +88,13 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) // (1) decrement, then shift a high-order bit right and mask to get 1 for did underflow, 0 otherwise; // (2) did_underflow * a + (did_underflow ^ 1) * b to pick between reloading and not reloading int did_underflow; -#define shift(x, r) \ - x -= resulting_steps; \ +#define shift(x, r, steps) \ + x -= steps; \ did_underflow = (x >> 16)&1; \ x = did_underflow * r + (did_underflow^1) * x; #define step_channel(c) \ - shift(_channel_dividers[c], _tone_generator_controls[c]); \ + shift(_channel_dividers[c], _tone_generator_controls[c], resulting_steps); \ _channel_output[c] ^= did_underflow; // update the tone channels @@ -103,18 +104,19 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) // ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting // it into the official 17 upon divider underflow. - shift(_noise_divider, _output_registers[6]&0x1f); + shift(_noise_divider, _output_registers[6]&0x1f, resulting_steps); _noise_output ^= did_underflow&_noise_shift_register&1; _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; _noise_shift_register >>= did_underflow; // ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of // implementing non-repeating patterns by locking them to table position 0x1f. - shift(_envelope_divider, _envelope_period); +// int envelope_divider = ((_master_divider ^ former_master_divider) >> 8) & 1; + shift(_envelope_divider, _envelope_period, resulting_steps); _envelope_position += did_underflow; int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5); _envelope_position = (_envelope_position & 0x1f) | refill; - int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position & 0xf]; + int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position]; #undef step_channel #undef shift @@ -197,7 +199,6 @@ void AY38910::set_register_value(uint8_t value) case 13: masked_value &= 0xf; _envelope_position = 0; -// printf("envelope %d\n", masked_value); break; } _output_registers[selected_register] = masked_value; From 59162228ef9bc50f72b08eb5139c11944263f74d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 22:14:05 -0400 Subject: [PATCH 52/60] Reduced mask for clarity. --- Components/AY38910/AY38910.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 2a1018d0e..e58f94e55 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -137,7 +137,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) // Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits #define channel_volume(c) \ - ((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0x1f) + ((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf) int volumes[3] = { channel_volume(8), From 08275c62414007927acab58e3114270386222d07 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 22:22:15 -0400 Subject: [PATCH 53/60] Fixed mixer IO bit usage. --- Components/AY38910/AY38910.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index e58f94e55..1d7d31fcd 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -129,9 +129,9 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) (((((_output_registers[7] >> tb)&1)^1) & _channel_output[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1 int channel_levels[3] = { - level(0, 0, 1), - level(1, 2, 3), - level(2, 4, 5), + level(0, 0, 3), + level(1, 1, 4), + level(2, 2, 5), }; #undef level From b59da7d4bc6bebf3baa36b20a4b2b5ff3340720f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 22:47:44 -0400 Subject: [PATCH 54/60] Added some documentation. --- Components/AY38910/AY38910.hpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index a43b9b6f5..e15f75f54 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -13,25 +13,44 @@ namespace GI { +/*! + Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a + noise generator and a volume envelope generator, which also provides two bidirectional + interface ports. +*/ class AY38910: public ::Outputs::Filter { public: + /// Creates a new AY38910. AY38910(); - void set_clock_rate(double clock_rate); - void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); + /// Sets the clock rate at which this AY38910 will be run. + void set_clock_rate(double clock_rate); enum ControlLines { BC1 = (1 << 0), BC2 = (1 << 1), BCDIR = (1 << 2) }; + + /// Sets the value the AY would read from its data lines if it were not outputting. void set_data_input(uint8_t r); + + /// Gets the value that would appear on the data lines if only the AY is outputting. uint8_t get_data_output(); + + /// Sets the void set_control_lines(ControlLines control_lines); + /*! + Gets the value that would appear on the requested interface port if it were in output mode. + @parameter port_b @c true to get the value for Port B, @c false to get the value for Port A. + */ uint8_t get_port_output(bool port_b); + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + private: int _selected_register; uint8_t _registers[16], _output_registers[16]; From c24c1bf3b1b893a8c9433e67b7cc6d3b2d8485c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 22:56:14 -0400 Subject: [PATCH 55/60] Created an options panel for the Oric. Which involved far too much copy and paste from the Electron. Time to figure out how to generalise this stuff, probably. --- .../Clock Signal.xcodeproj/project.pbxproj | 16 ++++++ .../Clock Signal/Base.lproj/OricOptions.xib | 54 +++++++++++++++++++ .../Documents/OricOptionsPanel.swift | 35 ++++++++++++ .../StaticAnalyser/CSStaticAnalyser.mm | 9 ++-- .../Clock Signal/Machine/Wrappers/CSOric.h | 2 + .../Clock Signal/Machine/Wrappers/CSOric.mm | 10 ++++ 6 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib create mode 100644 OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 22e4e5b1d..9dd259d2f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; + 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; }; + 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; }; 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; }; 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; @@ -422,6 +424,8 @@ 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = ""; }; 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = ""; }; + 4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; }; + 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OricOptionsPanel.swift; sourceTree = ""; }; 4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = ""; }; 4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = ""; }; 4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = ""; }; @@ -1101,10 +1105,12 @@ 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */, 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, + 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */, 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */, 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */, 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, + 4B2A332B1DB86821002876E3 /* OricOptions.xib */, 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */, ); path = Documents; @@ -1917,6 +1923,7 @@ buildActionMask = 2147483647; files = ( 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, + 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, 4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, @@ -2241,6 +2248,7 @@ 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, + 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, @@ -2342,6 +2350,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 4B2A332B1DB86821002876E3 /* OricOptions.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B2A332C1DB86821002876E3 /* Base */, + ); + name = OricOptions.xib; + sourceTree = ""; + }; 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib new file mode 100644 index 000000000..b590b86ab --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift b/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift new file mode 100644 index 000000000..aefd75a26 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift @@ -0,0 +1,35 @@ +// +// OricOptionsPanel.swift +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +class OricOptionsPanel: MachinePanel { + var oric: CSOric! { + get { + return self.machine as! CSOric + } + } + + fileprivate let displayTypeUserDefaultsKey = "oric.displayType" + + @IBOutlet var displayTypeButton: NSPopUpButton? + @IBAction func setDisplayType(_ sender: NSPopUpButton!) { + oric.useCompositeOutput = (sender.indexOfSelectedItem == 1) + UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) + } + + override func establishStoredOptions() { + super.establishStoredOptions() + let standardUserDefaults = UserDefaults.standard + standardUserDefaults.register(defaults: [ + displayTypeUserDefaultsKey: 0, + ]) + + let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey) + oric.useCompositeOutput = (displayType == 1) + self.displayTypeButton?.selectItem(at: displayType) + } +} diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index aa538059b..f8c07c466 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -45,9 +45,10 @@ { switch(_target.machine) { - case StaticAnalyser::Target::Electron: return @"ElectronOptions"; - case StaticAnalyser::Target::Vic20: return @"Vic20Options"; case StaticAnalyser::Target::Atari2600: return @"Atari2600Options"; + case StaticAnalyser::Target::Electron: return @"ElectronOptions"; + case StaticAnalyser::Target::Oric: return @"OricOptions"; + case StaticAnalyser::Target::Vic20: return @"Vic20Options"; default: return nil; } } @@ -56,10 +57,10 @@ { switch(_target.machine) { - case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; - case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; + case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; case StaticAnalyser::Target::Oric: return [[CSOric alloc] init]; + case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; default: return nil; } } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h index 285ac9d04..3cd3e67d4 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h @@ -11,4 +11,6 @@ @interface CSOric : CSMachine +@property(nonatomic, assign) BOOL useCompositeOutput; + @end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm index 1a726c4df..c6dde0e4a 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -130,6 +130,16 @@ - (void)clearAllKeys { + _oric.clear_all_keys(); +} + +#pragma mark - Options + +- (void)setUseCompositeOutput:(BOOL)useCompositeOutput { + @synchronized(self) { + _useCompositeOutput = useCompositeOutput; + _oric.get_crt()->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); + } } @end From cd59eb5f43a9d4e37160ee8bbb902b1ea4e864a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 23:07:51 -0400 Subject: [PATCH 56/60] Implemented non-linear volume. --- Components/AY38910/AY38910.cpp | 19 ++++++++++++++----- Components/AY38910/AY38910.hpp | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 1d7d31fcd..8595db8d9 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -64,6 +64,15 @@ AY38910::AY38910() : } } } + + // set up volume lookup table + float max_volume = 8192; + float root_two = sqrtf(2.0f); + for(int v = 0; v < 16; v++) + { + _volumes[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf))); + } + _volumes[0] = 0; } void AY38910::set_clock_rate(double clock_rate) @@ -147,11 +156,11 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) #undef channel_volume // Mix additively. TODO: non-linear volume. - target[c] = (int16_t)(( - volumes[0] * channel_levels[0] + - volumes[1] * channel_levels[1] + - volumes[2] * channel_levels[2] - ) * 512); + target[c] = (int16_t)( + _volumes[volumes[0]] * channel_levels[0] + + _volumes[volumes[1]] * channel_levels[1] + + _volumes[volumes[2]] * channel_levels[2] + ); } } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index e15f75f54..c6b5f7f78 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -59,6 +59,7 @@ class AY38910: public ::Outputs::Filter { int _channel_dividers[3]; int _channel_output[3]; + int _volumes[16]; int _master_divider; From 73365e187791c7420af345627f7eb3509e352c97 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 19 Oct 2016 23:09:05 -0400 Subject: [PATCH 57/60] Resolved sizing error. --- OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib | 2 -- 1 file changed, 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib index b590b86ab..e1cdad4d0 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib @@ -16,8 +16,6 @@ - - From 21cfb39ed90fc40b01765e472839e8f6fb48eccb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 20 Oct 2016 07:34:23 -0400 Subject: [PATCH 58/60] Added 60Hz output support. --- Machines/Oric/Video.cpp | 23 +++++++++++++++++++---- Machines/Oric/Video.hpp | 5 +++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 64fb5ae7d..7cbe9b29f 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -10,13 +10,24 @@ using namespace Oric; +namespace { + const unsigned int PAL50VSyncStartPosition = 256*64; + const unsigned int PAL60VSyncStartPosition = 234*64; + const unsigned int PAL50VSyncEndPosition = 259*64; + const unsigned int PAL60VSyncEndPosition = 238*64; + const unsigned int PAL50Period = 312*64; + const unsigned int PAL60Period = 262*64; +} + VideoOutput::VideoOutput(uint8_t *memory) : _ram(memory), _frame_counter(0), _counter(0), _state(Sync), _cycles_in_state(0), _is_graphics_mode(false), _character_set_base_address(0xb400), - _phase(0) + _phase(0), + _v_sync_start_position(PAL50VSyncStartPosition), _v_sync_end_position(PAL50VSyncEndPosition), + _counter_period(PAL50Period), _next_frame_is_sixty_hertz(false) { _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); @@ -45,7 +56,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) while(number_of_cycles--) { - _counter = (_counter + 1)%(312 * 64); // TODO: NTSC + _counter = (_counter + 1)%_counter_period; int h_counter =_counter & 63; if(!h_counter) @@ -60,13 +71,17 @@ void VideoOutput::run_for_cycles(int number_of_cycles) { _phase += 128; _frame_counter++; + + _v_sync_start_position = _next_frame_is_sixty_hertz ? PAL60VSyncStartPosition : PAL50VSyncStartPosition; + _v_sync_end_position = _next_frame_is_sixty_hertz ? PAL60VSyncEndPosition : PAL50VSyncEndPosition; + _counter_period = _next_frame_is_sixty_hertz ? PAL60Period : PAL50Period; } } State new_state = Blank; if( (h_counter >= 48 && h_counter <= 53) || - (_counter >= 256*64 && _counter <= 259*64)) new_state = Sync; + (_counter >= _v_sync_start_position && _counter <= _v_sync_end_position)) new_state = Sync; else if(h_counter >= 54 && h_counter <= 56) new_state = ColourBurst; else if(_counter < 224*64 && h_counter < 40) new_state = Pixels; @@ -150,7 +165,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: _is_graphics_mode = (control_byte & 4); - _is_sixty_hertz = !(control_byte & 2); + _next_frame_is_sixty_hertz = !(control_byte & 2); break; default: break; diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index e3cea4ee5..1a562347b 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -23,8 +23,9 @@ class VideoOutput { uint8_t *_ram; std::shared_ptr _crt; - // Counters + // Counters and limits int _counter, _frame_counter; + int _v_sync_start_position, _v_sync_end_position, _counter_period; // Output state enum State { @@ -44,7 +45,7 @@ class VideoOutput { } bool _is_graphics_mode; - bool _is_sixty_hertz; + bool _next_frame_is_sixty_hertz; bool _use_alternative_character_set; bool _use_double_height_characters; bool _blink_text; From f13d7ed6f45f0894fa5ead3a9004de7848ba5fa5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 20 Oct 2016 07:36:53 -0400 Subject: [PATCH 59/60] Added an extra safety rail. --- OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m index 309324f42..31d758c72 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m @@ -38,7 +38,7 @@ { dispatch_async(_serialDispatchQueue, ^{ NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate]; - if(_previousTimeInterval > DBL_EPSILON) + if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval) { NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval; double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError; From 5185927a43868013a5115d095f2f75dda61dee4f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 20 Oct 2016 07:38:48 -0400 Subject: [PATCH 60/60] Added formal admission of Oricness. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afd5d685c..a728a72b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ So its aims are: In terms of platforms, it currently contains emulations of the: * Acorn Electron; -* Atari 2600; and +* Atari 2600; +* Oric 1/Atmos; and * Commodore Vic-20 (and Commodore 1540/1). ## Single-click Loading