mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-08 10:07:25 +00:00
123 lines
3.0 KiB
C++
123 lines
3.0 KiB
C++
//
|
|
// SpectrumTAP.cpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 19/03/2021.
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#include "ZXSpectrumTAP.hpp"
|
|
|
|
using namespace Storage::Tape;
|
|
|
|
/*
|
|
The understanding of idiomatic Spectrum data encoding below
|
|
is taken from the TZX specifications at
|
|
https://worldofspectrum.net/features/TZXformat.html ;
|
|
specifics of the TAP encoding were gained from
|
|
https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format
|
|
*/
|
|
|
|
ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) :
|
|
file_(file_name)
|
|
{
|
|
// Check for a continuous series of blocks through to
|
|
// exactly file end.
|
|
//
|
|
// To consider: could also check those blocks of type 0
|
|
// and type ff for valid checksums?
|
|
while(true) {
|
|
const uint16_t block_length = file_.get16le();
|
|
if(file_.eof()) throw ErrorNotZXSpectrumTAP;
|
|
|
|
file_.seek(block_length, SEEK_CUR);
|
|
if(file_.tell() == file_.stats().st_size) break;
|
|
}
|
|
|
|
virtual_reset();
|
|
}
|
|
|
|
bool ZXSpectrumTAP::is_at_end() {
|
|
return file_.tell() == file_.stats().st_size && phase_ == Phase::Gap;
|
|
}
|
|
|
|
void ZXSpectrumTAP::virtual_reset() {
|
|
file_.seek(0, SEEK_SET);
|
|
read_next_block();
|
|
}
|
|
|
|
Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() {
|
|
// Adopt a general pattern of high then low.
|
|
Pulse pulse;
|
|
pulse.type = (distance_into_phase_ & 1) ? Pulse::Type::High : Pulse::Type::Low;
|
|
|
|
switch(phase_) {
|
|
default: break;
|
|
|
|
case Phase::PilotTone: {
|
|
// Output: pulses of length 2168;
|
|
// 8063 pulses if block type is 0, otherwise 3223;
|
|
// then a 667-length pulse followed by a 735-length pulse.
|
|
|
|
pulse.length = Time(271, 437'500); // i.e. 2168 / 3'500'000
|
|
++distance_into_phase_;
|
|
|
|
// Check whether in the last two.
|
|
if(distance_into_phase_ >= (block_type_ ? 8063 : 3223)) {
|
|
pulse.length = (distance_into_phase_ & 1) ? Time(667, 3'500'000) : Time(735, 3'500'000);
|
|
|
|
// Check whether this is the last one.
|
|
if(distance_into_phase_ == (block_type_ ? 8064 : 3224)) {
|
|
distance_into_phase_ = 0;
|
|
phase_ = Phase::Data;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case Phase::Data: {
|
|
// Output two pulses of length 855 for a 0; two of length 1710 for a 1,
|
|
// from MSB to LSB.
|
|
pulse.length = (data_byte_ & 0x80) ? Time(1710, 3'500'000) : Time(855, 3'500'000);
|
|
++distance_into_phase_;
|
|
|
|
if(!(distance_into_phase_ & 1)) {
|
|
data_byte_ <<= 1;
|
|
}
|
|
|
|
if(!(distance_into_phase_ & 15)) {
|
|
if((distance_into_phase_ >> 4) == block_length_) {
|
|
if(block_type_) {
|
|
distance_into_phase_ = 0;
|
|
phase_ = Phase::Gap;
|
|
} else {
|
|
read_next_block();
|
|
}
|
|
} else {
|
|
data_byte_ = file_.get8();
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case Phase::Gap:
|
|
Pulse gap;
|
|
gap.type = Pulse::Type::Zero;
|
|
gap.length = Time(1);
|
|
|
|
read_next_block();
|
|
return gap;
|
|
}
|
|
|
|
return pulse;
|
|
}
|
|
|
|
void ZXSpectrumTAP::read_next_block() {
|
|
if(file_.tell() == file_.stats().st_size) {
|
|
phase_ = Phase::Gap;
|
|
} else {
|
|
block_length_ = file_.get16le();
|
|
data_byte_ = block_type_ = file_.get8();
|
|
phase_ = Phase::PilotTone;
|
|
}
|
|
distance_into_phase_ = 0;
|
|
}
|