2017-12-27 03:19:37 +00:00
|
|
|
//
|
|
|
|
// MSX.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 26/12/2017.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
2017-12-27 03:19:37 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2018-05-13 19:46:14 +00:00
|
|
|
"When 1,111 cycles have been found with less than 35 microseconds
|
|
|
|
variation in their lengths a header has been located."
|
2017-12-27 03:19:37 +00:00
|
|
|
*/
|
|
|
|
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 {
|
2020-05-10 03:00:39 +00:00
|
|
|
next_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
2017-12-27 03:19:37 +00:00
|
|
|
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()) {
|
2020-05-10 03:00:39 +00:00
|
|
|
total_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
2017-12-27 03:19:37 +00:00
|
|
|
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."
|
|
|
|
*/
|
2018-11-24 03:32:32 +00:00
|
|
|
total_length = total_length / 256.0f; // To get the average, in microseconds.
|
2017-12-27 03:19:37 +00:00
|
|
|
// To convert to the loop count format used by the MSX BIOS.
|
2020-05-10 03:00:39 +00:00
|
|
|
uint8_t int_result = uint8_t(total_length / (0.00001145f * 0.75f));
|
2017-12-27 03:19:37 +00:00
|
|
|
|
2019-12-22 04:52:04 +00:00
|
|
|
auto result = std::make_unique<FileSpeed>();
|
2017-12-29 03:48:04 +00:00
|
|
|
result->minimum_start_bit_duration = int_result;
|
|
|
|
result->low_high_disrimination_duration = (int_result * 3) >> 2;
|
2017-12-27 03:19:37 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2018-05-13 19:43:03 +00:00
|
|
|
@returns A value in the range 0-255 if a byte is found before the end of the tape;
|
2017-12-27 03:19:37 +00:00
|
|
|
-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."
|
2018-01-11 02:54:15 +00:00
|
|
|
|
|
|
|
... 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.
|
2017-12-27 03:19:37 +00:00
|
|
|
*/
|
2019-12-22 05:22:17 +00:00
|
|
|
const float minimum_start_bit_duration = float(speed.minimum_start_bit_duration) * 0.00001145f * 0.5f;
|
2018-01-11 02:54:15 +00:00
|
|
|
int input = 0;
|
2017-12-27 03:19:37 +00:00
|
|
|
while(!tape_player.get_tape()->is_at_end()) {
|
2018-01-11 02:54:15 +00:00
|
|
|
// Find next transition.
|
2017-12-29 03:48:04 +00:00
|
|
|
bool level = tape_player.get_input();
|
2018-01-11 02:54:15 +00:00
|
|
|
float duration = 0.0;
|
|
|
|
while(level == tape_player.get_input()) {
|
2020-05-10 03:00:39 +00:00
|
|
|
duration += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
2017-12-27 03:19:37 +00:00
|
|
|
tape_player.run_for_input_pulse();
|
|
|
|
}
|
|
|
|
|
2018-01-11 02:54:15 +00:00
|
|
|
input = (input << 1);
|
|
|
|
if(duration >= minimum_start_bit_duration) input |= 1;
|
|
|
|
if((input&3) == 3) break;
|
2017-12-27 03:19:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"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;
|
2020-05-10 03:00:39 +00:00
|
|
|
const int cycles_per_window = int(
|
2017-12-29 03:48:04 +00:00
|
|
|
0.5f +
|
2019-12-22 05:22:17 +00:00
|
|
|
float(speed.low_high_disrimination_duration) *
|
2017-12-27 03:19:37 +00:00
|
|
|
0.0000173f *
|
2019-12-22 05:22:17 +00:00
|
|
|
float(tape_player.get_input_clock_rate())
|
2017-12-27 03:19:37 +00:00
|
|
|
);
|
|
|
|
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) {
|
2019-12-22 05:22:17 +00:00
|
|
|
const int cycles_until_next_event = int(tape_player.get_cycles_until_next_event());
|
2017-12-27 03:19:37 +00:00
|
|
|
const int cycles_to_run_for = std::min(cycles_until_next_event, cycles_remaining);
|
2017-12-29 03:48:04 +00:00
|
|
|
|
2017-12-27 03:19:37 +00:00
|
|
|
cycles_remaining -= cycles_to_run_for;
|
|
|
|
tape_player.run_for(Cycles(cycles_to_run_for));
|
2017-12-29 03:48:04 +00:00
|
|
|
|
2017-12-27 03:19:37 +00:00
|
|
|
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;
|
|
|
|
}
|