OXYGENHUB
◆ main openmsx / Contrib / tsx / tsx2wav.cc
3.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
// Compile with:
2
//   g++ -std=c++23 -O3 -g -Wall -Wextra -I ../../src/utils/ tsx2wav.cc TsxParser.cc -o tsx2wav
3
4
#include "TsxParser.hh"
5
6
#include "endian.hh"
7
8
#include <array>
9
#include <bit>
10
#include <cstdint>
11
#include <cstdlib>
12
#include <fstream>
13
#include <iostream>
14
#include <span>
15
#include <vector>
16
17
int main(int argc, const char** argv)
18
{
19
	auto arg = std::span(argv, argc);
20
	if (arg.size() != 3) {
21
		std::cerr << "Usage: tsx2wav <tsx-input-file> <wav-output-file>\n";
22
		exit(1);
23
	}
24
25
	// -- Read tsx file --
26
	std::ifstream in(arg[1], std::ios::binary);
27
	if (!in.is_open()) {
28
		std::cerr << "Failed to open: " << arg[1] << '\n';
29
		exit(1);
30
	}
31
	std::vector<uint8_t> inBuf(std::istreambuf_iterator<char>{in},
32
	                           std::istreambuf_iterator<char>{});
33
34
	// -- Parse tsx file --
35
	std::vector<int8_t> wave;
36
	try {
37
		std::cout << "Converting TSX to WAV ...\n" << std::flush;
38
		TsxParser parser(inBuf);
39
		wave = parser.stealOutput();
40
41
		// print info
42
		double len = double(wave.size()) / TsxParser::OUTPUT_FREQUENCY;
43
		std::cout << "  WAV length = " << len << " seconds\n";
44
		if (auto type = parser.getFirstFileType()) {
45
			std::cout << "  first file type = " <<
46
				[&] {
47
					switch (*type) {
48
					using enum TsxParser::FileType;
49
					case ASCII:  return "ASCII";
50
					case BINARY: return "BINARY";
51
					case BASIC:  return "BASIC";
52
					default: return "UNKNOWN";
53
					}
54
				}() << '\n';
55
		}
56
		for (const auto& msg : parser.getMessages()) {
57
			std::cout << "  message: " << msg << '\n';
58
		}
59
	} catch (const std::string& err) {
60
		std::cerr << "Invalid TSX file: " << err << '\n';
61
		exit(1);
62
	}
63
64
	// convert 8-bit signed to unsigned
65
	for (int8_t& e : wave) {
66
		e = int8_t(e + 128);
67
	}
68
69
	// -- Write wav --
70
	std::ofstream out(arg[2], std::ios::binary);
71
	if (!out.is_open()) {
72
		std::cerr << "Failed to open: " << arg[2] << '\n';
73
		exit(1);
74
	}
75
76
	int channels = 1;
77
	int bits = 8;
78
	int frequency = TsxParser::OUTPUT_FREQUENCY;
79
	auto samples = int(wave.size());
80
81
	// write header
82
	struct WavHeader {
83
		std::array<char, 4> chunkID;     // + 0 'RIFF'
84
		Endian::L32 chunkSize;           // + 4 total size
85
		std::array<char, 4> format;      // + 8 'WAVE'
86
		std::array<char, 4> subChunk1ID; // +12 'fmt '
87
		Endian::L32 subChunk1Size;       // +16 = 16 (fixed)
88
		Endian::L16 audioFormat;         // +20 =  1 (fixed)
89
		Endian::L16 numChannels;         // +22
90
		Endian::L32 sampleRate;          // +24
91
		Endian::L32 byteRate;            // +28
92
		Endian::L16 blockAlign;          // +32
93
		Endian::L16 bitsPerSample;       // +34
94
		std::array<char, 4> subChunk2ID; // +36 'data'
95
		Endian::L32 subChunk2Size;       // +40
96
	} header;
97
	memcpy(header.chunkID.data(), "RIFF", header.chunkID.size());
98
	header.chunkSize     = (samples + 44 - 8 + 1) & ~1; // round up to even number
99
	memcpy(header.format.data(),      "WAVE", header.format.size());
100
	memcpy(header.subChunk1ID.data(), "fmt ", header.subChunk1ID.size());
101
	header.subChunk1Size = 16;
102
	header.audioFormat   = 1;
103
	header.numChannels   = channels;
104
	header.sampleRate    = frequency;
105
	header.byteRate      = (channels * frequency * bits) / 8;
106
	header.blockAlign    = (channels * bits) / 8;
107
	header.bitsPerSample = bits;
108
	memcpy(header.subChunk2ID.data(), "data", header.subChunk2ID.size());
109
	header.subChunk2Size = samples;
110
111
	out.write(std::bit_cast<char*>(&header), sizeof(header));
112
113
	// write data
114
	out.write(std::bit_cast<char*>(wave.data()), samples);
115
	// data chunk must have an even number of bytes
116
	if (samples & 1) {
117
		char pad = 0;
118
		out.write(&pad, 1);
119
	}
120
	if (out.bad()) {
121
		std::cerr << "Error writing: " << arg[2] << '\n';
122
		exit(1);
123
	}
124
}