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