1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00
CLK/Storage/Tape/Formats/ZXSpectrumTAP.cpp

123 lines
3.0 KiB
C++
Raw Normal View History

//
// 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;
2021-03-20 03:29:09 +00:00
/*
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
2021-03-22 00:31:09 +00:00
// 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;
2021-03-22 00:31:09 +00:00
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);
2021-03-20 03:29:09 +00:00
read_next_block();
}
Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() {
2021-03-20 03:29:09 +00:00
// 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) {
2021-03-20 03:29:09 +00:00
phase_ = Phase::Gap;
} else {
block_length_ = file_.get16le();
data_byte_ = block_type_ = file_.get8();
phase_ = Phase::PilotTone;
}
distance_into_phase_ = 0;
}