forked from bubnikv/pathsim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
245 lines (227 loc) · 10.7 KB
/
Copy pathmain.cpp
File metadata and controls
245 lines (227 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#include "PathSimProcessor.h"
#include "PathSimParams.h"
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <vector>
#include <poll.h>
#include <unistd.h>
#include "cxxopts.h"
#include "AudioFile.h"
using namespace PathSim;
int main(int argc, char **argv)
{
try {
cxxopts::Options options(argv[0], "\
pathsim - HF ionospheric propagation simulator implementing a Watterson channel model\n\
Ported by Vojtech Bubnik OK1AK to a command line tool\n\
from a Windows GUI application by Moe Wheatley, AE4JY, 2000\n\
");
options
.positional_help("input_file output_file")
.show_positional_help();
options
.allow_unrecognised_options()
.add_options()
("help", "Print help")
("i,input_file", "Input audio file", cxxopts::value<std::string>())
("o,output_file", "Output audio file", cxxopts::value<std::string>())
// ->default_value("a.out")->implicit_value("b.def"), "BIN")
("positional",
"Positional arguments: these are the arguments that are entered "
"without an option", cxxopts::value<std::vector<std::string>>());
{
auto group = options.add_options("Propagation condition");
for (const PathSimParams ¶ms : default_params())
group(params.cmdline_param, params.title);
}
options.add_options("Propagation")
("snr", "Signal to Noise Ratio (SNR)", cxxopts::value<double>())
("spread", "Frequency spread of the 1st path [Hz]", cxxopts::value<double>())
("offset", "Frequency offset of the 1st path [Hz]", cxxopts::value<double>())
("delay2", "Delay of the 2nd path [Hz]", cxxopts::value<double>())
("spread2", "Frequency spread of the 2nd path [Hz]", cxxopts::value<double>())
("offset2", "Frequency offset of the 2nd path [Hz]", cxxopts::value<double>())
("delay3", "Delay of the 2nd path [Hz]", cxxopts::value<double>())
("spread3", "Frequency spread of the 3rd path [Hz]", cxxopts::value<double>())
("offset3", "Frequency offset of the 3rd path [Hz]", cxxopts::value<double>());
options.parse_positional({"input_file", "output_file", "positional"});
auto result = options.parse(argc, argv);
if (result.count("help")) {
std::cout << options.help({"", "Propagation condition", "Propagation"}) << std::endl;
exit(0);
}
if (argc > 1 ||
((result.count("input_file") != 1 || result.count("output_file") != 1) &&
result.arguments().size() != 2)) {
std::cerr << "pathsim: input / output not specified" << std::endl;
exit(-1);
}
std::string input_file = result.count("input_file") > 0 ? result["input_file"] .as<std::string>() : result.arguments().front().value();
std::string output_file = result.count("output_file") > 0 ? result["output_file"].as<std::string>() : result.arguments()[1].value();
PathSimParams params;
// Parse the propagation condition parameter sets.
for (const PathSimParams &p : default_params())
if (result.count(p.cmdline_param) > 0) {
if (params.cmdline_param.empty())
params = p;
else {
std::cerr << "pathsim: parameter " << p.cmdline_param << " overrides " << params.cmdline_param << std::endl;
std::cerr << "Use just one propagation condition parameter.";
return -1;
}
}
if (result.count("snr"))
params.noise = { true, result["snr"].as<double>() };
for (int i = 0; i < 3; ++ i) {
auto init_path = [i, ¶ms](){
if (int(params.paths.size()) <= i)
params.paths.push_back({});
};
std::string sidx;
if (i > 0)
sidx = std::to_string(i);
std::string delay = std::string("delay") + sidx;
if (result.count(delay)) {
init_path();
params.paths.back().delay = result[delay].as<double>();
}
std::string spread = std::string("spread") + sidx;
if (result.count(spread)) {
init_path();
params.paths.back().spread = result[spread].as<double>();
}
std::string offset = std::string("offset") + sidx;
if (result.count(offset)) {
init_path();
params.paths.back().offset = result[offset].as<double>();
}
}
if (input_file == "-" || result["output_file"].as<std::string>() == "-") {
// Streaming mode: raw signed 16-bit little-endian mono samples on
// stdin -> channel -> stdout, one BUF_SIZE block at a time with an
// explicit flush per block, so the tool can sit in a real-time
// audio pipe (e.g. a modem-under-test FIFO bridge). The final
// partial block is zero-padded through the channel and trimmed on
// output so byte counts match the input.
if (input_file != "-" || result["output_file"].as<std::string>() != "-") {
std::cerr << "pathsim: streaming mode requires BOTH input and output to be '-'" << std::endl;
return -1;
}
// The processor needs exact BUF_SIZE blocks, but waiting for a
// full block (256 ms at 8 kHz) adds intolerable latency in a
// real-time pipe: a modem handshake dies on half a second of
// round-trip delay. So: short reads accumulate; a full block is
// processed immediately; if the input goes idle (>50 ms) with a
// partial block pending, it is zero-padded through the channel
// and only the real samples are emitted — the pad is inaudible
// channel-time during what is already silence.
PathSimProcessor processor;
processor.init(params);
const int N = PathSimProcessor::BUF_SIZE;
std::vector<int16_t> pcm(N);
std::vector<double> buf(N);
size_t fill = 0;
bool eof = false;
auto emit = [&](size_t n) {
// Silence squelch: a half-duplex modem stream is mostly
// digital silence between bursts. Feeding that through the
// channel lets the AGC (m_SigRMS, ~50 s IIR) wind its gain up
// toward infinity, and the next real burst is destroyed. A
// channel has nothing to do to silence anyway — pass it
// through and hold the channel/AGC state for the next burst.
bool silent = true;
for (size_t i = 0; i < size_t(N) && silent; ++ i)
silent = (buf[i] > -1e-9 && buf[i] < 1e-9);
size_t out = n;
if (! silent) {
processor.process_buffer(buf.data());
// A processed block advanced the multipath/Doppler state
// by N samples, so N samples MUST be emitted: dropping the
// zero-pad tail of a partial block would shear the output
// timeline against the channel state on every flush and
// break OFDM sync under any multipath preset. The tail is
// genuine channel ring-out (the delayed path of the burst
// end) landing in what was silence anyway.
out = size_t(N);
}
for (size_t i = 0; i < out; ++ i) {
double v = buf[i] * 32767.;
pcm[i] = int16_t(v > 32767. ? 32767. : (v < -32768. ? -32768. : v));
}
fwrite(pcm.data(), sizeof(int16_t), out, stdout);
fflush(stdout);
fill = 0;
};
while (! eof) {
struct pollfd pfd = { 0 /* stdin */, POLLIN, 0 };
int pr = poll(&pfd, 1, 50 /* ms */);
if (pr < 0)
break;
if (pr == 0) {
// Input idle: flush any partial block through the channel.
if (fill > 0) {
size_t n = fill;
for (size_t i = fill; i < size_t(N); ++ i)
buf[i] = 0.;
emit(n);
}
continue;
}
// Byte-accurate read: an odd-length read must not byte-shift
// the sample stream, so a dangling byte is carried over.
static uint8_t carry[2];
static size_t carry_n = 0;
uint8_t *dst = reinterpret_cast<uint8_t *>(pcm.data());
if (carry_n)
dst[0] = carry[0];
ssize_t r = read(0, dst + carry_n,
(N - fill) * sizeof(int16_t) - carry_n);
if (r <= 0) {
eof = true;
} else {
size_t bytes = size_t(r) + carry_n;
size_t got = bytes / sizeof(int16_t);
carry_n = bytes % sizeof(int16_t);
if (carry_n)
carry[0] = dst[bytes - 1];
for (size_t i = 0; i < got; ++ i)
buf[fill + i] = double(pcm[i]) / 32768.;
fill += got;
}
if (fill == size_t(N))
emit(size_t(N));
}
if (fill > 0) {
size_t n = fill;
for (size_t i = fill; i < size_t(N); ++ i)
buf[i] = 0.;
emit(n);
}
return 0;
}
AudioFile<double> audio_file;
bool loaded = audio_file.load(input_file);
if (! loaded) {
std::cout << "failed loading input file: " << input_file << std::endl;
exit(1);
}
int len = audio_file.getNumSamplesPerChannel();
int nblocks = (len + PathSimProcessor::BUF_SIZE - 1) / PathSimProcessor::BUF_SIZE;
PathSimProcessor processor;
processor.init(params);
audio_file.samples.front().resize(PathSimProcessor::BUF_SIZE * nblocks, 0.);
for (int i = 0; i < nblocks; ++ i)
processor.process_buffer(audio_file.samples.front().data() + i * PathSimProcessor::BUF_SIZE);
audio_file.samples.front().resize(len);
for (int k = 1; k < audio_file.getNumChannels(); ++ k)
audio_file.samples[k] = audio_file.samples.front();
audio_file.save(result["output_file"].as<std::string>(), AudioFileFormat::Wave);
}
catch (const cxxopts::OptionException& e)
{
std::cout << "error parsing options: " << e.what() << std::endl;
exit(1);
}
return 0;
}