mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 06:35:04 +00:00
Introduces an attempted reimplementation of the MSX BIOS's two main tape reading entry points.
This commit is contained in:
parent
0b297f2972
commit
c4950574ea
@ -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 = "<group>"; };
|
||||
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = "<group>"; };
|
||||
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = "<group>"; };
|
||||
4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = "<group>"; };
|
||||
4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
|
||||
@ -817,6 +822,8 @@
|
||||
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
|
||||
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
|
||||
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
|
||||
4B651F9C1FF1B04100E18D9A /* Tape.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/MSX/Tape.cpp; sourceTree = "<group>"; };
|
||||
4B651F9D1FF1B04100E18D9A /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/MSX/Tape.hpp; sourceTree = "<group>"; };
|
||||
4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; };
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
|
||||
@ -1377,6 +1384,8 @@
|
||||
children = (
|
||||
4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */,
|
||||
4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */,
|
||||
4B651F9C1FF1B04100E18D9A /* Tape.cpp */,
|
||||
4B651F9D1FF1B04100E18D9A /* Tape.hpp */,
|
||||
);
|
||||
name = MSX;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
183
Storage/Tape/Parsers/MSX.cpp
Normal file
183
Storage/Tape/Parsers/MSX.cpp
Normal file
@ -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 <algorithm>
|
||||
|
||||
using namespace Storage::Tape::MSX;
|
||||
|
||||
std::unique_ptr<Parser::FileSpeed> 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<float>::max();
|
||||
float high = std::numeric_limits<float>::min();
|
||||
int samples = 0;
|
||||
while(!tape_player.get_tape()->is_at_end()) {
|
||||
float next_length = 0.0f;
|
||||
do {
|
||||
next_length += static_cast<float>(tape_player.get_cycles_until_next_event()) / static_cast<float>(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<float>::max();
|
||||
high = std::numeric_limits<float>::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<float>(tape_player.get_cycles_until_next_event()) / static_cast<float>(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<uint8_t>(total_length / (0.00001145f * 1.5f));
|
||||
|
||||
std::unique_ptr<FileSpeed> 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<float>(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<float>(tape_player.get_cycles_until_next_event()) / static_cast<float>(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<int>(
|
||||
static_cast<float>(speed.low_high_disrimination_duration) *
|
||||
0.0000173f *
|
||||
static_cast<float>(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<int>(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;
|
||||
}
|
56
Storage/Tape/Parsers/MSX.hpp
Normal file
56
Storage/Tape/Parsers/MSX.hpp
Normal file
@ -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 <memory>
|
||||
#include <cstdint>
|
||||
|
||||
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<FileSpeed> 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 */
|
Loading…
x
Reference in New Issue
Block a user