diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 13aeec8d4..2609736e5 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -135,6 +135,7 @@ 4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */; }; 4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; 4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; + 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; @@ -223,6 +224,8 @@ 4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; }; 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; + 4B651F9E1FF1B04100E18D9A /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B651F9C1FF1B04100E18D9A /* Tape.cpp */; }; + 4B651F9F1FF1B2AE00E18D9A /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B651F9C1FF1B04100E18D9A /* Tape.cpp */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; @@ -647,6 +650,8 @@ 4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.hpp; sourceTree = ""; }; 4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = ""; }; 4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = ""; }; + 4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = ""; }; + 4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = ""; }; 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = ""; }; 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = ""; }; 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = ""; }; @@ -817,6 +822,8 @@ 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 = ""; }; 4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = ""; }; + 4B651F9C1FF1B04100E18D9A /* Tape.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/MSX/Tape.cpp; sourceTree = ""; }; + 4B651F9D1FF1B04100E18D9A /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/MSX/Tape.hpp; sourceTree = ""; }; 4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = ""; }; 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; @@ -1377,6 +1384,8 @@ children = ( 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */, 4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */, + 4B651F9C1FF1B04100E18D9A /* Tape.cpp */, + 4B651F9D1FF1B04100E18D9A /* Tape.hpp */, ); name = MSX; sourceTree = ""; @@ -1995,10 +2004,12 @@ children = ( 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */, 4B8805F21DCFD22A003085B1 /* Commodore.cpp */, + 4B0E61051FF34737002A9DBD /* MSX.cpp */, 4B8805F91DCFF807003085B1 /* Oric.cpp */, 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */, 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */, 4B8805F31DCFD22A003085B1 /* Commodore.hpp */, + 4B0E61061FF34737002A9DBD /* MSX.hpp */, 4B8805FA1DCFF807003085B1 /* Oric.hpp */, 4B4518A71F76004200926311 /* TapeParser.hpp */, 4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */, @@ -3339,6 +3350,7 @@ 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, 4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */, + 4B651F9F1FF1B2AE00E18D9A /* Tape.cpp in Sources */, 4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */, 4B055AD91FAE9B180060FFFF /* ZX8081.cpp in Sources */, 4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */, @@ -3358,6 +3370,7 @@ 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, + 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */, @@ -3413,6 +3426,7 @@ 4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4B86E25B1F8C628F006FAA45 /* Keyboard.cpp in Sources */, + 4B651F9E1FF1B04100E18D9A /* Tape.cpp in Sources */, 4B4518851F75E91A00926311 /* DiskController.cpp in Sources */, 4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */, 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */, diff --git a/Storage/Tape/Parsers/MSX.cpp b/Storage/Tape/Parsers/MSX.cpp new file mode 100644 index 000000000..fe0edd8cb --- /dev/null +++ b/Storage/Tape/Parsers/MSX.cpp @@ -0,0 +1,183 @@ +// +// MSX.cpp +// Clock Signal +// +// Created by Thomas Harte on 26/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "MSX.hpp" + +#include + +using namespace Storage::Tape::MSX; + +std::unique_ptr Parser::find_header(Storage::Tape::BinaryTapePlayer &tape_player) { + if(!tape_player.get_motor_control()) { + return nullptr; + } + + /* + "When 1,111 cycles have been found with less than 35 µs variation in + their lengths a header has been located." + */ + bool last_level = tape_player.get_input(); + float low = std::numeric_limits::max(); + float high = std::numeric_limits::min(); + int samples = 0; + while(!tape_player.get_tape()->is_at_end()) { + float next_length = 0.0f; + do { + next_length += static_cast(tape_player.get_cycles_until_next_event()) / static_cast(tape_player.get_input_clock_rate()); + tape_player.run_for_input_pulse(); + } while(last_level == tape_player.get_input()); + last_level = tape_player.get_input(); + low = std::min(low, next_length); + high = std::max(high, next_length); + samples++; + if(high - low > 0.000035f) { + low = std::numeric_limits::max(); + high = std::numeric_limits::min(); + samples = 0; + } + if(samples == 1111*2) break; // Cycles are read, not half-cycles. + } + + if(tape_player.get_tape()->is_at_end()) return nullptr; + + /* + "The next 256 cycles are then read (1B34H) and averaged to determine the cassette HI cycle length." + */ + float total_length = 0.0f; + samples = 512; + while(!tape_player.get_tape()->is_at_end()) { + total_length += static_cast(tape_player.get_cycles_until_next_event()) / static_cast(tape_player.get_input_clock_rate()); + if(tape_player.get_input() != last_level) { + samples--; + if(!samples) break; + last_level = tape_player.get_input(); + } + tape_player.run_for_input_pulse(); + } + + if(tape_player.get_tape()->is_at_end()) return nullptr; + + /* + This figure is multiplied by 1.5 and placed in LOWLIM where it defines the minimum acceptable length + of a 0 start bit. The HI cycle length is placed in WINWID and will be used to discriminate + between LO and HI cycles." + */ + total_length = total_length / 256.0f; // To get the average, in microseconds. + // To convert to the loop count format used by the MSX BIOS. + uint8_t int_result = static_cast(total_length / (0.00001145f * 1.5f)); + + std::unique_ptr result(new FileSpeed); + result->minimum_start_bit_duration = int_result + ((int_result + 1) >> 1); + result->low_high_disrimination_duration = int_result; + + return result; +} + +/*! + Attempts to read the next byte from the cassette, with data encoded + at the rate as defined by @c speed. + + Attempts exactly to duplicate the MSX's TAPIN function. + + @returns A value in the range 0–255 if a byte is found before the end of the tape; + -1 otherwise. +*/ +int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &tape_player) { + if(!tape_player.get_motor_control()) { + return -1; + } + + /* + "The cassette is first read continuously until a start bit is found. + This is done by locating a negative transition, measuring the following + cycle length (1B1FH) and comparing this to see if it is greater than LOWLIM." + */ + float minimum_start_bit_duration = static_cast(speed.minimum_start_bit_duration) * 0.00001145f; + while(!tape_player.get_tape()->is_at_end()) { + while(!tape_player.get_tape()->is_at_end() && !tape_player.get_input()) { + tape_player.run_for_input_pulse(); + } + + bool level = true; + float time_to_transition = 0.0f; + while(!tape_player.get_tape()->is_at_end()) { + time_to_transition += static_cast(tape_player.get_cycles_until_next_event()) / static_cast(tape_player.get_input_clock_rate()); + tape_player.run_for_input_pulse(); + if(level != tape_player.get_input()) + break; + } + + if(time_to_transition > minimum_start_bit_duration) { + break; + } + } + + /* + "Each of the eight data bits is then read by counting the number of transitions within + a fixed period of time (1B03H). If zero or one transitions are found it is a 0 bit, if two + or three are found it is a 1 bit. If more than three transitions are found the routine + terminates with Flag C as this is presumed to be a hardware error of some sort. " + */ + int result = 0; + const int cycles_per_window = static_cast( + static_cast(speed.low_high_disrimination_duration) * + 0.0000173f * + static_cast(tape_player.get_input_clock_rate()) * + 2.0f + ); + int bits_left = 8; + bool level = tape_player.get_input(); + while(!tape_player.get_tape()->is_at_end() && bits_left--) { + // Count number of transitions within cycles_per_window. + int transitions = 0; + int cycles_remaining = cycles_per_window; + while(!tape_player.get_tape()->is_at_end() && cycles_remaining) { + const int cycles_until_next_event = static_cast(tape_player.get_cycles_until_next_event()); + const int cycles_to_run_for = std::min(cycles_until_next_event, cycles_remaining); + cycles_remaining -= cycles_to_run_for; + tape_player.run_for(Cycles(cycles_to_run_for)); + if(level != tape_player.get_input()) { + level = tape_player.get_input(); + transitions++; + } + } + + if(tape_player.get_tape()->is_at_end()) return -1; + + int next_bit = 0; + switch(transitions) { + case 0: case 1: + next_bit = 0x00; + break; + case 2: case 3: + next_bit = 0x80; + break; + default: + return -1; + } + result = (result >> 1) | next_bit; + + /* + "After the value of each bit has been determined a further one or two transitions are read (1B23H) + to retain synchronization. With an odd transition count one more will be read, with an even + transition count two more." + */ + int required_transitions = 2 - (transitions&1); + while(!tape_player.get_tape()->is_at_end()) { + tape_player.run_for_input_pulse(); + if(level != tape_player.get_input()) { + level = tape_player.get_input(); + required_transitions--; + if(!required_transitions) break; + } + } + + if(tape_player.get_tape()->is_at_end()) return -1; + } + return result; +} diff --git a/Storage/Tape/Parsers/MSX.hpp b/Storage/Tape/Parsers/MSX.hpp new file mode 100644 index 000000000..5146fb6c5 --- /dev/null +++ b/Storage/Tape/Parsers/MSX.hpp @@ -0,0 +1,56 @@ +// +// MSX.hpp +// Clock Signal +// +// Created by Thomas Harte on 26/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Tape_Parsers_MSX_hpp +#define Storage_Tape_Parsers_MSX_hpp + +#include "../Tape.hpp" + +#include +#include + +namespace Storage { +namespace Tape { +namespace MSX { + +class Parser { + public: + struct FileSpeed { + uint8_t minimum_start_bit_duration; // i.e. LOWLIM + uint8_t low_high_disrimination_duration; // i.e. WINWID + }; + + /*! + Finds the next header from the tape, determining constants for the + speed of file expected ahead. + + Attempts exactly to duplicate the MSX's TAPION function. + + @param tape_player The tape player containing the tape to search. + @returns An instance of FileSpeed if a header is found before the end of the tape; + @c nullptr otherwise. + */ + static std::unique_ptr find_header(Storage::Tape::BinaryTapePlayer &tape_player); + + /*! + Attempts to read the next byte from the cassette, with data encoded + at the rate as defined by @c speed. + + Attempts exactly to duplicate the MSX's TAPIN function. + + @returns A value in the range 0–255 if a byte is found before the end of the tape; + -1 otherwise. + */ + static int get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &tape_player); +}; + +} +} +} + +#endif /* Storage_Tape_Parsers_MSX_hpp */