Files
CLK/Storage/Tape/Parsers/TandyCoCo.cpp
2026-05-16 13:41:57 -04:00

118 lines
2.8 KiB
C++

//
// TandyCoCo.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/05/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include "TandyCoCo.hpp"
using namespace Storage::Tape::TandyCoCo;
std::optional<WaveType> Parser::next(Storage::Tape::TapeSerialiser &serialiser) {
const auto pulse = serialiser.next_pulse();
const auto length = pulse.length.as<float>();
if(length < 0.75f / 4800.0f || length > 3.0f / 4800.0f) {
return std::nullopt;
}
return length >= 1.5f / 4800.0f ? WaveType::Long : WaveType::Short;
}
void Parser::find_leadin(Storage::Tape::TapeSerialiser &serialiser) {
// Lead-in tone is repeated instances of 0x55, so look for a four-length
// short, short, long, long pattern.
WaveType waves[4];
size_t wave_pointer = 0;
while(!serialiser.is_at_end()) {
const auto next_wave = next(serialiser);
if(!next_wave.has_value()) {
wave_pointer = 0;
continue;
}
if(wave_pointer < 3) {
waves[wave_pointer++] = *next_wave;
if(wave_pointer != 3) continue;
} else {
waves[0] = waves[1];
waves[1] = waves[2];
waves[2] = waves[3];
waves[3] = *next_wave;
}
if(
waves[0] == WaveType::Short &&
waves[1] == WaveType::Short &&
waves[2] == WaveType::Long &&
waves[3] == WaveType::Long
) {
return;
}
}
}
std::optional<bool> Parser::bit(Storage::Tape::TapeSerialiser &serialiser) {
const auto next1 = next(serialiser);
const auto next2 = next(serialiser);
if(!next1.has_value() || !next2.has_value()) {
return std::nullopt;
}
if(*next1 != *next2) {
return std::nullopt;
}
return *next1 == WaveType::Short;
}
std::optional<uint8_t> Parser::byte(Storage::Tape::TapeSerialiser &serialiser) {
uint8_t result = 0;
for(int c = 0; c < 8; c++) {
const auto b = bit(serialiser);
if(!b.has_value()) return std::nullopt;
result = (result >> 1) | (*b ? 0x80 : 0x00);
}
return result;
}
std::optional<Block> Parser::block(Storage::Tape::TapeSerialiser &serialiser) {
find_leadin(serialiser);
uint16_t sync = 0;
while(sync != 0x3c55) {
const auto b = bit(serialiser);
if(!b.has_value()) return std::nullopt;
sync = (sync >> 1) | (*b ? 0x8000 : 0x000);
}
const auto type = byte(serialiser);
const auto length = byte(serialiser);
if(!type || !length) return std::nullopt;
Block block;
block.type = *type;
block.data.reserve(*length);
for(int c = 0; c < *length; c++) {
const auto next_byte = byte(serialiser);
if(!next_byte.has_value()) return std::nullopt;
block.data.push_back(*next_byte);
}
const auto expected_checksum = static_cast<uint8_t>(
*length +
std::accumulate(block.data.begin(), block.data.end(), 0)
);
const auto checksum = byte(serialiser);
if(!checksum.has_value()) return std::nullopt;
block.checksum_error = uint8_t(*checksum - expected_checksum);
return block;
}