OXYGENHUB
◆ main openmsx / src / cassette / TsxParser.cc
10.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
// Based on code written by:
2
//   (2017) NataliaPC aka @ishwin74
3
//   Under GPL License
4
5
#include "TsxParser.hh"
6
7
#include "strCat.hh"
8
#include "xrange.hh"
9
10
#include <array>
11
#include <cstring>
12
13
// Current version supports these TZX 1.20 blocks plus the #4B TSX block:
14
static constexpr uint8_t B10_STD_BLOCK      = 0x10; // Standard Speed Block (Turbo normal)
15
static constexpr uint8_t B11_TURBO_BLOCK    = 0x11; // Turbo Speed Block (Turbo speed)
16
static constexpr uint8_t B12_PURE_TONE      = 0x12; // Pure Tone Block
17
static constexpr uint8_t B13_PULSE_SEQUENCE = 0x13; // Pulse sequence Block
18
static constexpr uint8_t B15_DIRECT_REC     = 0x15; // Direct recording Block
19
static constexpr uint8_t B20_SILENCE_BLOCK  = 0x20; // Silence Block
20
static constexpr uint8_t B30_TEXT_DESCRIP   = 0x30; // Text description Block
21
static constexpr uint8_t B32_ARCHIVE_INFO   = 0x32; // Archive info Block
22
static constexpr uint8_t B35_CUSTOM_INFO    = 0x35; // Custom info Block
23
static constexpr uint8_t B4B_KCS_BLOCK      = 0x4B; // KCS (Kansas City Standard) Block
24
25
// These blocks are detected, but ignored
26
static constexpr uint8_t B21_GRP_START      = 0x21; // Group start Block
27
static constexpr uint8_t B22_GRP_END        = 0x22; // Group end Block
28
static constexpr uint8_t B5A_GLUE_BLOCK     = 0x5A; // Glue Block
29
30
// These blocks are not supported at all (trigger an error when encountered)
31
static constexpr uint8_t B14_PURE_DATA      = 0x14; // Pure data Block
32
static constexpr uint8_t B18_CSW_RECORDING  = 0x18; // CSW recording block
33
static constexpr uint8_t B19_GEN_DATA       = 0x19; // Generalized data block
34
static constexpr uint8_t B23_JUMP_BLOCK     = 0x23; // Jump Block
35
static constexpr uint8_t B24_LOOP_START     = 0x24; // Loop start Block
36
static constexpr uint8_t B25_LOOP_END       = 0x25; // Loop end Block
37
static constexpr uint8_t B26_CALL_SEQ       = 0x26; // Call sequence Block
38
static constexpr uint8_t B27_RET_SEQ        = 0x27; // Return sequence Block
39
static constexpr uint8_t B28_SELECT_BLOCK   = 0x28; // Select block
40
static constexpr uint8_t B2A_STOP_TAPE      = 0x2A; // Stop the tape if in 48K mode
41
static constexpr uint8_t B2B_SIGNAL_LEVEL   = 0x2B; // Set signal level
42
static constexpr uint8_t B31_MSG_BLOCK      = 0x31; // Message Block
43
static constexpr uint8_t B33_HARDWARE_TYPE  = 0x33; // Hardware type
44
45
46
TsxParser::TsxParser(std::span<const uint8_t> file)
47
	: buf(file)
