Skip to content

Commit ba807f0

Browse files
committed
v0.5.4-rc1 Fix Issues #4 and #3
1 parent 8b95b38 commit ba807f0

7 files changed

Lines changed: 97 additions & 71 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ enable_testing()
44

55
# Pass version to source code
66
add_definitions(
7-
-DAPP_VERSION="${PROJECT_VERSION}-rc0"
7+
-DAPP_VERSION="${PROJECT_VERSION}-rc1"
88
-DAPP_NAME="inspectrum ng"
99
)
1010

src/inputsource.cpp

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@
4343

4444
class ComplexF32SampleAdapter : public SampleAdapter {
4545
public:
46-
size_t sampleSize() override {
47-
return sizeof(std::complex<float>);
48-
}
46+
size_t sampleSize() override { return sizeof(std::complex<float>); }
47+
size_t sampleAlign() override { return alignof(float); }
4948

5049
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
5150
auto s = reinterpret_cast<const std::complex<float>*>(src);
@@ -55,9 +54,8 @@ class ComplexF32SampleAdapter : public SampleAdapter {
5554

5655
class ComplexF64SampleAdapter : public SampleAdapter {
5756
public:
58-
size_t sampleSize() override {
59-
return sizeof(std::complex<double>);
60-
}
57+
size_t sampleSize() override { return sizeof(std::complex<double>); }
58+
size_t sampleAlign() override { return alignof(double); }
6159

6260
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
6361
auto s = reinterpret_cast<const std::complex<double>*>(src);
@@ -71,9 +69,8 @@ class ComplexF64SampleAdapter : public SampleAdapter {
7169

7270
class ComplexS32SampleAdapter : public SampleAdapter {
7371
public:
74-
size_t sampleSize() override {
75-
return sizeof(std::complex<int32_t>);
76-
}
72+
size_t sampleSize() override { return sizeof(std::complex<int32_t>); }
73+
size_t sampleAlign() override { return alignof(int32_t); }
7774

7875
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
7976
auto s = reinterpret_cast<const std::complex<int32_t>*>(src);
@@ -88,9 +85,8 @@ class ComplexS32SampleAdapter : public SampleAdapter {
8885

8986
class ComplexS16SampleAdapter : public SampleAdapter {
9087
public:
91-
size_t sampleSize() override {
92-
return sizeof(std::complex<int16_t>);
93-
}
88+
size_t sampleSize() override { return sizeof(std::complex<int16_t>); }
89+
size_t sampleAlign() override { return alignof(int16_t); }
9490

9591
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
9692
auto s = reinterpret_cast<const std::complex<int16_t>*>(src);
@@ -105,9 +101,8 @@ class ComplexS16SampleAdapter : public SampleAdapter {
105101

106102
class ComplexS8SampleAdapter : public SampleAdapter {
107103
public:
108-
size_t sampleSize() override {
109-
return sizeof(std::complex<int8_t>);
110-
}
104+
size_t sampleSize() override { return sizeof(std::complex<int8_t>); }
105+
size_t sampleAlign() override { return alignof(int8_t); }
111106

112107
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
113108
auto s = reinterpret_cast<const std::complex<int8_t>*>(src);
@@ -122,9 +117,8 @@ class ComplexS8SampleAdapter : public SampleAdapter {
122117

123118
class ComplexU8SampleAdapter : public SampleAdapter {
124119
public:
125-
size_t sampleSize() override {
126-
return sizeof(std::complex<uint8_t>);
127-
}
120+
size_t sampleSize() override { return sizeof(std::complex<uint8_t>); }
121+
size_t sampleAlign() override { return alignof(uint8_t); }
128122

129123
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
130124
auto s = reinterpret_cast<const std::complex<uint8_t>*>(src);
@@ -139,9 +133,8 @@ class ComplexU8SampleAdapter : public SampleAdapter {
139133

140134
class RealF32SampleAdapter : public SampleAdapter {
141135
public:
142-
size_t sampleSize() override {
143-
return sizeof(float);
144-
}
136+
size_t sampleSize() override { return sizeof(float); }
137+
size_t sampleAlign() override { return alignof(float); }
145138

146139
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
147140
auto s = reinterpret_cast<const float*>(src);
@@ -155,9 +148,8 @@ class RealF32SampleAdapter : public SampleAdapter {
155148

156149
class RealF64SampleAdapter : public SampleAdapter {
157150
public:
158-
size_t sampleSize() override {
159-
return sizeof(double);
160-
}
151+
size_t sampleSize() override { return sizeof(double); }
152+
size_t sampleAlign() override { return alignof(double); }
161153

162154
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
163155
auto s = reinterpret_cast<const double*>(src);
@@ -171,9 +163,8 @@ class RealF64SampleAdapter : public SampleAdapter {
171163

172164
class RealS16SampleAdapter : public SampleAdapter {
173165
public:
174-
size_t sampleSize() override {
175-
return sizeof(int16_t);
176-
}
166+
size_t sampleSize() override { return sizeof(int16_t); }
167+
size_t sampleAlign() override { return alignof(int16_t); }
177168

178169
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
179170
auto s = reinterpret_cast<const int16_t*>(src);
@@ -188,9 +179,8 @@ class RealS16SampleAdapter : public SampleAdapter {
188179

189180
class RealS8SampleAdapter : public SampleAdapter {
190181
public:
191-
size_t sampleSize() override {
192-
return sizeof(int8_t);
193-
}
182+
size_t sampleSize() override { return sizeof(int8_t); }
183+
size_t sampleAlign() override { return alignof(int8_t); }
194184

195185
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
196186
auto s = reinterpret_cast<const int8_t*>(src);
@@ -205,9 +195,8 @@ class RealS8SampleAdapter : public SampleAdapter {
205195

206196
class RealU8SampleAdapter : public SampleAdapter {
207197
public:
208-
size_t sampleSize() override {
209-
return sizeof(uint8_t);
210-
}
198+
size_t sampleSize() override { return sizeof(uint8_t); }
199+
size_t sampleAlign() override { return alignof(uint8_t); }
211200

212201
void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) override {
213202
auto s = reinterpret_cast<const uint8_t*>(src);
@@ -358,11 +347,9 @@ QJsonObject InputSource::readMetaData(const QString &filename)
358347

359348
/*
360349
* Parse a RIFF/WAV header from memory-mapped data.
361-
* Supports IQ WAV files as produced by SDR++ and similar tools:
362-
* - PCM (codec 1): uint8, int16, int32 (2-channel IQ)
363-
* - IEEE float (codec 3): float32 (2-channel IQ)
350+
* Codecs: PCM (1, uint8/int16/int32), IEEE float (3, float32),
351+
* EXTENSIBLE (0xFFFE, PCM/float wrapped with a SubFormat GUID).
364352
* Returns the byte offset where sample data begins.
365-
* Sets sampleAdapter and sampleRate from the header.
366353
*/
367354
size_t InputSource::parseWavHeader(const uchar *data, size_t fileSize)
368355
{
@@ -406,6 +393,21 @@ size_t InputSource::parseWavHeader(const uchar *data, size_t fileSize)
406393
memcpy(&numChannels, data + pos + 10, 2);
407394
memcpy(&wavSampleRate, data + pos + 12, 4);
408395
memcpy(&bitsPerSample, data + pos + 22, 2);
396+
397+
/* WAVE_FORMAT_EXTENSIBLE: actual codec is the first 4
398+
* bytes of the 16-byte SubFormat GUID at fmt+24 */
399+
if (audioFormat == 0xFFFE) {
400+
if (chunkSize < 40)
401+
throw std::runtime_error("WAV EXTENSIBLE fmt chunk too small");
402+
uint32_t subFormat;
403+
memcpy(&subFormat, data + pos + 8 + 24, 4);
404+
if (subFormat == 1 || subFormat == 3)
405+
audioFormat = (uint16_t)subFormat;
406+
else
407+
throw std::runtime_error("WAV: unsupported EXTENSIBLE SubFormat "
408+
+ std::to_string(subFormat));
409+
}
410+
409411
foundFmt = true;
410412
}
411413
else if (memcmp(data + pos, "data", 4) == 0) {
@@ -576,22 +578,17 @@ void InputSource::openFile(const char *filename)
576578
throw;
577579
}
578580

579-
/*
580-
* Verify (data + dataOffset) alignment matches the adapter's
581-
* sample type. mmap'd files are page-aligned, but with a non-zero
582-
* dataOffset (e.g., WAV header) the resulting pointer can be
583-
* misaligned for complex<float> / int32_t reads. Misaligned typed
584-
* access is UB on strict-alignment archs (ARM/SPARC) and a
585-
* strict-aliasing violation everywhere. Refuse the file in that
586-
* case rather than crash silently downstream.
587-
*/
581+
/* Refuse files where dataOffset breaks scalar alignment for
582+
* typed reads. Shifting by whole samples can't fix it
583+
* (sampleSize is always a multiple of sampleAlign), and
584+
* shifting by less swaps I<->Q. */
588585
if (sampleAdapter) {
589-
const size_t sampleAlign = sampleAdapter->sampleSize();
586+
const size_t sampleAlign = sampleAdapter->sampleAlign();
590587
if (sampleAlign > 1 &&
591588
(reinterpret_cast<uintptr_t>(data + dataOffset) % sampleAlign) != 0) {
592589
file->unmap(data);
593590
throw std::runtime_error(
594-
"File data offset is not aligned to sample size; "
591+
"File data offset is not aligned to scalar size; "
595592
"cannot mmap-access. Convert the file or remove the header.");
596593
}
597594
}

src/inputsource.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
class SampleAdapter {
2929
public:
3030
virtual size_t sampleSize() = 0;
31+
/* alignof(scalar), not sizeof(sample): complex<T> only needs alignof(T) */
32+
virtual size_t sampleAlign() = 0;
3133
virtual void copyRange(const void* const src, size_t start, size_t length, std::complex<float>* const dest) = 0;
3234
virtual ~SampleAdapter() { };
3335
};

src/main.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020

2121
#include <QApplication>
2222
#include <QCommandLineParser>
23+
#include <QLocale>
2324
#include <QProgressDialog>
2425
#include <QStyleFactory>
2526

27+
#include <clocale>
28+
#include <locale>
29+
2630
#include "crashlog.h"
2731
#include "fft.h"
2832
#include "mainwindow.h"
@@ -31,6 +35,11 @@ int main(int argc, char *argv[])
3135
{
3236
QApplication a(argc, argv);
3337

38+
/* pin numeric I/O to '.' decimal separator on every locale layer */
39+
QLocale::setDefault(QLocale::c());
40+
std::setlocale(LC_NUMERIC, "C");
41+
std::locale::global(std::locale::classic());
42+
3443
CrashLog::init(APP_NAME, APP_VERSION);
3544
CrashLog::installCrashHandlers();
3645
CrashLog::log(CrashLog::LOG_INFO, "Application started");

src/mainwindow.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <QJsonDocument>
2828
#include <QJsonObject>
2929
#include <QJsonArray>
30+
#include <locale>
3031
#include <sstream>
3132

3233
#include "mainwindow.h"
@@ -139,6 +140,7 @@ void MainWindow::openFile(QString fileName)
139140
QString samplerate = match.captured(3);
140141

141142
std::stringstream ss(samplerate.toUtf8().constData());
143+
ss.imbue(std::locale::classic());
142144

143145
// Needs to be a double as the number is in scientific format
144146
double rate;

src/plotview.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
#include "frequencydemod.h"
2626
#include "phasedemod.h"
2727
#include <liquid/liquid.h>
28+
#include <iomanip>
2829
#include <iostream>
2930
#include <fstream>
31+
#include <locale>
32+
#include <sstream>
3033
#include <QtGlobal>
3134
#include <QApplication>
3235
#include <QClipboard>
@@ -48,6 +51,17 @@
4851
#include <QVBoxLayout>
4952
#include "plots.h"
5053

54+
namespace {
55+
/* locale-independent: snprintf("%f") follows LC_NUMERIC */
56+
static QString formatTimeTick(double tick)
57+
{
58+
std::ostringstream ss;
59+
ss.imbue(std::locale::classic());
60+
ss << std::fixed << std::setprecision(6) << tick;
61+
return QString::fromStdString(ss.str());
62+
}
63+
}
64+
5165
PlotView::PlotView(InputSource *input) : cursors(this), viewRange({0, 0})
5266
{
5367
mainSampleSource = input;
@@ -915,14 +929,13 @@ void PlotView::exportSpectrogramPng()
915929
int x = (int)((tickSample - viewRange.minimum) / spc);
916930
if (x < 0 || x >= imgWidth) continue;
917931

918-
char buf[128];
919-
snprintf(buf, sizeof(buf), "%.06f", tick);
932+
QString label = formatTimeTick(tick);
920933

921934
painter.drawLine(x, topY, x, topY + scaleHeight);
922-
painter.drawText(x + 2, topY + scaleHeight - 5, buf);
935+
painter.drawText(x + 2, topY + scaleHeight - 5, label);
923936

924937
painter.drawLine(x, botY, x, botY + scaleHeight);
925-
painter.drawText(x + 2, botY + scaleHeight - 5, buf);
938+
painter.drawText(x + 2, botY + scaleHeight - 5, label);
926939
}
927940

928941
/* minor ticks */
@@ -1730,16 +1743,15 @@ void PlotView::paintTimeScale(QPainter &painter, QRect &rect, range_t<size_t> sa
17301743
if (tickSample < sampleRange.minimum) { tick += durationPerTick; continue; }
17311744
int tickLine = sampleToColumn(tickSample - sampleRange.minimum);
17321745

1733-
char buf[128];
1734-
snprintf(buf, sizeof(buf), "%.06f", tick);
1746+
QString label = formatTimeTick(tick);
17351747

17361748
/* top */
17371749
painter.drawLine(tickLine, 0, tickLine, 30);
1738-
painter.drawText(tickLine + 2, 25, buf);
1750+
painter.drawText(tickLine + 2, 25, label);
17391751

17401752
/* bottom */
17411753
painter.drawLine(tickLine, botY, tickLine, botY + 30);
1742-
painter.drawText(tickLine + 2, botY + 25, buf);
1754+
painter.drawText(tickLine + 2, botY + 25, label);
17431755

17441756
tick += durationPerTick;
17451757
}

0 commit comments

Comments
 (0)