1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-07 19:05:38 +00:00
CLK/Storage/Tape/Parsers/MSX.cpp

194 lines
6.2 KiB
C++
Raw Normal View History

//
// 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 * 0.75f));
std::unique_ptr<FileSpeed> result(new FileSpeed);
result->minimum_start_bit_duration = int_result;
result->low_high_disrimination_duration = (int_result * 3) >> 2;
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()) {
// Find a negative transition.
while(!tape_player.get_tape()->is_at_end() && tape_player.get_input()) {
tape_player.run_for_input_pulse();
}
// Measure the following cycle (i.e. two transitions).
bool level = tape_player.get_input();
float time_to_transition = 0.0f;
int transitions = 0;
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()) {
level = tape_player.get_input();
transitions++;
if(transitions == 2)
break;
}
}
// Check length against 'LOWLIM' (i.e. the minimum start bit duration).
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>(
0.5f +
static_cast<float>(speed.low_high_disrimination_duration) *
0.0000173f *
static_cast<float>(tape_player.get_input_clock_rate())
);
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;
}