48
{
49
	// Check for a TZX header
50
	static constexpr std::array<uint8_t, 8> TSX_HEADER = { 'Z','X','T','a','p','e','!', 0x1A };
51
	if (memcmp(get<uint8_t>(TSX_HEADER.size()).data(), TSX_HEADER.data(), TSX_HEADER.size()) != 0) {
52
		error("Invalid TSX header");
53
	}
54
55
	// Check version >= 1.21
56
	if (auto version = Endian::read_UA_B16(get<uint8_t>(2).data());
57
	    version < 0x0115) {
58
		error("TSX version below 1.21");
59
	}
60
61
	while (!buf.empty()) {
62
		accumBytes = 0.f;
63
		auto blockId = get<uint8_t>();
64
		switch (blockId) {
65
		case B10_STD_BLOCK:
66
			processBlock10(get<Block10>());
67
			break;
68
		case B11_TURBO_BLOCK:
69
			processBlock11(get<Block11>());
70
			break;
71
		case B12_PURE_TONE:
72
			processBlock12(get<Block12>());
73
			break;
74
		case B13_PULSE_SEQUENCE:
75
			processBlock13(get<Block13>());
76
			break;
77
		case B15_DIRECT_REC:
78
			processBlock15(get<Block15>());
79
			break;
80
		case B20_SILENCE_BLOCK:
81
			processBlock20(get<Block20>());
82
			break;
83
		case B21_GRP_START:
84
			processBlock21(get<Block21>());
85
			break;
86
		case B22_GRP_END:
87
			// ignore (block has no data)
88
			break;
89
		case B30_TEXT_DESCRIP:
90
			processBlock30(get<Block30>());
91
			break;
92
		case B32_ARCHIVE_INFO:
93
			processBlock32(get<Block32>());
94
			break;
95
		case B35_CUSTOM_INFO:
96
			processBlock35(get<Block35>());
97
			break;
98
		case B4B_KCS_BLOCK:
99
			processBlock4B(get<Block4B>());
100
			break;
101
		case B5A_GLUE_BLOCK:
102
			get<uint8_t>(10); // skip (ignore) this block
103
			break;
104
		case B14_PURE_DATA:
105
		case B18_CSW_RECORDING:
106
		case B19_GEN_DATA:
107
		case B23_JUMP_BLOCK:
108
		case B24_LOOP_START:
109
		case B25_LOOP_END:
110
		case B26_CALL_SEQ:
111
		case B27_RET_SEQ:
112
		case B28_SELECT_BLOCK:
113
		case B2A_STOP_TAPE:
114
		case B2B_SIGNAL_LEVEL:
115
		case B31_MSG_BLOCK:
116
		case B33_HARDWARE_TYPE:
117
			// TODO not yet implemented, useful?
118
			[[fallthrough]];
119
		default:
120
			error(strCat("Unsupported block: #", hex_string<2>(blockId)));
121
		}
122
	}
123
}
124
125
[[nodiscard]] static float tStates2samples(float tStates)
126
{
127
	return tStates * TsxParser::OUTPUT_FREQUENCY / TsxParser::TZX_Z80_FREQ;
128
}
129
130
void TsxParser::writeSample(uint32_t tStates, int8_t value)
131
{
132
	accumBytes += tStates2samples(float(tStates));
133
	output.insert(end(output), int(accumBytes), value);
134
	accumBytes -= float(int(accumBytes));
135
}
136
137
void TsxParser::writePulse(uint32_t tStates)
138
{
139
	writeSample(tStates, currentValue);
140
	currentValue = int8_t(-currentValue);
141
}
142
143
void TsxParser::writePulses(uint32_t count, uint32_t tStates)
144
{
145
	repeat(count, [&] { writePulse(tStates); });
146
}
147
148
void TsxParser::writeSilence(int ms)
149
{
150
	if (!ms) return;
151
	output.insert(end(output), OUTPUT_FREQUENCY * ms / 1000, 0);
152
	currentValue = 127;
153
}
154
155
// Standard Speed Block
156
void TsxParser::processBlock10(const Block10& b)
157
{
158
	// delegate to 'block 11' but with some hardcoded values
159
	Block11 b11 = {};
160
	b11.pilot = 2168;
161
	b11.sync1 = 667;
162
	b11.sync2 = 735;
163
	b11.zero = 855;
164
	b11.one = 1710;
165
	b11.pilotLen = 3223;
166
	b11.lastBits = 8;
167
	b11.pauseMs = b.pauseMs;
168
	b11.len = b.len;
169
	processBlock11(b11);
170
}
171
172
// Turbo Speed Block
173
void TsxParser::processBlock11(const Block11& b)
174
{
175
	if ((b.len < 1) || (b.lastBits < 1) || (b.lastBits > 8)) {
176
		error("Invalid block #11");
177
	}
178
179
	currentValue = -127;
180
	writePulses(b.pilotLen, b.pilot);
181
	writePulse(b.sync1);
182
	writePulse(b.sync2);
183
184
	auto writeByte = [&](uint8_t d, int nBits) {
185
		for (auto bit : xrange(nBits)) {
186
			if (d & (128 >> bit)) {
187
				writePulses(2, b.one);
188
			} else {
189
				writePulses(2, b.zero);
190
			}
191
		}
192
	};
193
	uint32_t len = b.len;
194
	auto data = get<uint8_t>(len);
195
	for (auto i : xrange(len - 1)) {
196
		writeByte(data[i], 8);
197
	}
198
	writeByte(data[len - 1], b.lastBits);
199
200
	if (b.pauseMs != 0) writePulse(2000);
201
	writeSilence(b.pauseMs);
202
}
203
204
// Pure Tone Block
205
void TsxParser::processBlock12(const Block12& b)
206
{
207
	auto n = b.pulses & ~1; // round down to even
208
	writePulses(n, b.len);
209
}
210
211
// Pulse sequence Block
212
void TsxParser::processBlock13(const Block13& b)
213
{
214
	auto pulses = get<Endian::UA_L16>(b.num);
215
	for (auto i : xrange(b.num)) {
216
		writePulse(pulses[i]);
217
	}
218
}
219
220
// Direct Recording
221
void TsxParser::processBlock15(const Block15& b)
222
{
223
	if ((b.len < 1) || (b.lastBits < 1) || (b.lastBits > 8)) {
224
		error("Invalid block #15");
225
	}
226
	auto samples = get<uint8_t>(b.len);
227
228
	auto writeBit = [&](uint8_t& sample) {
229
		writeSample(b.bitTstates, (sample & 128) ? 127 : -127);
230
		sample <<= 1;
231
	};
232
	auto writeByte = [&](uint8_t sample, int nBits) {
233
		repeat(nBits, [&]{ writeBit(sample); });
234
	};
235
	for (auto i : xrange(b.len - 1)) {
236
		writeByte(samples[i], 8);
237
	}
238
	writeByte(samples[b.len - 1], b.lastBits);
239
240
	writeSilence(b.pauseMs);
241
}
242
243
// Silence Block
244
void TsxParser::processBlock20(const Block20& b)
245
{
246
	writeSilence(b.pauseMs);
247
}
248
249
// Group start Block
250
void TsxParser::processBlock21(const Block21& b)
251
{
252
	get<uint8_t>(b.len); // ignore group name
253
}
254
255
// Text description Block
256
void TsxParser::processBlock30(const Block30& b)
257
{
258
	auto text = get<char>(b.len);
259
	messages.emplace_back(text.data(), text.size());
260
}
261
262
// Archive info Block
263
void TsxParser::processBlock32(const Block32& b)
264
{
265
	uint16_t len = b.blockLen;
266
	auto extra = sizeof(Block32) - sizeof(uint16_t);
267
	if (len < extra) error("Invalid block #32");
268
	len -= extra; // number of available bytes in the data block
269
270
	auto data = get<char>(len);
271
	repeat(uint32_t(b.num), [&] {
272
		if (data.size() < 2) error("Invalid block #32");
273
		uint8_t textId  = data[0];
274
		uint8_t textLen = data[1];
275
		data = data.subspan(2);
276
277
		if (data.size() < textLen) error("Invalid block #32");
278
		if (textId == 0) {
279
			messages.emplace_back(data.data(), textLen);
280
		}
281
		data = data.subspan(textLen);
282
	});
283
	if (!data.empty()) error("Invalid block #32");
284
}
285
286
// Custom info Block
287
void TsxParser::processBlock35(const Block35& b)
288
{
289
	get<uint8_t>(b.len); // just skip (ignore) the data
290
}
291
292
// MSX KCS Block
293
void TsxParser::processBlock4B(const Block4B& b)
294
{
295
	static constexpr std::array<uint8_t, 10> ASCII_HEADER  = { 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA };
296
	static constexpr std::array<uint8_t, 10> BINARY_HEADER = { 0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0 };
297
	static constexpr std::array<uint8_t, 10> BASIC_HEADER  = { 0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3 };
298
299
	// get data block
300
	uint32_t len = b.blockLen;
301
	auto extra = sizeof(Block4B) - sizeof(uint32_t);
302
	if (len < extra) error("Invalid block #4B: invalid length");
303
	len -= extra; // number of available bytes in the data block
304
	auto data = get<uint8_t>(len);
305
306
	// determine file type
307
	if (!firstFileType && (len == 16)) {
308
		using enum FileType;
309
		if (memcmp(data.data(), ASCII_HEADER.data(), ASCII_HEADER.size()) == 0) {
310
			firstFileType = ASCII;
311
		} else if (memcmp(data.data(), BINARY_HEADER.data(), BINARY_HEADER.size()) == 0) {
312
			firstFileType = BINARY;
313
		} else if (memcmp(data.data(), BASIC_HEADER.data(), BASIC_HEADER.size()) == 0) {
314
			firstFileType = BASIC;
315
		} else {
316
			firstFileType = UNKNOWN;
317
		}
318
	}
319
320
	// read the block
321
	uint32_t pulsePilot = b.pilot;
322
	uint32_t pulseOne   = b.bit1len;
323
	uint32_t pulseZero  = b.bit0len;
324
325
	auto decodeBitCfg = [](uint8_t x) { return x ? x : 16; };
326
	auto numZeroPulses = decodeBitCfg(b.bitCfg >> 4); // 2 for MSX
327
	auto numOnePulses  = decodeBitCfg(b.bitCfg & 15); // 4 for MSX
328
329
	auto numStartBits = (b.byteCfg & 0b11000000) >> 6; // 1 for MSX
330
	bool startBitVal  = (b.byteCfg & 0b00100000) >> 5; // 0 for MSX
331
	auto numStopBits  = (b.byteCfg & 0b00011000) >> 3; // 2 for MSX
332
	bool stopBitVal   = (b.byteCfg & 0b00000100) >> 2; // 1 for MSX
333
	bool msb          = (b.byteCfg & 0b00000001) >> 0; // 0 (LSB first) for MSX
334
	if (b.byteCfg & 0b00000010) {
335
		error(strCat("Invalid block #4B: unsupported byte-cfg: ", hex_string<2>(b.byteCfg)));
336
	}
337
338
	// write a header signal
339
	writePulses(b.pulses, pulsePilot);
340
341
	// write KCS bytes
342
	auto write_01 = [&](bool bit) {
343
		if (bit) {
344
			writePulses(numOnePulses, pulseOne);
345
		} else {
346
			writePulses(numZeroPulses, pulseZero);
347
		}
348
	};
349
	auto write_N_01 = [&](unsigned n, bool bit) {
350
		repeat(n, [&] { write_01(bit); });
351
	};
352
	for (auto i : xrange(len)) {
353
		// start bit(s)
354
		write_N_01(numStartBits, startBitVal);
355
		// 8 data bits
356
		uint8_t d = data[i];
357
		for (auto bit : xrange(8)) {
358
			auto mask = uint8_t(1) << (msb ? (7 - bit) : bit);
359
			write_01(d & mask);
360
		}
361
		// stop bit(s)
362
		write_N_01(numStopBits, stopBitVal);
363
	}
364
	writeSilence(b.pauseMs);
365
}
366
367
template<typename T>
368
std::span<const T> TsxParser::get(size_t count)
369
{
370
	static_assert(alignof(T) == 1, "T must be unaligned");
371
372
	auto bytes = count * sizeof(T);
373
	if (buf.size() < bytes) error("Invalid TSX file, read beyond end of file");
374
375
	const T* result = std::bit_cast<const T*>(buf.data());
376
	buf = buf.subspan(bytes);
377
	return {result, count};
378
}
379
380
template<typename T>
381
const T& TsxParser::get()
382
{
383
	const T* t = get<T>(1).data();
384
	return *t;
385
}
386
387
void TsxParser::error(std::string msg) const
388
{
389
	throw msg;
390
}