Files
ggwave/src/ggwave.cpp
2022-06-05 18:19:25 +03:00

1929 lines
66 KiB
C++

#include "ggwave/ggwave.h"
#include "fft.h"
#include "reed-solomon/rs.hpp"
#include <cmath>
#include <ctime>
#include <cstdio>
//#include <random>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifdef ARDUINO
#define ggprintf(...)
#else
#define ggprintf(...) \
g_fptr && fprintf(g_fptr, __VA_ARGS__)
#endif
//
// C interface
//
namespace {
FILE * g_fptr = stderr;
std::vector<GGWave *> g_instances;
double linear_interp(double first_number, double second_number, double fraction) {
return (first_number + ((second_number - first_number)*fraction));
}
}
extern "C"
void ggwave_setLogFile(void * fptr) {
GGWave::setLogFile((FILE *) fptr);
}
extern "C"
ggwave_Parameters ggwave_getDefaultParameters(void) {
return GGWave::getDefaultParameters();
}
extern "C"
ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
static ggwave_Instance curId = 0;
if ((int) g_instances.size() < curId + 1) {
g_instances.resize(curId + 1, nullptr);
}
g_instances[curId] = new GGWave({
parameters.payloadLength,
parameters.sampleRateInp,
parameters.sampleRateOut,
parameters.sampleRate,
parameters.samplesPerFrame,
parameters.soundMarkerThreshold,
parameters.sampleFormatInp,
parameters.sampleFormatOut,
parameters.operatingMode});
return curId++;
}
extern "C"
void ggwave_free(ggwave_Instance instance) {
if ((int) g_instances.size() > instance && g_instances[instance]) {
delete (GGWave *) g_instances[instance];
g_instances[instance] = nullptr;
}
}
extern "C"
int ggwave_encode(
ggwave_Instance instance,
const void * payloadBuffer,
int payloadSize,
ggwave_ProtocolId protocolId,
int volume,
void * waveformBuffer,
int query) {
GGWave * ggWave = (GGWave *) g_instances[instance];
if (ggWave == nullptr) {
ggprintf("Invalid GGWave instance %d\n", instance);
return -1;
}
if (ggWave->init(payloadSize, (const char *) payloadBuffer, protocolId, volume) == false) {
ggprintf("Failed to initialize Tx transmission for GGWave instance %d\n", instance);
return -1;
}
if (query != 0) {
if (query == 1) {
return ggWave->encodeSize_bytes();
}
return ggWave->encodeSize_samples();
}
const int nBytes = ggWave->encode();
if (nBytes == 0) {
ggprintf("Failed to encode data - GGWave instance %d\n", instance);
return -1;
}
{
auto pSrc = (const char *) ggWave->txData();
auto pDst = ( char *) waveformBuffer;
std::copy(pSrc, pSrc + nBytes, pDst);
}
return nBytes;
}
extern "C"
int ggwave_decode(
ggwave_Instance instance,
const void * waveformBuffer,
int waveformSize,
void * payloadBuffer) {
GGWave * ggWave = (GGWave *) g_instances[instance];
if (ggWave->decode(waveformBuffer, waveformSize) == false) {
ggprintf("Failed to decode data - GGWave instance %d\n", instance);
return -1;
}
static thread_local GGWave::TxRxData data;
const auto dataLength = ggWave->rxTakeData(data);
if (dataLength == -1) {
// failed to decode message
return -1;
} else if (dataLength > 0) {
memcpy(payloadBuffer, data.data(), dataLength);
}
return dataLength;
}
extern "C"
int ggwave_ndecode(
ggwave_Instance instance,
const void * waveformBuffer,
int waveformSize,
void * payloadBuffer,
int payloadSize) {
GGWave * ggWave = (GGWave *) g_instances[instance];
if (ggWave->decode(waveformBuffer, waveformSize) == false) {
ggprintf("Failed to decode data - GGWave instance %d\n", instance);
return -1;
}
static thread_local GGWave::TxRxData data;
const auto dataLength = ggWave->rxTakeData(data);
if (dataLength == -1) {
// failed to decode message
return -1;
} else if (dataLength > payloadSize) {
// the payloadBuffer is not big enough to store the data
return -2;
} else if (dataLength > 0) {
memcpy(payloadBuffer, data.data(), dataLength);
}
return dataLength;
}
extern "C"
void ggwave_rxToggleProtocol(
ggwave_ProtocolId protocolId,
int state) {
GGWave::Protocols::rx().toggle(protocolId, state != 0);
}
extern "C"
void ggwave_txToggleProtocol(
ggwave_ProtocolId protocolId,
int state) {
GGWave::Protocols::tx().toggle(protocolId, state != 0);
}
//
// C++ implementation
//
namespace {
void FFT(float * f, int N, int * ip, float * w) {
rdft(N, 1, f, ip, w);
}
void FFT(const float * src, float * dst, int N, int * ip, float * w) {
std::copy(src, src + N, dst);
FFT(dst, N, ip, w);
}
inline void addAmplitudeSmooth(
const GGWave::Amplitude & src,
GGWave::Amplitude & dst,
float scalar, int startId, int finalId, int cycleMod, int nPerCycle) {
const int nTotal = nPerCycle*finalId;
const float frac = 0.15f;
const float ds = frac*nTotal;
const float ids = 1.0f/ds;
const int nBegin = frac*nTotal;
const int nEnd = (1.0f - frac)*nTotal;
for (int i = startId; i < finalId; i++) {
const float k = cycleMod*finalId + i;
if (k < nBegin) {
dst[i] += scalar*src[i]*(k*ids);
} else if (k > nEnd) {
dst[i] += scalar*src[i]*(((float)(nTotal) - k)*ids);
} else {
dst[i] += scalar*src[i];
}
}
}
int getECCBytesForLength(int len) {
return len < 4 ? 2 : std::max(4, 2*(len/5));
}
int bytesForSampleFormat(GGWave::SampleFormat sampleFormat) {
switch (sampleFormat) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: return 0; break;
case GGWAVE_SAMPLE_FORMAT_U8: return sizeof(uint8_t); break;
case GGWAVE_SAMPLE_FORMAT_I8: return sizeof(int8_t); break;
case GGWAVE_SAMPLE_FORMAT_U16: return sizeof(uint16_t); break;
case GGWAVE_SAMPLE_FORMAT_I16: return sizeof(int16_t); break;
case GGWAVE_SAMPLE_FORMAT_F32: return sizeof(float); break;
};
ggprintf("Invalid sample format: %d\n", (int) sampleFormat);
return 0;
}
}
struct GGWave::Rx {
bool receiving = false;
bool analyzing = false;
int nMarkersSuccess = 0;
int markerFreqStart = 0;
int recvDuration_frames = 0;
int framesLeftToAnalyze = 0;
int framesLeftToRecord = 0;
int framesToAnalyze = 0;
int framesToRecord = 0;
int samplesNeeded = 0;
std::vector<float> fftOut; // complex
std::vector<int> fftWorkI;
std::vector<float> fftWorkF;
bool hasNewRxData = false;
bool hasNewSpectrum = false;
bool hasNewAmplitude = false;
Spectrum spectrum;
Amplitude amplitude;
Amplitude amplitudeResampled;
TxRxData amplitudeTmp;
int dataLength = 0;
TxRxData data;
RxProtocol protocol;
RxProtocolId protocolId;
RxProtocols protocols;
// variable-length decoding
int historyId = 0;
Amplitude amplitudeAverage;
std::vector<Amplitude> amplitudeHistory;
RecordedData amplitudeRecorded;
// fixed-length decoding
int historyIdFixed = 0;
std::vector<std::vector<uint16_t>> spectrumHistoryFixed;
std::vector<uint8_t> detectedBins;
std::vector<uint8_t> detectedTones;
};
struct GGWave::Tx {
bool hasData = false;
float sendVolume = 0.1f;
int dataLength = 0;
int lastAmplitudeSize = 0;
std::vector<bool> dataBits;
std::vector<double> phaseOffsets;
std::vector<Amplitude> bit1Amplitude;
std::vector<Amplitude> bit0Amplitude;
TxRxData data;
TxProtocol protocol;
TxProtocols protocols;
Amplitude output;
Amplitude outputResampled;
TxRxData outputTmp;
AmplitudeI16 outputI16;
Tones tones;
};
//
// Protocols
//
void GGWave::Protocols::enableAll() {
for (auto & p : *this) {
if (p.name) {
p.enabled = true;
}
}
}
void GGWave::Protocols::disableAll() {
for (auto & p : *this) {
p.enabled = false;
}
}
void GGWave::Protocols::toggle(ProtocolId id, bool state) {
if (state) {
// enable protocol
(*this)[id].enabled = true;
} else {
// disable protocol
(*this)[id].enabled = false;
}
}
void GGWave::Protocols::only(ProtocolId id) {
disableAll();
(*this)[id].enabled = true;
}
void GGWave::Protocols::only(std::initializer_list<ProtocolId> ids) {
disableAll();
for (auto id : ids) {
(*this)[id].enabled = true;
}
}
GGWave::TxProtocols & GGWave::Protocols::tx() {
static TxProtocols protocols = kDefault();
return protocols;
}
GGWave::RxProtocols & GGWave::Protocols::rx() {
static RxProtocols protocols = kDefault();
return protocols;
}
//
// GGWave
//
GGWave::GGWave(const Parameters & parameters) :
m_sampleRateInp (parameters.sampleRateInp),
m_sampleRateOut (parameters.sampleRateOut),
m_sampleRate (parameters.sampleRate),
m_samplesPerFrame (parameters.samplesPerFrame),
m_isamplesPerFrame (1.0f/m_samplesPerFrame),
m_sampleSizeInp (bytesForSampleFormat(parameters.sampleFormatInp)),
m_sampleSizeOut (bytesForSampleFormat(parameters.sampleFormatOut)),
m_sampleFormatInp (parameters.sampleFormatInp),
m_sampleFormatOut (parameters.sampleFormatOut),
m_hzPerSample (m_sampleRate/m_samplesPerFrame),
m_ihzPerSample (1.0f/m_hzPerSample),
m_freqDelta_bin (1),
m_freqDelta_hz (2*m_hzPerSample),
m_nBitsInMarker (16),
m_nMarkerFrames (parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames),
m_encodedDataOffset (parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset),
m_soundMarkerThreshold(parameters.soundMarkerThreshold),
m_isFixedPayloadLength(parameters.payloadLength > 0),
m_payloadLength (parameters.payloadLength),
m_isRxEnabled (parameters.operatingMode & GGWAVE_OPERATING_MODE_RX),
m_isTxEnabled (parameters.operatingMode & GGWAVE_OPERATING_MODE_TX),
m_needResampling (m_sampleRateInp != m_sampleRate || m_sampleRateOut != m_sampleRate),
m_txOnlyTones (parameters.operatingMode & GGWAVE_OPERATING_MODE_TX_ONLY_TONES),
m_isDSSEnabled (parameters.operatingMode & GGWAVE_OPERATING_MODE_USE_DSS),
// common
m_dataEncoded (kMaxDataSize),
m_rx(nullptr),
m_tx(nullptr),
m_resampler(nullptr) {
if (m_sampleSizeInp == 0) {
ggprintf("Invalid or unsupported capture sample format: %d\n", (int) parameters.sampleFormatInp);
return;
}
if (m_sampleSizeOut == 0) {
ggprintf("Invalid or unsupported playback sample format: %d\n", (int) parameters.sampleFormatOut);
return;
}
if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {
ggprintf("Invalid samples per frame: %d, max: %d\n", parameters.samplesPerFrame, kMaxSamplesPerFrame);
return;
}
if (m_sampleRateInp < kSampleRateMin) {
ggprintf("Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin);
return;
}
if (m_sampleRateInp > kSampleRateMax) {
ggprintf("Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax);
return;
}
if (m_isRxEnabled) {
m_rx = new Rx();
m_rx->samplesNeeded = m_samplesPerFrame;
m_rx->fftOut.resize(2*m_samplesPerFrame);
m_rx->fftWorkI.resize(3 + sqrt(m_samplesPerFrame/2));
m_rx->fftWorkF.resize(m_samplesPerFrame/2);
m_rx->fftWorkI[0] = 0;
m_rx->spectrum.resize(m_samplesPerFrame);
m_rx->amplitude.resize(m_needResampling ? m_samplesPerFrame + 128 : m_samplesPerFrame); // small extra space because sometimes resampling needs a few more samples
m_rx->amplitudeResampled.resize(m_needResampling ? 8*m_samplesPerFrame : m_samplesPerFrame); // min input sampling rate is 0.125*m_sampleRate
m_rx->amplitudeTmp.resize(m_needResampling ? 8*m_samplesPerFrame*m_sampleSizeInp : m_samplesPerFrame*m_sampleSizeInp);
m_rx->data.resize(kMaxDataSize);
m_rx->protocol = {};
m_rx->protocolId = GGWAVE_PROTOCOL_COUNT;
m_rx->protocols = Protocols::rx();
if (m_isFixedPayloadLength) {
if (m_payloadLength > kMaxLengthFixed) {
ggprintf("Invalid payload legnth: %d, max: %d\n", m_payloadLength, kMaxLengthFixed);
return;
}
const int totalLength = m_payloadLength + getECCBytesForLength(m_payloadLength);
const int totalTxs = (totalLength + minBytesPerTx(m_rx->protocols) - 1)/minBytesPerTx(m_rx->protocols);
m_rx->spectrumHistoryFixed.resize(totalTxs*maxFramesPerTx(m_rx->protocols, false));
m_rx->detectedBins.resize(2*totalLength);
m_rx->detectedTones.resize(2*16*maxBytesPerTx(m_rx->protocols));
} else {
// variable payload length
m_rx->amplitudeRecorded.resize(kMaxRecordedFrames*m_samplesPerFrame);
m_rx->amplitudeAverage.resize(m_samplesPerFrame);
m_rx->amplitudeHistory.resize(kMaxSpectrumHistory);
}
for (auto & s : m_rx->amplitudeHistory) {
s.resize(m_samplesPerFrame);
}
for (auto & s : m_rx->spectrumHistoryFixed) {
s.resize(m_samplesPerFrame);
}
}
if (m_isTxEnabled) {
m_tx = new Tx();
m_tx->protocols = Protocols::tx();
const int maxDataBits = 2*16*maxBytesPerTx(m_tx->protocols);
m_tx->data.resize(kMaxDataSize);
m_tx->dataBits.resize(maxDataBits);
if (m_txOnlyTones == false) {
m_tx->phaseOffsets.resize(maxDataBits);
m_tx->bit0Amplitude.resize(maxDataBits);
for (auto & a : m_tx->bit0Amplitude) {
a.resize(m_samplesPerFrame);
}
m_tx->bit1Amplitude.resize(maxDataBits);
for (auto & a : m_tx->bit1Amplitude) {
a.resize(m_samplesPerFrame);
}
m_tx->output.resize(m_samplesPerFrame);
m_tx->outputResampled.resize(2*m_samplesPerFrame);
m_tx->outputTmp.resize(kMaxRecordedFrames*m_samplesPerFrame*m_sampleSizeOut);
m_tx->outputI16.resize(kMaxRecordedFrames*m_samplesPerFrame);
}
// TODO
// m_tx->tones;
}
// pre-allocate Reed-Solomon memory buffers
{
const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable;
if (m_isFixedPayloadLength == false) {
m_workRSLength.resize(RS::ReedSolomon::getWorkSize_bytes(1, m_encodedDataOffset - 1));
}
m_workRSData.resize(RS::ReedSolomon::getWorkSize_bytes(maxLength, getECCBytesForLength(maxLength)));
}
if (m_needResampling) {
m_resampler = new Resampler();
}
if (m_isDSSEnabled) {
// magic numbers used to XOR the Rx / Tx data
// this achieves more homogeneous distribution of the sound energy across the spectrum
m_dssMagic = {
0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f,
0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d,
0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f,
0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e,
};
}
init("", {}, 0);
}
GGWave::~GGWave() {
if (m_rx) {
delete m_rx;
m_rx = nullptr;
}
if (m_tx) {
delete m_tx;
m_tx = nullptr;
}
if (m_resampler) {
delete m_resampler;
m_resampler = nullptr;
}
}
void GGWave::setLogFile(FILE * fptr) {
g_fptr = fptr;
}
const GGWave::Parameters & GGWave::getDefaultParameters() {
static ggwave_Parameters result {
-1, // vaiable payload length
kDefaultSampleRate,
kDefaultSampleRate,
kDefaultSampleRate,
kDefaultSamplesPerFrame,
kDefaultSoundMarkerThreshold,
GGWAVE_SAMPLE_FORMAT_F32,
GGWAVE_SAMPLE_FORMAT_F32,
(ggwave_OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX),
};
return result;
}
bool GGWave::init(const char * text, TxProtocolId protocolId, const int volume) {
return init(strlen(text), text, protocolId, volume);
}
bool GGWave::init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume) {
if (dataSize < 0) {
ggprintf("Negative data size: %d\n", dataSize);
return false;
}
// Tx
if (m_isTxEnabled) {
const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable;
if (dataSize > maxLength) {
ggprintf("Truncating data from %d to %d bytes\n", dataSize, maxLength);
dataSize = maxLength;
}
if (volume < 0 || volume > 100) {
ggprintf("Invalid volume: %d\n", volume);
return false;
}
m_tx->hasData = false;
std::fill(m_tx->data.begin(), m_tx->data.end(), 0);
std::fill(m_dataEncoded.begin(), m_dataEncoded.end(), 0);
if (dataSize > 0) {
if (protocolId < 0 || protocolId >= m_tx->protocols.size()) {
ggprintf("Invalid protocol ID: %d\n", protocolId);
return false;
}
const auto & protocol = m_tx->protocols[protocolId];
if (protocol.enabled == false) {
ggprintf("Protocol %d is not enabled - make sure to enable it before creating the instance\n", protocolId);
return false;
}
if (protocol.extra == 2 && m_isFixedPayloadLength == false) {
ggprintf("Mono-tone protocols with variable length are not supported\n");
return false;
}
m_tx->protocol = protocol;
m_tx->dataLength = m_isFixedPayloadLength ? m_payloadLength : dataSize;
m_tx->sendVolume = ((double)(volume))/100.0f;
m_tx->data[0] = m_tx->dataLength;
for (int i = 0; i < m_tx->dataLength; ++i) {
m_tx->data[i + 1] = i < dataSize ? dataBuffer[i] : 0;
if (m_isDSSEnabled) {
m_tx->data[i + 1] ^= m_dssMagic[i%m_dssMagic.size()];
}
}
m_tx->hasData = true;
}
} else {
if (dataSize > 0) {
ggprintf("Tx is disabled - cannot transmit data with this GGWave instance\n");
}
}
// Rx
if (m_isRxEnabled) {
m_rx->receiving = false;
m_rx->analyzing = false;
m_rx->framesToAnalyze = 0;
m_rx->framesLeftToAnalyze = 0;
m_rx->framesToRecord = 0;
m_rx->framesLeftToRecord = 0;
std::fill(m_rx->spectrum.begin(), m_rx->spectrum.end(), 0);
std::fill(m_rx->amplitude.begin(), m_rx->amplitude.end(), 0);
for (auto & s : m_rx->amplitudeHistory) {
std::fill(s.begin(), s.end(), 0);
}
std::fill(m_rx->data.begin(), m_rx->data.end(), 0);
for (auto & s : m_rx->spectrumHistoryFixed) {
std::fill(s.begin(), s.end(), 0);
}
}
return true;
}
uint32_t GGWave::encodeSize_bytes() const {
return encodeSize_samples()*m_sampleSizeOut;
}
uint32_t GGWave::encodeSize_samples() const {
if (m_tx->hasData == false) {
return 0;
}
float factor = 1.0f;
int samplesPerFrameOut = m_samplesPerFrame;
if (m_sampleRateOut != m_sampleRate) {
factor = m_sampleRate/m_sampleRateOut;
// note : +1 extra sample in order to overestimate the buffer size
samplesPerFrameOut = m_resampler->resample(factor, m_samplesPerFrame, m_tx->output.data(), nullptr) + 1;
}
const int nECCBytesPerTx = getECCBytesForLength(m_tx->dataLength);
const int sendDataLength = m_tx->dataLength + m_encodedDataOffset;
const int totalBytes = sendDataLength + nECCBytesPerTx;
const int totalDataFrames = m_tx->protocol.extra*((totalBytes + m_tx->protocol.bytesPerTx - 1)/m_tx->protocol.bytesPerTx)*m_tx->protocol.framesPerTx;
return (
m_nMarkerFrames + totalDataFrames + m_nMarkerFrames
)*samplesPerFrameOut;
}
uint32_t GGWave::encode() {
if (m_isTxEnabled == false) {
ggprintf("Tx is disabled - cannot transmit data with this GGWave instance\n");
return 0;
}
if (m_resampler) {
m_resampler->reset();
}
const int nECCBytesPerTx = getECCBytesForLength(m_tx->dataLength);
const int sendDataLength = m_tx->dataLength + m_encodedDataOffset;
const int totalBytes = sendDataLength + nECCBytesPerTx;
const int totalDataFrames = m_tx->protocol.extra*((totalBytes + m_tx->protocol.bytesPerTx - 1)/m_tx->protocol.bytesPerTx)*m_tx->protocol.framesPerTx;
if (m_isFixedPayloadLength == false) {
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data());
rsLength.Encode(m_tx->data.data(), m_dataEncoded.data());
}
// first byte of m_tx->data contains the length of the payload, so we skip it:
RS::ReedSolomon rsData = RS::ReedSolomon(m_tx->dataLength, nECCBytesPerTx, m_workRSData.data());
rsData.Encode(m_tx->data.data() + 1, m_dataEncoded.data() + m_encodedDataOffset);
// generate tones
{
int frameId = 0;
bool hasData = m_tx->hasData;
m_tx->tones.clear();
while (hasData) {
m_tx->tones.push_back({});
if (frameId < m_nMarkerFrames) {
for (int i = 0; i < m_nBitsInMarker; ++i) {
m_tx->tones.back().push_back({});
m_tx->tones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
if (i%2 == 0) {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, i);
} else {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, i) + m_hzPerSample;
}
}
} else if (frameId < m_nMarkerFrames + totalDataFrames) {
int dataOffset = frameId - m_nMarkerFrames;
dataOffset /= m_tx->protocol.framesPerTx;
dataOffset *= m_tx->protocol.bytesPerTx;
std::fill(m_tx->dataBits.begin(), m_tx->dataBits.end(), 0);
for (int j = 0; j < m_tx->protocol.bytesPerTx; ++j) {
if (m_tx->protocol.extra == 1) {
{
uint8_t d = m_dataEncoded[dataOffset + j] & 15;
m_tx->dataBits[(2*j + 0)*16 + d] = 1;
}
{
uint8_t d = m_dataEncoded[dataOffset + j] & 240;
m_tx->dataBits[(2*j + 1)*16 + (d >> 4)] = 1;
}
} else {
if (dataOffset % m_tx->protocol.extra == 0) {
uint8_t d = m_dataEncoded[dataOffset/m_tx->protocol.extra + j] & 15;
m_tx->dataBits[(2*j + 0)*16 + d] = 1;
} else {
uint8_t d = m_dataEncoded[dataOffset/m_tx->protocol.extra + j] & 240;
m_tx->dataBits[(2*j + 0)*16 + (d >> 4)] = 1;
}
}
}
for (int k = 0; k < 2*m_tx->protocol.bytesPerTx*16; ++k) {
if (m_tx->dataBits[k] == 0) continue;
m_tx->tones.back().push_back({});
m_tx->tones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
if (k%2) {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, k/2) + m_hzPerSample;
} else {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, k/2);
}
}
} else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) {
for (int i = 0; i < m_nBitsInMarker; ++i) {
m_tx->tones.back().push_back({});
m_tx->tones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
if (i%2 == 0) {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, i) + m_hzPerSample;
} else {
m_tx->tones.back().back().freq_hz = bitFreq(m_tx->protocol, i);
}
}
} else {
hasData = false;
break;
}
++frameId;
}
if (m_txOnlyTones) {
m_tx->hasData = false;
return true;
}
}
// compute Tx data
{
for (int k = 0; k < (int) m_tx->phaseOffsets.size(); ++k) {
m_tx->phaseOffsets[k] = (M_PI*k)/(m_tx->protocol.nDataBitsPerTx());
}
// note : what is the purpose of this shuffle ? I forgot .. :(
//std::random_device rd;
//std::mt19937 g(rd());
//std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);
for (int k = 0; k < (int) m_tx->dataBits.size(); ++k) {
const double freq = bitFreq(m_tx->protocol, k);
const double phaseOffset = m_tx->phaseOffsets[k];
const double curHzPerSample = m_hzPerSample;
const double curIHzPerSample = 1.0/curHzPerSample;
for (int i = 0; i < m_samplesPerFrame; i++) {
const double curi = i;
m_tx->bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*(freq*curIHzPerSample) + phaseOffset);
}
for (int i = 0; i < m_samplesPerFrame; i++) {
const double curi = i;
m_tx->bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*((freq + m_hzPerSample*m_freqDelta_bin)*curIHzPerSample) + phaseOffset);
}
}
}
int frameId = 0;
uint32_t offset = 0;
const float factor = m_sampleRate/m_sampleRateOut;
while (m_tx->hasData) {
std::fill(m_tx->output.begin(), m_tx->output.end(), 0.0f);
uint16_t nFreq = 0;
if (frameId < m_nMarkerFrames) {
nFreq = m_nBitsInMarker;
for (int i = 0; i < m_nBitsInMarker; ++i) {
if (i%2 == 0) {
::addAmplitudeSmooth(m_tx->bit1Amplitude[i], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);
} else {
::addAmplitudeSmooth(m_tx->bit0Amplitude[i], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);
}
}
} else if (frameId < m_nMarkerFrames + totalDataFrames) {
int dataOffset = frameId - m_nMarkerFrames;
int cycleModMain = dataOffset%m_tx->protocol.framesPerTx;
dataOffset /= m_tx->protocol.framesPerTx;
dataOffset *= m_tx->protocol.bytesPerTx;
std::fill(m_tx->dataBits.begin(), m_tx->dataBits.end(), 0);
for (int j = 0; j < m_tx->protocol.bytesPerTx; ++j) {
if (m_tx->protocol.extra == 1) {
{
uint8_t d = m_dataEncoded[dataOffset + j] & 15;
m_tx->dataBits[(2*j + 0)*16 + d] = 1;
}
{
uint8_t d = m_dataEncoded[dataOffset + j] & 240;
m_tx->dataBits[(2*j + 1)*16 + (d >> 4)] = 1;
}
} else {
if (dataOffset % m_tx->protocol.extra == 0) {
uint8_t d = m_dataEncoded[dataOffset/m_tx->protocol.extra + j] & 15;
m_tx->dataBits[(2*j + 0)*16 + d] = 1;
} else {
uint8_t d = m_dataEncoded[dataOffset/m_tx->protocol.extra + j] & 240;
m_tx->dataBits[(2*j + 0)*16 + (d >> 4)] = 1;
}
}
}
for (int k = 0; k < 2*m_tx->protocol.bytesPerTx*16; ++k) {
if (m_tx->dataBits[k] == 0) continue;
++nFreq;
if (k%2) {
::addAmplitudeSmooth(m_tx->bit0Amplitude[k/2], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx->protocol.framesPerTx);
} else {
::addAmplitudeSmooth(m_tx->bit1Amplitude[k/2], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx->protocol.framesPerTx);
}
}
} else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) {
nFreq = m_nBitsInMarker;
const int fId = frameId - (m_nMarkerFrames + totalDataFrames);
for (int i = 0; i < m_nBitsInMarker; ++i) {
if (i%2 == 0) {
addAmplitudeSmooth(m_tx->bit0Amplitude[i], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);
} else {
addAmplitudeSmooth(m_tx->bit1Amplitude[i], m_tx->output, m_tx->sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);
}
}
} else {
m_tx->hasData = false;
break;
}
if (nFreq == 0) nFreq = 1;
const float scale = 1.0f/nFreq;
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_tx->output[i] *= scale;
}
int samplesPerFrameOut = m_samplesPerFrame;
if (m_sampleRateOut != m_sampleRate) {
samplesPerFrameOut = m_resampler->resample(factor, m_samplesPerFrame, m_tx->output.data(), m_tx->outputResampled.data());
} else {
m_tx->outputResampled = m_tx->output;
}
// default output is in 16-bit signed int so we always compute it
for (int i = 0; i < samplesPerFrameOut; ++i) {
m_tx->outputI16[offset + i] = 32768*m_tx->outputResampled[i];
}
// convert from 32-bit float
switch (m_sampleFormatOut) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
{
auto p = reinterpret_cast<uint8_t *>(m_tx->outputTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 128*(m_tx->outputResampled[i] + 1.0f);
}
} break;
case GGWAVE_SAMPLE_FORMAT_I8:
{
auto p = reinterpret_cast<uint8_t *>(m_tx->outputTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 128*m_tx->outputResampled[i];
}
} break;
case GGWAVE_SAMPLE_FORMAT_U16:
{
auto p = reinterpret_cast<uint16_t *>(m_tx->outputTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 32768*(m_tx->outputResampled[i] + 1.0f);
}
} break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
// skip because we already have the data in m_tx->outputI16
//auto p = reinterpret_cast<uint16_t *>(m_tx->outputTmp.data());
//for (int i = 0; i < samplesPerFrameOut; ++i) {
// p[offset + i] = 32768*m_tx->outputResampled[i];
//}
} break;
case GGWAVE_SAMPLE_FORMAT_F32:
{
auto p = reinterpret_cast<float *>(m_tx->outputTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = m_tx->outputResampled[i];
}
} break;
}
++frameId;
offset += samplesPerFrameOut;
}
m_tx->lastAmplitudeSize = offset;
// the encoded waveform can be accessed via the txData() method
// we return the size of the waveform in bytes:
return offset*m_sampleSizeOut;
}
const void * GGWave::txData() const {
if (m_tx == nullptr) {
ggprintf("Tx is disabled - cannot transmit data with this GGWave instance\n");
return nullptr;
}
switch (m_sampleFormatOut) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
return m_tx->outputI16.data();
} break;
case GGWAVE_SAMPLE_FORMAT_U8:
case GGWAVE_SAMPLE_FORMAT_I8:
case GGWAVE_SAMPLE_FORMAT_U16:
case GGWAVE_SAMPLE_FORMAT_F32:
{
return m_tx->outputTmp.data();
} break;
}
return nullptr;
}
bool GGWave::decode(const void * data, uint32_t nBytes) {
if (m_isRxEnabled == false) {
ggprintf("Rx is disabled - cannot receive data with this GGWave instance\n");
return false;
}
if (m_tx && m_tx->hasData) {
ggprintf("Cannot decode while transmitting\n");
return false;
}
auto dataBuffer = (uint8_t *) data;
const float factor = m_sampleRateInp/m_sampleRate;
while (true) {
// read capture data
uint32_t nBytesNeeded = m_rx->samplesNeeded*m_sampleSizeInp;
if (m_sampleRateInp != m_sampleRate) {
// note : predict 4 extra samples just to make sure we have enough data
nBytesNeeded = (m_resampler->resample(1.0f/factor, m_rx->samplesNeeded, m_rx->amplitudeResampled.data(), nullptr) + 4)*m_sampleSizeInp;
}
const uint32_t nBytesRecorded = std::min(nBytes, nBytesNeeded);
if (nBytesRecorded == 0) {
break;
}
switch (m_sampleFormatInp) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
case GGWAVE_SAMPLE_FORMAT_I8:
case GGWAVE_SAMPLE_FORMAT_U16:
case GGWAVE_SAMPLE_FORMAT_I16:
{
std::copy(dataBuffer, dataBuffer + nBytesRecorded, m_rx->amplitudeTmp.data());
} break;
case GGWAVE_SAMPLE_FORMAT_F32:
{
std::copy(dataBuffer, dataBuffer + nBytesRecorded, (uint8_t *) m_rx->amplitudeResampled.data());
} break;
}
dataBuffer += nBytesRecorded;
nBytes -= nBytesRecorded;
if (nBytesRecorded % m_sampleSizeInp != 0) {
ggprintf("Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n",
nBytesRecorded, m_sampleSizeInp);
m_rx->samplesNeeded = m_samplesPerFrame;
break;
}
// convert to 32-bit float
int nSamplesRecorded = nBytesRecorded/m_sampleSizeInp;
switch (m_sampleFormatInp) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
{
constexpr float scale = 1.0f/128;
auto p = reinterpret_cast<uint8_t *>(m_rx->amplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_rx->amplitudeResampled[i] = float(int16_t(*(p + i)) - 128)*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_I8:
{
constexpr float scale = 1.0f/128;
auto p = reinterpret_cast<int8_t *>(m_rx->amplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_rx->amplitudeResampled[i] = float(*(p + i))*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_U16:
{
constexpr float scale = 1.0f/32768;
auto p = reinterpret_cast<uint16_t *>(m_rx->amplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_rx->amplitudeResampled[i] = float(int32_t(*(p + i)) - 32768)*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
constexpr float scale = 1.0f/32768;
auto p = reinterpret_cast<int16_t *>(m_rx->amplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_rx->amplitudeResampled[i] = float(*(p + i))*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_F32: break;
}
uint32_t offset = m_samplesPerFrame - m_rx->samplesNeeded;
if (m_sampleRateInp != m_sampleRate) {
if (nSamplesRecorded <= 2*Resampler::kWidth) {
m_rx->samplesNeeded = m_samplesPerFrame;
break;
}
// reset resampler state every minute
if (!m_rx->receiving && m_resampler->nSamplesTotal() > 60.0f*factor*m_sampleRate) {
m_resampler->reset();
}
int nSamplesResampled = offset + m_resampler->resample(factor, nSamplesRecorded, m_rx->amplitudeResampled.data(), m_rx->amplitude.data() + offset);
nSamplesRecorded = nSamplesResampled;
} else {
for (int i = 0; i < nSamplesRecorded; ++i) {
m_rx->amplitude[offset + i] = m_rx->amplitudeResampled[i];
}
}
// we have enough bytes to do analysis
if (nSamplesRecorded >= m_samplesPerFrame) {
m_rx->hasNewAmplitude = true;
if (m_isFixedPayloadLength) {
decode_fixed();
} else {
decode_variable();
}
int nExtraSamples = nSamplesRecorded - m_samplesPerFrame;
for (int i = 0; i < nExtraSamples; ++i) {
m_rx->amplitude[i] = m_rx->amplitude[m_samplesPerFrame + i];
}
m_rx->samplesNeeded = m_samplesPerFrame - nExtraSamples;
} else {
m_rx->samplesNeeded = m_samplesPerFrame - nSamplesRecorded;
break;
}
}
return true;
}
//
// instance state
//
bool GGWave::isDSSEnabled() const { return m_isDSSEnabled; }
int GGWave::samplesPerFrame() const { return m_samplesPerFrame; }
int GGWave::sampleSizeInp() const { return m_sampleSizeInp; }
int GGWave::sampleSizeOut() const { return m_sampleSizeOut; }
float GGWave::sampleRateInp() const { return m_sampleRateInp; }
float GGWave::sampleRateOut() const { return m_sampleRateOut; }
GGWave::SampleFormat GGWave::sampleFormatInp() const { return m_sampleFormatInp; }
GGWave::SampleFormat GGWave::sampleFormatOut() const { return m_sampleFormatOut; }
//
// Tx
//
const GGWave::Tones & GGWave::txTones() const { return m_tx->tones; }
bool GGWave::txHasData() const { return m_tx && m_tx->hasData; }
bool GGWave::txTakeAmplitudeI16(AmplitudeI16 & dst) {
if (m_tx->lastAmplitudeSize == 0) return false;
if ((int) dst.size() < m_tx->lastAmplitudeSize) {
dst.resize(m_tx->lastAmplitudeSize);
}
std::copy(m_tx->outputI16.begin(), m_tx->outputI16.begin() + m_tx->lastAmplitudeSize, dst.begin());
m_tx->lastAmplitudeSize = 0;
return true;
}
const GGWave::RxProtocols & GGWave::txProtocols() const { return m_tx->protocols; }
//
// Rx
//
bool GGWave::rxReceiving() const { return m_rx->receiving; }
bool GGWave::rxAnalyzing() const { return m_rx->analyzing; }
int GGWave::rxSamplesNeeded() const { return m_rx->samplesNeeded; }
int GGWave::rxFramesToRecord() const { return m_rx->framesToRecord; }
int GGWave::rxFramesLeftToRecord() const { return m_rx->framesLeftToRecord; }
int GGWave::rxFramesToAnalyze() const { return m_rx->framesToAnalyze; }
int GGWave::rxFramesLeftToAnalyze() const { return m_rx->framesLeftToAnalyze; }
bool GGWave::rxStopReceiving() {
if (m_rx->receiving == false) {
return false;
}
m_rx->receiving = false;
return true;
}
GGWave::RxProtocols & GGWave::rxProtocols() { return m_rx->protocols; }
int GGWave::rxDataLength() const { return m_rx->dataLength; }
const GGWave::TxRxData & GGWave::rxData() const { return m_rx->data; }
const GGWave::RxProtocol & GGWave::rxProtocol() const { return m_rx->protocol; }
const GGWave::RxProtocolId & GGWave::rxProtocolId() const { return m_rx->protocolId; }
const GGWave::Spectrum & GGWave::rxSpectrum() const { return m_rx->spectrum; }
const GGWave::Amplitude & GGWave::rxAmplitude() const { return m_rx->amplitude; }
int GGWave::rxTakeData(TxRxData & dst) {
if (m_rx->dataLength == 0) return 0;
auto res = m_rx->dataLength;
m_rx->dataLength = 0;
if (res != -1) {
dst = m_rx->data;
}
return res;
}
bool GGWave::rxTakeSpectrum(Spectrum & dst) {
if (m_rx->hasNewSpectrum == false) return false;
m_rx->hasNewSpectrum = false;
dst = m_rx->spectrum;
return true;
}
bool GGWave::rxTakeAmplitude(Amplitude & dst) {
if (m_rx->hasNewAmplitude == false) return false;
m_rx->hasNewAmplitude = false;
dst = m_rx->amplitude;
return true;
}
bool GGWave::computeFFTR(const float * src, float * dst, int N) {
if (N != m_samplesPerFrame) {
ggprintf("computeFFTR: N (%d) must be equal to 'samplesPerFrame' %d\n", N, m_samplesPerFrame);
return false;
}
FFT(src, dst, N, m_rx->fftWorkI.data(), m_rx->fftWorkF.data());
return true;
}
//
// GGWave::Resampler
//
GGWave::Resampler::Resampler() :
m_sincTable(kWidth*kSamplesPerZeroCrossing),
m_delayBuffer(3*kWidth),
m_edgeSamples(kWidth),
m_samplesInp(2048) {
makeSinc();
reset();
}
void GGWave::Resampler::reset() {
m_state = {};
std::fill(m_edgeSamples.begin(), m_edgeSamples.end(), 0.0f);
std::fill(m_delayBuffer.begin(), m_delayBuffer.end(), 0.0f);
std::fill(m_samplesInp.begin(), m_samplesInp.end(), 0.0f);
}
int GGWave::Resampler::resample(
float factor,
int nSamples,
const float * samplesInp,
float * samplesOut) {
int idxInp = -1;
int idxOut = 0;
int notDone = 1;
float data_in = 0.0f;
float data_out = 0.0f;
double one_over_factor = 1.0;
auto stateSave = m_state;
m_state.nSamplesTotal += nSamples;
if (samplesOut) {
assert(nSamples > kWidth);
if ((int) m_samplesInp.size() < nSamples + kWidth) {
m_samplesInp.resize(nSamples + kWidth);
}
for (int i = 0; i < kWidth; ++i) {
m_samplesInp[i] = m_edgeSamples[i];
m_edgeSamples[i] = samplesInp[nSamples - kWidth + i];
}
for (int i = 0; i < nSamples; ++i) {
m_samplesInp[i + kWidth] = samplesInp[i];
}
samplesInp = m_samplesInp.data();
}
while (notDone) {
while (m_state.timeLast < m_state.timeInt) {
if (++idxInp >= nSamples) {
notDone = 0;
break;
} else {
data_in = samplesInp[idxInp];
}
//printf("xxxx idxInp = %d\n", idxInp);
if (samplesOut) newData(data_in);
m_state.timeLast += 1;
}
if (notDone == false) break;
double temp1 = 0.0;
int left_limit = m_state.timeNow - kWidth + 1; /* leftmost neighboring sample used for interp.*/
int right_limit = m_state.timeNow + kWidth; /* rightmost leftmost neighboring sample used for interp.*/
if (left_limit < 0) left_limit = 0;
if (right_limit > m_state.nSamplesTotal + kWidth) right_limit = m_state.nSamplesTotal + kWidth;
if (factor < 1.0) {
for (int j = left_limit; j < right_limit; j++) {
temp1 += getData(j - m_state.timeInt)*sinc(m_state.timeNow - (double) j);
}
data_out = temp1;
}
else {
one_over_factor = 1.0 / factor;
for (int j = left_limit; j < right_limit; j++) {
temp1 += getData(j - m_state.timeInt)*one_over_factor*sinc(one_over_factor*(m_state.timeNow - (double) j));
}
data_out = temp1;
}
if (samplesOut) {
//printf("inp = %d, l = %d, r = %d, n = %d, a = %d, b = %d\n", idxInp, left_limit, right_limit, m_state.nSamplesTotal, left_limit - m_state.timeInt, right_limit - m_state.timeInt - 1);
samplesOut[idxOut] = data_out;
}
++idxOut;
m_state.timeNow += factor;
m_state.timeLast = m_state.timeInt;
m_state.timeInt = m_state.timeNow;
while (m_state.timeLast < m_state.timeInt) {
if (++idxInp >= nSamples) {
notDone = 0;
break;
} else {
data_in = samplesInp[idxInp];
}
if (samplesOut) newData(data_in);
m_state.timeLast += 1;
}
//printf("last idxInp = %d, nSamples = %d\n", idxInp, nSamples);
}
if (samplesOut == nullptr) {
m_state = stateSave;
}
return idxOut;
}
float GGWave::Resampler::getData(int j) const {
return m_delayBuffer[(int) j + kWidth];
}
void GGWave::Resampler::newData(float data) {
for (int i = 0; i < kDelaySize - 5; i++) {
m_delayBuffer[i] = m_delayBuffer[i + 1];
}
m_delayBuffer[kDelaySize - 5] = data;
}
void GGWave::Resampler::makeSinc() {
double temp, win_freq, win;
win_freq = M_PI/kWidth/kSamplesPerZeroCrossing;
m_sincTable[0] = 1.0;
for (int i = 1; i < kWidth*kSamplesPerZeroCrossing; i++) {
temp = (double) i*M_PI/kSamplesPerZeroCrossing;
m_sincTable[i] = sin(temp)/temp;
win = 0.5 + 0.5*cos(win_freq*i);
m_sincTable[i] *= win;
}
}
double GGWave::Resampler::sinc(double x) const {
int low;
double temp, delta;
if (fabs(x) >= kWidth - 1) {
return 0.0;
} else {
temp = fabs(x)*(double) kSamplesPerZeroCrossing;
low = temp; /* these are interpolation steps */
delta = temp - low; /* and can be ommited if desired */
return linear_interp(m_sincTable[low], m_sincTable[low + 1], delta);
}
}
//
// Variable payload length
//
void GGWave::decode_variable() {
m_rx->amplitudeHistory[m_rx->historyId] = m_rx->amplitude;
if (++m_rx->historyId >= kMaxSpectrumHistory) {
m_rx->historyId = 0;
}
if (m_rx->historyId == 0 || m_rx->receiving) {
m_rx->hasNewSpectrum = true;
std::fill(m_rx->amplitudeAverage.begin(), m_rx->amplitudeAverage.end(), 0.0f);
for (auto & s : m_rx->amplitudeHistory) {
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->amplitudeAverage[i] += s[i];
}
}
float norm = 1.0f/kMaxSpectrumHistory;
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->amplitudeAverage[i] *= norm;
}
// calculate spectrum
FFT(m_rx->amplitudeAverage.data(), m_rx->fftOut.data(), m_samplesPerFrame, m_rx->fftWorkI.data(), m_rx->fftWorkF.data());
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->spectrum[i] = (m_rx->fftOut[2*i + 0]*m_rx->fftOut[2*i + 0] + m_rx->fftOut[2*i + 1]*m_rx->fftOut[2*i + 1]);
}
for (int i = 1; i < m_samplesPerFrame/2; ++i) {
m_rx->spectrum[i] += m_rx->spectrum[m_samplesPerFrame - i];
}
}
if (m_rx->framesLeftToRecord > 0) {
std::copy(m_rx->amplitude.begin(),
m_rx->amplitude.begin() + m_samplesPerFrame,
m_rx->amplitudeRecorded.data() + (m_rx->framesToRecord - m_rx->framesLeftToRecord)*m_samplesPerFrame);
if (--m_rx->framesLeftToRecord <= 0) {
m_rx->analyzing = true;
}
}
if (m_rx->analyzing) {
ggprintf("Analyzing captured data ..\n");
const int stepsPerFrame = 16;
const int step = m_samplesPerFrame/stepsPerFrame;
bool isValid = false;
for (int protocolId = 0; protocolId < (int) m_rx->protocols.size(); ++protocolId) {
const auto & protocol = m_rx->protocols[protocolId];
if (protocol.enabled == false) {
continue;
}
// skip Rx protocol if it is mono-tone
if (protocol.extra == 2) {
continue;
}
// skip Rx protocol if start frequency is different from detected one
if (protocol.freqStart != m_rx->markerFreqStart) {
continue;
}
std::fill(m_rx->spectrum.begin(), m_rx->spectrum.end(), 0.0f);
m_rx->framesToAnalyze = m_nMarkerFrames*stepsPerFrame;
m_rx->framesLeftToAnalyze = m_rx->framesToAnalyze;
// note : not sure if looping backwards here is more meaningful than looping forwards
for (int ii = m_nMarkerFrames*stepsPerFrame - 1; ii >= 0; --ii) {
bool knownLength = false;
int decodedLength = 0;
const int offsetStart = ii;
for (int itx = 0; itx < 1024; ++itx) {
int offsetTx = offsetStart + itx*protocol.framesPerTx*stepsPerFrame;
if (offsetTx >= m_rx->recvDuration_frames*stepsPerFrame || (itx + 1)*protocol.bytesPerTx >= (int) m_dataEncoded.size()) {
break;
}
std::copy(
m_rx->amplitudeRecorded.begin() + offsetTx*step,
m_rx->amplitudeRecorded.begin() + offsetTx*step + m_samplesPerFrame, m_rx->fftOut.data());
// note : should we skip the first and last frame here as they are amplitude-smoothed?
for (int k = 1; k < protocol.framesPerTx; ++k) {
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->fftOut[i] += m_rx->amplitudeRecorded[(offsetTx + k*stepsPerFrame)*step + i];
}
}
FFT(m_rx->fftOut.data(), m_samplesPerFrame, m_rx->fftWorkI.data(), m_rx->fftWorkF.data());
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->spectrum[i] = (m_rx->fftOut[2*i + 0]*m_rx->fftOut[2*i + 0] + m_rx->fftOut[2*i + 1]*m_rx->fftOut[2*i + 1]);
}
for (int i = 1; i < m_samplesPerFrame/2; ++i) {
m_rx->spectrum[i] += m_rx->spectrum[m_samplesPerFrame - i];
}
uint8_t curByte = 0;
for (int i = 0; i < 2*protocol.bytesPerTx; ++i) {
double freq = m_hzPerSample*protocol.freqStart;
int bin = round(freq*m_ihzPerSample) + 16*i;
int kmax = 0;
double amax = 0.0;
for (int k = 0; k < 16; ++k) {
if (m_rx->spectrum[bin + k] > amax) {
kmax = k;
amax = m_rx->spectrum[bin + k];
}
}
if (i%2) {
curByte += (kmax << 4);
m_dataEncoded[itx*protocol.bytesPerTx + i/2] = curByte;
curByte = 0;
} else {
curByte = kmax;
}
}
if (itx*protocol.bytesPerTx > m_encodedDataOffset && knownLength == false) {
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data());
if ((rsLength.Decode(m_dataEncoded.data(), m_rx->data.data()) == 0) && (m_rx->data[0] > 0 && m_rx->data[0] <= 140)) {
knownLength = true;
decodedLength = m_rx->data[0];
const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength);
const int nTotalFramesExpected = 2*m_nMarkerFrames + ((nTotalBytesExpected + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx;
if (m_rx->recvDuration_frames > nTotalFramesExpected ||
m_rx->recvDuration_frames < nTotalFramesExpected - 2*m_nMarkerFrames) {
knownLength = false;
break;
}
} else {
break;
}
}
{
const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength);
if (knownLength && itx*protocol.bytesPerTx > nTotalBytesExpected + 1) {
break;
}
}
}
if (knownLength) {
RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength), m_workRSData.data());
if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rx->data.data()) == 0) {
if (decodedLength > 0) {
if (m_isDSSEnabled) {
for (int i = 0; i < decodedLength; ++i) {
m_rx->data[i] = m_rx->data[i] ^ m_dssMagic[i%m_dssMagic.size()];
}
}
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", decodedLength, protocol.name, protocolId);
ggprintf("Received sound data successfully: '%s'\n", m_rx->data.data());
isValid = true;
m_rx->hasNewRxData = true;
m_rx->dataLength = decodedLength;
m_rx->protocol = protocol;
m_rx->protocolId = RxProtocolId(protocolId);
}
}
}
if (isValid) {
break;
}
--m_rx->framesLeftToAnalyze;
}
if (isValid) break;
}
m_rx->framesToRecord = 0;
if (isValid == false) {
ggprintf("Failed to capture sound data. Please try again (length = %d)\n", m_rx->data[0]);
m_rx->dataLength = -1;
m_rx->framesToRecord = -1;
}
m_rx->receiving = false;
m_rx->analyzing = false;
std::fill(m_rx->spectrum.begin(), m_rx->spectrum.end(), 0.0f);
m_rx->framesToAnalyze = 0;
m_rx->framesLeftToAnalyze = 0;
}
// check if receiving data
if (m_rx->receiving == false) {
bool isReceiving = false;
for (const auto & protocol : m_rx->protocols) {
if (protocol.enabled == false) {
continue;
}
int nDetectedMarkerBits = m_nBitsInMarker;
for (int i = 0; i < m_nBitsInMarker; ++i) {
double freq = bitFreq(protocol, i);
int bin = round(freq*m_ihzPerSample);
if (i%2 == 0) {
if (m_rx->spectrum[bin] <= m_soundMarkerThreshold*m_rx->spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits;
} else {
if (m_rx->spectrum[bin] >= m_soundMarkerThreshold*m_rx->spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits;
}
}
if (nDetectedMarkerBits == m_nBitsInMarker) {
m_rx->markerFreqStart = protocol.freqStart;
isReceiving = true;
break;
}
}
if (isReceiving) {
if (++m_rx->nMarkersSuccess >= 1) {
} else {
isReceiving = false;
}
} else {
m_rx->nMarkersSuccess = 0;
}
if (isReceiving) {
std::time_t timestamp = std::time(nullptr);
ggprintf("%sReceiving sound data ...\n", std::asctime(std::localtime(&timestamp)));
m_rx->receiving = true;
std::fill(m_rx->data.begin(), m_rx->data.end(), 0);
// max recieve duration
m_rx->recvDuration_frames =
2*m_nMarkerFrames +
maxFramesPerTx(m_rx->protocols, true)*(
(kMaxLengthVariable + ::getECCBytesForLength(kMaxLengthVariable))/minBytesPerTx(m_rx->protocols) + 1
);
m_rx->nMarkersSuccess = 0;
m_rx->framesToRecord = m_rx->recvDuration_frames;
m_rx->framesLeftToRecord = m_rx->recvDuration_frames;
}
} else {
bool isEnded = false;
for (const auto & protocol : m_rx->protocols) {
if (protocol.enabled == false) {
continue;
}
int nDetectedMarkerBits = m_nBitsInMarker;
for (int i = 0; i < m_nBitsInMarker; ++i) {
double freq = bitFreq(protocol, i);
int bin = round(freq*m_ihzPerSample);
if (i%2 == 0) {
if (m_rx->spectrum[bin] >= m_soundMarkerThreshold*m_rx->spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--;
} else {
if (m_rx->spectrum[bin] <= m_soundMarkerThreshold*m_rx->spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--;
}
}
if (nDetectedMarkerBits == m_nBitsInMarker) {
isEnded = true;
break;
}
}
if (isEnded) {
if (++m_rx->nMarkersSuccess >= 1) {
} else {
isEnded = false;
}
} else {
m_rx->nMarkersSuccess = 0;
}
if (isEnded && m_rx->framesToRecord > 1) {
std::time_t timestamp = std::time(nullptr);
m_rx->recvDuration_frames -= m_rx->framesLeftToRecord - 1;
ggprintf("%sReceived end marker. Frames left = %d, recorded = %d\n", std::asctime(std::localtime(&timestamp)), m_rx->framesLeftToRecord, m_rx->recvDuration_frames);
m_rx->nMarkersSuccess = 0;
m_rx->framesLeftToRecord = 1;
}
}
}
//
// Fixed payload length
void GGWave::decode_fixed() {
m_rx->hasNewSpectrum = true;
// calculate spectrum
FFT(m_rx->amplitude.data(), m_rx->fftOut.data(), m_samplesPerFrame, m_rx->fftWorkI.data(), m_rx->fftWorkF.data());
float amax = 0.0f;
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->spectrum[i] = (m_rx->fftOut[2*i + 0]*m_rx->fftOut[2*i + 0] + m_rx->fftOut[2*i + 1]*m_rx->fftOut[2*i + 1]);
}
for (int i = 1; i < m_samplesPerFrame/2; ++i) {
m_rx->spectrum[i] += m_rx->spectrum[m_samplesPerFrame - i];
amax = std::max(amax, m_rx->spectrum[i]);
}
// original, floating-point version
//m_rx->spectrumHistoryFixed[m_rx->historyIdFixed] = m_rx->spectrum;
// in theory, using uint8_t should work almost the same and save 4 times the memory, but for some reason
// the results are not as good as with the floating-point version
// float -> uint8_t
//amax = 255.0f/(amax == 0.0f ? 1.0f : amax);
//for (int i = 0; i < m_samplesPerFrame; ++i) {
// m_rx->spectrumHistoryFixed[m_rx->historyIdFixed][i] = std::min(255.0f, std::max(0.0f, (float) round(m_rx->spectrum[i]*amax)));
//}
// hence we opt for the uint16_t version, saving 2 times the memory and getting similar results as the floating-point version
// float -> uint16_t
amax = 65535.0f/(amax == 0.0f ? 1.0f : amax);
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->spectrumHistoryFixed[m_rx->historyIdFixed][i] = std::min(65535.0f, std::max(0.0f, (float) round(m_rx->spectrum[i]*amax)));
}
if (++m_rx->historyIdFixed >= (int) m_rx->spectrumHistoryFixed.size()) {
m_rx->historyIdFixed = 0;
}
bool isValid = false;
for (int protocolId = 0; protocolId < (int) m_rx->protocols.size(); ++protocolId) {
const auto & protocol = m_rx->protocols[protocolId];
if (protocol.enabled == false) {
continue;
}
const int binStart = protocol.freqStart;
const int binDelta = 16;
const int binOffset = protocol.extra == 1 ? binDelta : 0;
if (binStart > m_samplesPerFrame) {
continue;
}
const int totalLength = m_payloadLength + getECCBytesForLength(m_payloadLength);
const int totalTxs = protocol.extra*((totalLength + protocol.bytesPerTx - 1)/protocol.bytesPerTx);
int historyStartId = m_rx->historyIdFixed - totalTxs*protocol.framesPerTx;
if (historyStartId < 0) {
historyStartId += m_rx->spectrumHistoryFixed.size();
}
const int nTones = 2*protocol.bytesPerTx;
std::fill(m_rx->detectedBins.begin(), m_rx->detectedBins.end(), 0);
int txNeededTotal = 0;
int txDetectedTotal = 0;
bool detectedSignal = true;
for (int k = 0; k < totalTxs; ++k) {
if (k % protocol.extra == 0) {
std::fill(m_rx->detectedTones.begin(), m_rx->detectedTones.begin() + 16*nTones, 0);
}
for (int i = 0; i < protocol.framesPerTx; ++i) {
int historyId = historyStartId + k*protocol.framesPerTx + i;
if (historyId >= (int) m_rx->spectrumHistoryFixed.size()) {
historyId -= m_rx->spectrumHistoryFixed.size();
}
for (int j = 0; j < protocol.bytesPerTx; ++j) {
int f0bin = 0;
uint16_t f0max = m_rx->spectrumHistoryFixed[historyId][binStart + 2*j*binDelta];
for (int b = 0; b < 16; ++b) {
{
const auto & v = m_rx->spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + b];
if (f0max <= v) {
f0max = v;
f0bin = b;
}
}
}
int f1bin = 0;
if (protocol.extra == 1) {
uint16_t f1max = m_rx->spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset];
for (int b = 0; b < 16; ++b) {
const auto & v = m_rx->spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset + b];
if (f1max <= v) {
f1max = v;
f1bin = b;
}
}
} else {
f1bin = f0bin;
}
if ((k + 0)%protocol.extra == 0) m_rx->detectedTones[(2*j + 0)*16 + f0bin]++;
if ((k + 1)%protocol.extra == 0) m_rx->detectedTones[(2*j + 1)*16 + f1bin]++;
}
}
if (protocol.extra > 1 && (k % protocol.extra == 0)) continue;
int txNeeded = 0;
int txDetected = 0;
for (int j = 0; j < protocol.bytesPerTx; ++j) {
if ((k/protocol.extra)*protocol.bytesPerTx + j >= totalLength) break;
txNeeded += 2;
for (int b = 0; b < 16; ++b) {
if (m_rx->detectedTones[(2*j + 0)*16 + b] > protocol.framesPerTx/2) {
m_rx->detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 0] = b;
txDetected++;
}
if (m_rx->detectedTones[(2*j + 1)*16 + b] > protocol.framesPerTx/2) {
m_rx->detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 1] = b;
txDetected++;
}
}
}
txDetectedTotal += txDetected;
txNeededTotal += txNeeded;
}
if (txDetectedTotal < 0.75*txNeededTotal) {
detectedSignal = false;
}
if (detectedSignal) {
RS::ReedSolomon rsData(m_payloadLength, getECCBytesForLength(m_payloadLength), m_workRSData.data());
for (int j = 0; j < totalLength; ++j) {
m_dataEncoded[j] = (m_rx->detectedBins[2*j + 1] << 4) + m_rx->detectedBins[2*j + 0];
}
if (rsData.Decode(m_dataEncoded.data(), m_rx->data.data()) == 0) {
if (m_isDSSEnabled) {
for (int i = 0; i < m_payloadLength; ++i) {
m_rx->data[i] = m_rx->data[i] ^ m_dssMagic[i%m_dssMagic.size()];
}
}
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", m_payloadLength, protocol.name, protocolId);
ggprintf("Received sound data successfully: '%s'\n", m_rx->data.data());
isValid = true;
m_rx->hasNewRxData = true;
m_rx->dataLength = m_payloadLength;
m_rx->protocol = protocol;
m_rx->protocolId = RxProtocolId(protocolId);
}
}
if (isValid) {
break;
}
}
}
int GGWave::maxFramesPerTx(const Protocols & protocols, bool excludeMT) const {
int res = 0;
for (const auto & protocol : protocols) {
if (protocol.enabled == false) {
continue;
}
if (excludeMT && protocol.extra > 1) {
continue;
}
res = std::max(res, protocol.framesPerTx*protocol.extra);
}
return res;
}
int GGWave::minBytesPerTx(const Protocols & protocols) const {
int res = 1;
for (const auto & protocol : protocols) {
if (protocol.enabled == false) {
continue;
}
res = std::min(res, protocol.bytesPerTx);
}
return res;
}
int GGWave::maxBytesPerTx(const Protocols & protocols) const {
int res = 1;
for (const auto & protocol : protocols) {
if (protocol.enabled == false) {
continue;
}
res = std::max(res, protocol.bytesPerTx);
}
return res;
}
double GGWave::bitFreq(const Protocol & p, int bit) const {
return m_hzPerSample*p.freqStart + m_freqDelta_hz*bit;
}