1
0
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:
Thomas Harte 2017-12-26 22:19:37 -05:00
parent 0b297f2972
commit c4950574ea
3 changed files with 253 additions and 0 deletions

View File

@ -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 */,

View 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 0255 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;
}

View 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 0255 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 */