// // MSX.cpp // Clock Signal // // Created by Thomas Harte on 26/12/2017. // Copyright © 2017 Thomas Harte. All rights reserved. // #include "MSX.hpp" #include using namespace Storage::Tape::MSX; std::unique_ptr 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::max(); float high = std::numeric_limits::min(); int samples = 0; while(!tape_player.get_tape()->is_at_end()) { float next_length = 0.0f; do { next_length += static_cast(tape_player.get_cycles_until_next_event()) / static_cast(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::max(); high = std::numeric_limits::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(tape_player.get_cycles_until_next_event()) / static_cast(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(total_length / (0.00001145f * 0.75f)); std::unique_ptr 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." */ float minimum_start_bit_duration = static_cast(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(tape_player.get_cycles_until_next_event()) / static_cast(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( 0.5f + static_cast(speed.low_high_disrimination_duration) * 0.0000173f * static_cast(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(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; }