14static constexpr uint8_t B10_STD_BLOCK = 0x10;
15static constexpr uint8_t B11_TURBO_BLOCK = 0x11;
16static constexpr uint8_t B12_PURE_TONE = 0x12;
17static constexpr uint8_t B13_PULSE_SEQUENCE = 0x13;
18static constexpr uint8_t B15_DIRECT_REC = 0x15;
19static constexpr uint8_t B20_SILENCE_BLOCK = 0x20;
20static constexpr uint8_t B30_TEXT_DESCRIP = 0x30;
21static constexpr uint8_t B32_ARCHIVE_INFO = 0x32;
22static constexpr uint8_t B35_CUSTOM_INFO = 0x35;
23static constexpr uint8_t B4B_KCS_BLOCK = 0x4B;
26static constexpr uint8_t B21_GRP_START = 0x21;
27static constexpr uint8_t B22_GRP_END = 0x22;
28static constexpr uint8_t B5A_GLUE_BLOCK = 0x5A;
31static constexpr uint8_t B14_PURE_DATA = 0x14;
32static constexpr uint8_t B18_CSW_RECORDING = 0x18;
33static constexpr uint8_t B19_GEN_DATA = 0x19;
34static constexpr uint8_t B23_JUMP_BLOCK = 0x23;
35static constexpr uint8_t B24_LOOP_START = 0x24;
36static constexpr uint8_t B25_LOOP_END = 0x25;
37static constexpr uint8_t B26_CALL_SEQ = 0x26;
38static constexpr uint8_t B27_RET_SEQ = 0x27;
39static constexpr uint8_t B28_SELECT_BLOCK = 0x28;
40static constexpr uint8_t B2A_STOP_TAPE = 0x2A;
41static constexpr uint8_t B2B_SIGNAL_LEVEL = 0x2B;
42static constexpr uint8_t B31_MSG_BLOCK = 0x31;
43static constexpr uint8_t B33_HARDWARE_TYPE = 0x33;
46[[nodiscard]]
static std::string toHex(
int x)
57 static constexpr std::array<uint8_t, 8> TSX_HEADER = {
'Z',
'X',
'T',
'a',
'p',
'e',
'!', 0x1A };
58 if (memcmp(get<uint8_t>(TSX_HEADER.size()).data(), TSX_HEADER.data(), TSX_HEADER.size()) != 0) {
59 error(
"Invalid TSX header");
65 error(
"TSX version below 1.21");
68 while (!buf.empty()) {
70 auto blockId = get<uint8_t>();
73 processBlock10(get<Block10>());
76 processBlock11(get<Block11>());
79 processBlock12(get<Block12>());
81 case B13_PULSE_SEQUENCE:
82 processBlock13(get<Block13>());
85 processBlock15(get<Block15>());
87 case B20_SILENCE_BLOCK:
88 processBlock20(get<Block20>());
91 processBlock21(get<Block21>());
96 case B30_TEXT_DESCRIP:
97 processBlock30(get<Block30>());
99 case B32_ARCHIVE_INFO:
100 processBlock32(get<Block32>());
102 case B35_CUSTOM_INFO:
103 processBlock35(get<Block35>());
106 processBlock4B(get<Block4B>());
112 case B18_CSW_RECORDING:
119 case B28_SELECT_BLOCK:
121 case B2B_SIGNAL_LEVEL:
123 case B33_HARDWARE_TYPE:
127 error(
"Unsupported block: #" + toHex(blockId));
132[[nodiscard]]
static float tStates2samples(
float tStates)
137void TsxParser::writeSample(uint32_t tStates, int8_t value)
139 accumBytes += tStates2samples(
float(tStates));
140 output.insert(
end(output),
int(accumBytes), value);
141 accumBytes -= float(
int(accumBytes));
144void TsxParser::writePulse(uint32_t tStates)
146 writeSample(tStates, currentValue);
147 currentValue = int8_t(-currentValue);
150void TsxParser::writePulses(uint32_t count, uint32_t tStates)
152 repeat(count, [&] { writePulse(tStates); });
155void TsxParser::writeSilence(
int ms)
163void TsxParser::processBlock10(
const Block10& b)
174 b11.pauseMs = b.pauseMs;
180void TsxParser::processBlock11(
const Block11& b)
182 if ((b.len < 1) || (b.lastBits < 1) || (b.lastBits > 8)) {
183 error(
"Invalid block #11");
187 writePulses(b.pilotLen, b.pilot);
191 auto writeByte = [&](uint8_t d,
int nBits) {
192 for (
auto bit :
xrange(nBits)) {
193 if (d & (128 >> bit)) {
194 writePulses(2, b.one);
196 writePulses(2, b.zero);
200 uint32_t len = b.len;
201 auto data = get<uint8_t>(len);
202 for (
auto i :
xrange(len - 1)) {
203 writeByte(data[i], 8);
205 writeByte(data[len - 1], b.lastBits);
207 if (b.pauseMs != 0) writePulse(2000);
208 writeSilence(b.pauseMs);
212void TsxParser::processBlock12(
const Block12& b)
214 auto n = b.pulses & ~1;
215 writePulses(n, b.len);
219void TsxParser::processBlock13(
const Block13& b)
221 auto pulses = get<Endian::UA_L16>(b.num);
222 for (
auto i :
xrange(b.num)) {
223 writePulse(pulses[i]);
228void TsxParser::processBlock15(
const Block15& b)
230 if ((b.len < 1) || (b.lastBits < 1) || (b.lastBits > 8)) {
231 error(
"Invalid block #15");
233 auto samples = get<uint8_t>(b.len);
235 auto writeBit = [&](uint8_t& sample) {
236 writeSample(b.bitTstates, (sample & 128) ? 127 : -127);
239 auto writeByte = [&](uint8_t sample,
int nBits) {
240 repeat(nBits, [&]{ writeBit(sample); });
242 for (
auto i :
xrange(b.len - 1)) {
243 writeByte(samples[i], 8);
245 writeByte(samples[b.len - 1], b.lastBits);
247 writeSilence(b.pauseMs);
251void TsxParser::processBlock20(
const Block20& b)
253 writeSilence(b.pauseMs);
257void TsxParser::processBlock21(
const Block21& b)
263void TsxParser::processBlock30(
const Block30& b)
265 auto text = get<char>(b.len);
266 messages.emplace_back(text.data(), text.size());
270void TsxParser::processBlock32(
const Block32& b)
272 uint16_t len = b.blockLen;
273 auto extra =
sizeof(Block32) -
sizeof(uint16_t);
274 if (len < extra) error(
"Invalid block #32");
277 auto data = get<char>(len);
278 repeat(uint32_t(b.num), [&] {
279 if (data.size() < 2) error(
"Invalid block #32");
280 uint8_t textId = data[0];
281 uint8_t textLen = data[1];
282 data = data.subspan(2);
284 if (data.size() < textLen) error(
"Invalid block #32");
286 messages.emplace_back(data.data(), textLen);
288 data = data.subspan(textLen);
290 if (!data.empty()) error(
"Invalid block #32");
294void TsxParser::processBlock35(
const Block35& b)
300void TsxParser::processBlock4B(
const Block4B& b)
302 static constexpr std::array<uint8_t, 10> ASCII_HEADER = { 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA };
303 static constexpr std::array<uint8_t, 10> BINARY_HEADER = { 0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0 };
304 static constexpr std::array<uint8_t, 10> BASIC_HEADER = { 0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3 };
307 uint32_t len = b.blockLen;
308 auto extra =
sizeof(Block4B) -
sizeof(uint32_t);
309 if (len < extra) error(
"Invalid block #4B: invalid length");
311 auto data = get<uint8_t>(len);
314 if (!firstFileType && (len == 16)) {
316 if (memcmp(data.data(), ASCII_HEADER.data(), ASCII_HEADER.size()) == 0) {
317 firstFileType =
ASCII;
318 }
else if (memcmp(data.data(), BINARY_HEADER.data(), BINARY_HEADER.size()) == 0) {
320 }
else if (memcmp(data.data(), BASIC_HEADER.data(), BASIC_HEADER.size()) == 0) {
321 firstFileType =
BASIC;
328 uint32_t pulsePilot = b.pilot;
329 uint32_t pulseOne = b.bit1len;
330 uint32_t pulseZero = b.bit0len;
332 auto decodeBitCfg = [](uint8_t x) {
return x ? x : 16; };
333 auto numZeroPulses = decodeBitCfg(b.bitCfg >> 4);
334 auto numOnePulses = decodeBitCfg(b.bitCfg & 15);
336 auto numStartBits = (b.byteCfg & 0b11000000) >> 6;
337 bool startBitVal = (b.byteCfg & 0b00100000) >> 5;
338 auto numStopBits = (b.byteCfg & 0b00011000) >> 3;
339 bool stopBitVal = (b.byteCfg & 0b00000100) >> 2;
340 bool msb = (b.byteCfg & 0b00000001) >> 0;
341 if (b.byteCfg & 0b00000010) {
342 error(
"Invalid block #4B: unsupported byte-cfg: " + toHex(b.byteCfg));
346 writePulses(b.pulses, pulsePilot);
349 auto write_01 = [&](
bool bit) {
351 writePulses(numOnePulses, pulseOne);
353 writePulses(numZeroPulses, pulseZero);
356 auto write_N_01 = [&](
unsigned n,
bool bit) {
357 repeat(n, [&] { write_01(bit); });
359 for (
auto i :
xrange(len)) {
361 write_N_01(numStartBits, startBitVal);
364 for (
auto bit :
xrange(8)) {
365 auto mask = uint8_t(1) << (msb ? (7 - bit) : bit);
369 write_N_01(numStopBits, stopBitVal);
371 writeSilence(b.pauseMs);
375std::span<const T> TsxParser::get(
size_t count)
377 static_assert(
alignof(T) == 1,
"T must be unaligned");
379 auto bytes =
count *
sizeof(T);
380 if (buf.size() < bytes) error(
"Invalid TSX file, read beyond end of file");
382 const T* result = std::bit_cast<const T*>(buf.data());
383 buf = buf.subspan(bytes);
384 return {result,
count};
388const T& TsxParser::get()
390 const T*
t = get<T>(1).data();
394void TsxParser::error(std::string msg)
const
static constexpr unsigned TZX_Z80_FREQ
TsxParser(std::span< const uint8_t > file)
static constexpr unsigned OUTPUT_FREQUENCY
ALWAYS_INLINE uint16_t read_UA_B16(const void *p)
auto count(InputRange &&range, const T &value)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)
constexpr auto end(const zstring_view &x)