mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 16:31:31 +00:00
def82cba49
Also reintroduces proper file type association for TSX on the Mac.
186 lines
6.0 KiB
C++
186 lines
6.0 KiB
C++
//
|
||
// 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 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."
|
||
|
||
... but I don't buy that, as it makes the process overly dependent on phase.
|
||
So I'm going to look for the next two consecutive pulses that are each big
|
||
enough to be half of a zero.
|
||
*/
|
||
const float minimum_start_bit_duration = static_cast<float>(speed.minimum_start_bit_duration) * 0.00001145f * 0.5f;
|
||
int input = 0;
|
||
while(!tape_player.get_tape()->is_at_end()) {
|
||
// Find next transition.
|
||
bool level = tape_player.get_input();
|
||
float duration = 0.0;
|
||
while(level == tape_player.get_input()) {
|
||
duration += 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();
|
||
}
|
||
|
||
input = (input << 1);
|
||
if(duration >= minimum_start_bit_duration) input |= 1;
|
||
if((input&3) == 3) 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;
|
||
}
|