ggwave : various improvements

- faster FFT implementation
- built-in Direct Sequence Spread option
- remove <map> dependency from implementation
- update arduino-rx example
This commit is contained in:
Georgi Gerganov
2022-06-04 15:41:23 +03:00
parent f4020f63f9
commit f5e08d921b
16 changed files with 959 additions and 254 deletions

View File

@@ -41,6 +41,7 @@ EMSCRIPTEN_BINDINGS(ggwave) {
.value("GGWAVE_OPERATING_MODE_TX", GGWAVE_OPERATING_MODE_TX)
.value("GGWAVE_OPERATING_MODE_RX_AND_TX", (ggwave_OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX))
.value("GGWAVE_OPERATING_MODE_TX_ONLY_TONES", GGWAVE_OPERATING_MODE_TX_ONLY_TONES)
.value("GGWAVE_OPERATING_MODE_TX_USE_DSS", GGWAVE_OPERATING_MODE_USE_DSS)
;
emscripten::class_<ggwave_Parameters>("Parameters")

File diff suppressed because one or more lines are too long

View File

@@ -34,7 +34,8 @@ cdef extern from "ggwave.h" nogil:
GGWAVE_OPERATING_MODE_RX,
GGWAVE_OPERATING_MODE_TX,
GGWAVE_OPERATING_MODE_RX_AND_TX,
GGWAVE_OPERATING_MODE_TX_ONLY_TONES
GGWAVE_OPERATING_MODE_TX_ONLY_TONES,
GGWAVE_OPERATING_MODE_USE_DSS
ctypedef struct ggwave_Parameters:
int payloadLength

View File

@@ -20,15 +20,6 @@
namespace {
// Direct-sequence spread magic numbers
// Used to xor the actual payload
const std::array<uint8_t, 64> kDSSMagic = {
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,
};
std::string g_defaultCaptureDeviceName = "";
SDL_AudioDeviceID g_devIdInp = 0;
@@ -99,7 +90,8 @@ bool GGWave_init(
const int playbackId,
const int captureId,
const int payloadLength,
const float sampleRateOffset) {
const float sampleRateOffset,
const bool useDSS) {
if (g_devIdInp && g_devIdOut) {
return false;
@@ -235,6 +227,9 @@ bool GGWave_init(
if (reinit) {
if (g_ggWave) delete g_ggWave;
ggwave_OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;
if (useDSS) mode = ggwave_OperatingMode(mode | GGWAVE_OPERATING_MODE_USE_DSS);
g_ggWave = new GGWave({
payloadLength,
(float) g_obtainedSpecInp.freq,
@@ -244,7 +239,7 @@ bool GGWave_init(
GGWave::kDefaultSoundMarkerThreshold,
sampleFormatInp,
sampleFormatOut,
GGWAVE_OPERATING_MODE_RX_AND_TX,
mode,
});
}
@@ -278,9 +273,6 @@ bool GGWave_mainLoop() {
GGWave::TxRxData rxData;
int n = g_ggWave->takeRxData(rxData);
if (n > 0) {
for (int i = 0; i < n; i++) {
rxData[i] ^= kDSSMagic[i%kDSSMagic.size()];
}
std::time_t timestamp = std::time(nullptr);
std::string tstr = std::asctime(std::localtime(&timestamp));
tstr.back() = 0;
@@ -359,7 +351,7 @@ int main(int argc, char** argv) {
bool isInitialized = false;
g_doInit = [&]() {
if (GGWave_init(0, captureId, payloadLength, 0) == false) {
if (GGWave_init(0, captureId, payloadLength, 0, true) == false) {
fprintf(stderr, "Failed to initialize GGWave\n");
return false;
}

View File

@@ -73,13 +73,6 @@
const kPayloadLength = 16;
var kDSSMagic = [
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,
];
// instantiate the ggwave instance
// ggwave_factory comes from the ggwave.js module
ggwave_factory().then(function(obj) {
@@ -127,9 +120,6 @@
for (var i = 0; i < text.length; i++) {
payload[i] = text.charCodeAt(i);
}
for (var i = 0; i < kPayloadLength; i++) {
payload[i] = payload[i] ^ kDSSMagic[i];
}
// generate audio waveform
var waveform = ggwave.encode(instance, payload, ggwave.TxProtocolId.GGWAVE_TX_PROTOCOL_MT_FASTEST, 25)
@@ -187,7 +177,6 @@
var payload = "";
res8 = convertTypedArray(res, Uint8Array);
for (var i = 0; i < kPayloadLength; i++) {
res8[i] = res8[i] ^ kDSSMagic[i];
payload += String.fromCharCode(res8[i]);
if (res8[i] == 0) {
break;

View File

@@ -1,5 +1,6 @@
ggwave
ggwave.cpp
fft.h
resampler.h
resampler.cpp
reed-solomon

View File

@@ -1,9 +1,10 @@
#
# arduino-rx
configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave/ggwave.h COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave/ggwave.h COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/fft.h ${CMAKE_CURRENT_SOURCE_DIR}/fft.h COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY)

View File

@@ -49,51 +49,53 @@ void loop() {
auto p = GGWave::getDefaultParameters();
p.sampleRateInp = frequency;
p.sampleRateOut = frequency;
p.sampleRate = frequency;
p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;
p.samplesPerFrame = 128;
p.payloadLength = 16;
p.operatingMode = (ggwave_OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES);
p.operatingMode = (ggwave_OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES);
{
auto & protocols = GGWave::getTxProtocols();
for (auto & p : protocols) {
p.enabled = false;
}
protocols[GGWAVE_TX_PROTOCOL_MT_FASTEST].enabled = true;
protocols[GGWAVE_TX_PROTOCOL_DT_FASTEST].enabled = true;
}
GGWave ggwave(p);
ggwave.setRxProtocols({
//{ GGWAVE_TX_PROTOCOL_MT_FASTEST, ggwave.getTxProtocol(GGWAVE_TX_PROTOCOL_MT_FASTEST) },
{ GGWAVE_TX_PROTOCOL_DT_FASTEST, ggwave.getTxProtocol(GGWAVE_TX_PROTOCOL_DT_FASTEST) },
//ggwave.getTxProtocol(GGWAVE_TX_PROTOCOL_MT_FASTEST),
ggwave.getTxProtocol(GGWAVE_TX_PROTOCOL_DT_FASTEST),
});
Serial.println("Instance initialized");
static GGWave::CBWaveformInp cbWaveformInp = [&](void * data, uint32_t nMaxBytes) {
const int nSamples = nMaxBytes/kSampleSize_bytes;
if (qsize < nSamples) {
return 0u;
}
qsize -= nSamples;
TSample * pDst = (TSample *)(data);
TSample * pSrc = (TSample *)(sampleBuffer + qhead);
if (qhead + nSamples > qmax) {
// should never happen but just in case
memcpy(pDst, pSrc, (qmax - qhead)*kSampleSize_bytes);
memcpy(pDst + (qmax - qhead), sampleBuffer, (nSamples - (qmax - qhead))*kSampleSize_bytes);
qhead += nSamples - qmax;
} else {
memcpy(pDst, pSrc, nSamples*kSampleSize_bytes);
qhead += nSamples;
}
return nSamples*kSampleSize_bytes;
};
int nr = 0;
int niter = 0;
GGWave::TxRxData result;
while (true) {
if (qsize >= 128) {
while (qsize >= p.samplesPerFrame) {
auto tStart = millis();
ggwave.decode(cbWaveformInp);
//Serial.print(qhead);
//Serial.print(" ");
//Serial.print(qtail);
//Serial.print(" ");
//Serial.print(qsize);
//Serial.print(" ");
//Serial.print(ggwave.getSamplesNeeded());
//Serial.println("");
ggwave.decode(sampleBuffer + qhead, p.samplesPerFrame*kSampleSize_bytes);
qsize -= p.samplesPerFrame;
qhead += p.samplesPerFrame;
if (qhead >= qmax) {
qhead = 0;
}
auto tEnd = millis();
if (++niter % 10 == 0) {
@@ -108,17 +110,17 @@ void loop() {
if (strcmp((char *)result.data(), "test") == 0) {
ggwave.init("hello", ggwave.getTxProtocol(GGWAVE_TX_PROTOCOL_MT_FASTEST));
ggwave.encode(nullptr);
ggwave.encode();
const auto & waveformTones = ggwave.getWaveformTones();
for (int i = 0; i < (int) waveformTones.size(); ++i) {
const auto & tones = ggwave.txTones();
for (int i = 0; i < (int) tones.size(); ++i) {
Serial.print(" - frame ");
Serial.print(i);
Serial.print(", ");
Serial.print(waveformTones[i].size());
Serial.print(tones[i].size());
Serial.print(": ");
for (int j = 0; j < (int) waveformTones[i].size(); ++j) {
Serial.print((int)(waveformTones[i][j].freq_hz));
for (int j = 0; j < (int) tones[i].size(); ++j) {
Serial.print((int)(tones[i][j].freq_hz));
Serial.print(" ");
}
Serial.println();

View File

@@ -54,13 +54,6 @@
const kPayloadLength = 3;
var kDSSMagic = [
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,
];
// instantiate the ggwave instance
// ggwave_factory comes from the ggwave.js module
ggwave_factory().then(function(obj) {
@@ -93,12 +86,14 @@
parameters.payloadLength = kPayloadLength;
parameters.sampleRateInp = context.sampleRate;
parameters.sampleRateOut = context.sampleRate;
parameters.operatingMode = GGWAVE_OPERATING_MODE_RX_AND_TX | GGWAVE_OPERATING_MODE_USE_DSS;
instance = ggwave.init(parameters);
parameters = ggwave_FluentPet.getDefaultParameters();
parameters.payloadLength = kPayloadLength;
parameters.sampleRateInp = context.sampleRate;
parameters.sampleRateOut = context.sampleRate - 512;
parameters.operatingMode = GGWAVE_OPERATING_MODE_RX_AND_TX | GGWAVE_OPERATING_MODE_USE_DSS;
instance = ggwave_FluentPet.init(parameters);
}
}
@@ -110,10 +105,9 @@
function onSend(text) {
init();
// DSS : xor the text with the magic numbers
var payload = new Uint8Array(text.length);
for (var i = 0; i < text.length; i++) {
payload[i] = text.charCodeAt(i) ^ kDSSMagic[i];
payload[i] = text.charCodeAt(i);
}
// generate audio waveform
@@ -177,7 +171,7 @@
var payload = "";
res8 = convertTypedArray(res, Uint8Array);
for (var i = 0; i < 3; i++) {
payload += String.fromCharCode(res8[i] ^ kDSSMagic[i]);
payload += String.fromCharCode(res8[i]);
}
if (payload == 'RRR' || payload == 'GGG' || payload == 'BBB') {

View File

@@ -18,17 +18,19 @@ int main(int argc, char** argv) {
printf(" -pN - select playback device N\n");
printf(" -tN - transmission protocol\n");
printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed);
printf(" -s - use Direct Sequence Spread (DSS)\n");
printf(" -v - print generated tones on resend\n");
printf("\n");
auto argm = parseCmdArguments(argc, argv);
int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]);
int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]);
int txProtocolId = argm["t"].empty() ? 1 : std::stoi(argm["t"]);
int payloadLength = argm["l"].empty() ? -1 : std::stoi(argm["l"]);
bool printTones = argm.find("v") == argm.end() ? false : true;
const auto argm = parseCmdArguments(argc, argv);
const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c"));
const int playbackId = argm.count("p") == 0 ? 0 : std::stoi(argm.at("p"));
const int txProtocolId = argm.count("t") == 0 ? 1 : std::stoi(argm.at("t"));
const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l"));
const bool useDSS = argm.count("s") > 0;
const bool printTones = argm.count("v") > 0;
if (GGWave_init(playbackId, captureId, payloadLength) == false) {
if (GGWave_init(playbackId, captureId, payloadLength, 0.0f, useDSS) == false) {
fprintf(stderr, "Failed to initialize GGWave\n");
return -1;
}
@@ -39,6 +41,9 @@ int main(int argc, char** argv) {
const auto & protocols = GGWave::getTxProtocols();
for (int i = 0; i < (int) protocols.size(); ++i) {
const auto & protocol = protocols[i];
if (protocol.enabled == false) {
continue;
}
printf(" %d - %s\n", i, protocol.name);
}

View File

@@ -79,7 +79,8 @@ bool GGWave_init(
const int playbackId,
const int captureId,
const int payloadLength,
const float sampleRateOffset) {
const float sampleRateOffset,
const bool useDSS) {
if (g_devIdInp && g_devIdOut) {
return false;
@@ -213,6 +214,9 @@ bool GGWave_init(
}
if (reinit) {
ggwave_OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;
if (useDSS) mode = ggwave_OperatingMode(mode | GGWAVE_OPERATING_MODE_USE_DSS);
g_ggWave = std::make_shared<GGWave>(GGWave::Parameters {
payloadLength,
(float) g_obtainedSpecInp.freq,
@@ -222,7 +226,7 @@ bool GGWave_init(
GGWave::kDefaultSoundMarkerThreshold,
sampleFormatInp,
sampleFormatOut,
(GGWave::OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX),
mode,
});
}

View File

@@ -8,7 +8,7 @@ class GGWave;
// GGWave helpers
void GGWave_setDefaultCaptureDeviceName(std::string name);
bool GGWave_init(const int playbackId, const int captureId, const int payloadLength = -1, const float sampleRateOffset = 0);
bool GGWave_init(const int playbackId, const int captureId, const int payloadLength = -1, const float sampleRateOffset = 0, const bool useDSS = false);
std::shared_ptr<GGWave> GGWave_instance();
void GGWave_reset(void * parameters);
bool GGWave_mainLoop();

View File

@@ -36,15 +36,6 @@
namespace {
// Direct-sequence spread magic numbers
// Used to xor the actual payload
const std::array<uint8_t, 64> kDSSMagic = {
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,
};
std::mutex g_mutex;
char * toTimeString(const std::chrono::system_clock::time_point & tp) {
std::lock_guard<std::mutex> lock(g_mutex);
@@ -139,6 +130,7 @@ struct Message {
std::chrono::system_clock::time_point timestamp;
std::string data;
int protocolId;
bool dss;
float volume;
Type type;
};
@@ -225,7 +217,6 @@ struct Input {
bool changeNeedSpectrum = false;
bool stopReceiving = false;
bool changeRxProtocols = false;
bool changeDSS = false;
void clear() { memset(this, 0, sizeof(Flags)); }
} flags;
@@ -244,6 +235,7 @@ struct Input {
dst.flags.needReinit = true;
dst.sampleRateOffset = std::move(this->sampleRateOffset);
dst.payloadLength = std::move(this->payloadLength);
dst.directSequenceSpread = std::move(this->directSequenceSpread);
}
if (this->flags.changeNeedSpectrum) {
@@ -263,12 +255,6 @@ struct Input {
dst.rxProtocols = std::move(this->rxProtocols);
}
if (this->flags.changeDSS) {
dst.update = true;
dst.flags.changeDSS = true;
dst.directSequenceSpread = std::move(this->directSequenceSpread);
}
flags.clear();
update = false;
}
@@ -608,7 +594,6 @@ void updateCore() {
static bool isFirstCall = true;
static bool needSpectrum = false;
static bool directSequenceSpread = false;
static int rxDataLengthLast = 0;
static float rxTimestampLast = 0.0f;
static GGWave::TxRxData rxDataLast;
@@ -627,12 +612,6 @@ void updateCore() {
if (inputCurrent.flags.newMessage) {
int n = (int) inputCurrent.message.data.size();
if (directSequenceSpread) {
for (int i = 0; i < n; ++i) {
inputCurrent.message.data[i] ^= kDSSMagic[i%kDSSMagic.size()];
}
}
ggWave->init(
n, inputCurrent.message.data.data(),
ggWave->getTxProtocol(inputCurrent.message.protocolId),
@@ -646,6 +625,9 @@ void updateCore() {
GGWave::SampleFormat sampleFormatOutOld = ggWave->getSampleFormatOut();
auto rxProtocolsOld = ggWave->getRxProtocols();
GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;
if (inputCurrent.directSequenceSpread) mode = GGWave::OperatingMode(mode | GGWAVE_OPERATING_MODE_USE_DSS);
GGWave::Parameters parameters {
inputCurrent.payloadLength,
sampleRateInpOld,
@@ -655,7 +637,7 @@ void updateCore() {
GGWave::kDefaultSoundMarkerThreshold,
sampleFormatInpOld,
sampleFormatOutOld,
(GGWave::OperatingMode) (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX),
mode,
};
GGWave_reset(&parameters);
@@ -676,10 +658,6 @@ void updateCore() {
ggWave->setRxProtocols(inputCurrent.rxProtocols);
}
if (inputCurrent.flags.changeDSS) {
directSequenceSpread = inputCurrent.directSequenceSpread;
}
inputCurrent.flags.clear();
inputCurrent.update = false;
}
@@ -695,18 +673,13 @@ void updateCore() {
std::chrono::system_clock::now(),
"",
ggWave->getRxProtocolId(),
ggWave->isDSSEnabled(),
0,
Message::Error,
};
} else if (rxDataLengthLast > 0 && ImGui::GetTime() - rxTimestampLast > 0.5f) {
auto message = std::string((char *) rxDataLast.data(), rxDataLengthLast);
if (directSequenceSpread) {
for (int i = 0; i < rxDataLengthLast; ++i) {
message[i] ^= kDSSMagic[i%kDSSMagic.size()];
}
}
const Message::Type type = isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text;
g_buffer.stateCore.update = true;
g_buffer.stateCore.flags.newMessage = true;
@@ -715,6 +688,7 @@ void updateCore() {
std::chrono::system_clock::now(),
std::move(message),
ggWave->getRxProtocolId(),
ggWave->isDSSEnabled(),
0,
type,
};
@@ -728,7 +702,7 @@ void updateCore() {
static float tmp[2*NMax];
int N = ggWave->getSamplesPerFrame();
ggWave->computeFFTR(g_buffer.stateCore.rxAmplitude.data(), tmp, N, 1.0);
ggWave->computeFFTR(g_buffer.stateCore.rxAmplitude.data(), tmp, N);
g_buffer.stateCore.rxSpectrum.resize(N);
for (int i = 0; i < N; ++i) {
@@ -1264,7 +1238,7 @@ void renderMain() {
}
if (ImGui::Checkbox("##direct-sequence-spread", &settings.directSequenceSpread)) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.flags.changeDSS = true;
g_buffer.inputUI.flags.needReinit = true;
g_buffer.inputUI.directSequenceSpread = settings.directSequenceSpread;
}
@@ -1375,6 +1349,13 @@ void renderMain() {
ImGui::SameLine();
ImGui::TextColored({ 0.0f, 0.6f, 0.4f, interp }, "%s", settings.txProtocols.at(GGWave::TxProtocolId(message.protocolId)).name);
ImGui::SameLine();
if (message.dss) {
ImGui::TextColored({ 0.4f, 0.6f, 0.4f, interp }, "DSS");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Direct Sequence Spread");
}
ImGui::SameLine();
}
ImGui::TextDisabled("|");
{
@@ -1449,7 +1430,7 @@ void renderMain() {
if (ImGui::ButtonDisablable("Resend", {}, messageSelected.type != Message::Text)) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.flags.newMessage = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume, Message::Text };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, messageSelected.dss, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
ImGui::CloseCurrentPopup();
@@ -1671,7 +1652,7 @@ void renderMain() {
inputLast = std::string(inputBuf);
g_buffer.inputUI.update = true;
g_buffer.inputUI.flags.newMessage = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume, Message::Text };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.directSequenceSpread, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
@@ -1798,6 +1779,7 @@ void renderMain() {
std::chrono::system_clock::now(),
::generateFileBroadcastMessage(),
settings.protocolId,
settings.directSequenceSpread,
settings.volume,
Message::FileBroadcast
};

View File

@@ -69,6 +69,7 @@ extern "C" {
GGWAVE_OPERATING_MODE_RX_AND_TX = (GGWAVE_OPERATING_MODE_RX |
GGWAVE_OPERATING_MODE_TX),
GGWAVE_OPERATING_MODE_TX_ONLY_TONES = 1 << 3,
GGWAVE_OPERATING_MODE_USE_DSS = 1 << 4,
} ggwave_OperatingMode;
// GGWave instance parameters
@@ -447,6 +448,7 @@ public:
// instance state
bool hasTxData() const;
bool isDSSEnabled() const;
int getSamplesPerFrame() const;
int getSampleSizeBytesInp() const;
@@ -475,6 +477,7 @@ public:
bool isReceiving() const;
bool isAnalyzing() const;
int getSamplesNeeded() const;
int getFramesToRecord() const;
int getFramesLeftToRecord() const;
int getFramesToAnalyze() const;
@@ -499,10 +502,9 @@ public:
// src - input real-valued data, size is N
// dst - output complex-valued data, size is 2*N
//
// d is scaling factor
// N must be <= kMaxSamplesPerFrame
//
static bool computeFFTR(const float * src, float * dst, int N, float d);
bool computeFFTR(const float * src, float * dst, int N);
// resample audio waveforms from one sample rate to another using
// sinc interpolation
@@ -592,11 +594,13 @@ private:
const bool m_isTxEnabled;
const bool m_needResampling;
const bool m_txOnlyTones;
const bool m_isDSSEnabled;
// common
TxRxData m_dataEncoded;
TxRxData m_workRSLength; // Reed-Solomon work buffers
TxRxData m_workRSData;
TxRxData m_dssMagic;
// Impl
struct Rx;

779
src/fft.h Normal file
View File

@@ -0,0 +1,779 @@
#pragma once
/*
The FFT routines below are taken from:
https://www.kurims.kyoto-u.ac.jp/~ooura/fft.html
License
Copyright Takuya OOURA, 1996-2001
*/
/*
Fast Fourier/Cosine/Sine Transform
dimension :one
data length :power of 2
decimation :frequency
radix :4, 2
data :inplace
table :use
functions
rdft: Real Discrete Fourier Transform
function prototypes
void rdft(int, int, float *, int *, float *);
-------- Real DFT / Inverse of Real DFT --------
[definition]
<case1> RDFT
R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2
I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0<k<n/2
<case2> IRDFT (excluding scale)
a[k] = (R[0] + R[n/2]*cos(pi*k))/2 +
sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) +
sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k<n
[usage]
<case1>
ip[0] = 0; // first time only
rdft(n, 1, a, ip, w);
<case2>
ip[0] = 0; // first time only
rdft(n, -1, a, ip, w);
[parameters]
n :data length (int)
n >= 2, n = power of 2
a[0...n-1] :input/output data (float *)
<case1>
output data
a[2*k] = R[k], 0<=k<n/2
a[2*k+1] = I[k], 0<k<n/2
a[1] = R[n/2]
<case2>
input data
a[2*j] = R[j], 0<=j<n/2
a[2*j+1] = I[j], 0<j<n/2
a[1] = R[n/2]
ip[0...*] :work area for bit reversal (int *)
length of ip >= 2+sqrt(n/2)
strictly,
length of ip >=
2+(1<<(int)(log(n/2+0.5)/log(2))/2).
ip[0],ip[1] are pointers of the cos/sin table.
w[0...n/2-1] :cos/sin table (float *)
w[],ip[] are initialized if ip[0] == 0.
[remark]
Inverse of
rdft(n, 1, a, ip, w);
is
rdft(n, -1, a, ip, w);
for (j = 0; j <= n - 1; j++) {
a[j] *= 2.0 / n;
}
.
Appendix :
The cos/sin table is recalculated when the larger table required.
w[] and ip[] are compatible with all routines.
*/
void rdft(int n, int isgn, float *a, int *ip, float *w)
{
void makewt(int nw, int *ip, float *w);
void makect(int nc, int *ip, float *c);
void bitrv2(int n, int *ip, float *a);
void cftfsub(int n, float *a, float *w);
void cftbsub(int n, float *a, float *w);
void rftfsub(int n, float *a, int nc, float *c);
void rftbsub(int n, float *a, int nc, float *c);
int nw, nc;
float xi;
nw = ip[0];
if (n > (nw << 2)) {
nw = n >> 2;
makewt(nw, ip, w);
}
nc = ip[1];
if (n > (nc << 2)) {
nc = n >> 2;
makect(nc, ip, w + nw);
}
if (isgn >= 0) {
if (n > 4) {
bitrv2(n, ip + 2, a);
cftfsub(n, a, w);
rftfsub(n, a, nc, w + nw);
} else if (n == 4) {
cftfsub(n, a, w);
}
xi = a[0] - a[1];
a[0] += a[1];
a[1] = xi;
} else {
a[1] = 0.5 * (a[0] - a[1]);
a[0] -= a[1];
if (n > 4) {
rftbsub(n, a, nc, w + nw);
bitrv2(n, ip + 2, a);
cftbsub(n, a, w);
} else if (n == 4) {
cftfsub(n, a, w);
}
}
}
/* -------- initializing routines -------- */
#include <math.h>
void makewt(int nw, int *ip, float *w)
{
void bitrv2(int n, int *ip, float *a);
int j, nwh;
float delta, x, y;
ip[0] = nw;
ip[1] = 1;
if (nw > 2) {
nwh = nw >> 1;
delta = atan(1.0) / nwh;
w[0] = 1;
w[1] = 0;
w[nwh] = cos(delta * nwh);
w[nwh + 1] = w[nwh];
if (nwh > 2) {
for (j = 2; j < nwh; j += 2) {
x = cos(delta * j);
y = sin(delta * j);
w[j] = x;
w[j + 1] = y;
w[nw - j] = y;
w[nw - j + 1] = x;
}
bitrv2(nw, ip + 2, w);
}
}
}
void makect(int nc, int *ip, float *c)
{
int j, nch;
float delta;
ip[1] = nc;
if (nc > 1) {
nch = nc >> 1;
delta = atan(1.0) / nch;
c[0] = cos(delta * nch);
c[nch] = 0.5 * c[0];
for (j = 1; j < nch; j++) {
c[j] = 0.5 * cos(delta * j);
c[nc - j] = 0.5 * sin(delta * j);
}
}
}
/* -------- child routines -------- */
void bitrv2(int n, int *ip, float *a)
{
int j, j1, k, k1, l, m, m2;
float xr, xi, yr, yi;
ip[0] = 0;
l = n;
m = 1;
while ((m << 3) < l) {
l >>= 1;
for (j = 0; j < m; j++) {
ip[m + j] = ip[j] + l;
}
m <<= 1;
}
m2 = 2 * m;
if ((m << 3) == l) {
for (k = 0; k < m; k++) {
for (j = 0; j < k; j++) {
j1 = 2 * j + ip[k];
k1 = 2 * k + ip[j];
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += 2 * m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 -= m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += 2 * m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
}
j1 = 2 * k + m2 + ip[k];
k1 = j1 + m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
}
} else {
for (k = 1; k < m; k++) {
for (j = 0; j < k; j++) {
j1 = 2 * j + ip[k];
k1 = 2 * k + ip[j];
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
yi = a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
}
}
}
}
void bitrv2conj(int n, int *ip, float *a)
{
int j, j1, k, k1, l, m, m2;
float xr, xi, yr, yi;
ip[0] = 0;
l = n;
m = 1;
while ((m << 3) < l) {
l >>= 1;
for (j = 0; j < m; j++) {
ip[m + j] = ip[j] + l;
}
m <<= 1;
}
m2 = 2 * m;
if ((m << 3) == l) {
for (k = 0; k < m; k++) {
for (j = 0; j < k; j++) {
j1 = 2 * j + ip[k];
k1 = 2 * k + ip[j];
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += 2 * m2;
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 -= m2;
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += 2 * m2;
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
}
k1 = 2 * k + ip[k];
a[k1 + 1] = -a[k1 + 1];
j1 = k1 + m2;
k1 = j1 + m2;
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
k1 += m2;
a[k1 + 1] = -a[k1 + 1];
}
} else {
a[1] = -a[1];
a[m2 + 1] = -a[m2 + 1];
for (k = 1; k < m; k++) {
for (j = 0; j < k; j++) {
j1 = 2 * j + ip[k];
k1 = 2 * k + ip[j];
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
j1 += m2;
k1 += m2;
xr = a[j1];
xi = -a[j1 + 1];
yr = a[k1];
yi = -a[k1 + 1];
a[j1] = yr;
a[j1 + 1] = yi;
a[k1] = xr;
a[k1 + 1] = xi;
}
k1 = 2 * k + ip[k];
a[k1 + 1] = -a[k1 + 1];
a[k1 + m2 + 1] = -a[k1 + m2 + 1];
}
}
}
void cftfsub(int n, float *a, float *w)
{
void cft1st(int n, float *a, float *w);
void cftmdl(int n, int l, float *a, float *w);
int j, j1, j2, j3, l;
float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
l = 2;
if (n > 8) {
cft1st(n, a, w);
l = 8;
while ((l << 2) < n) {
cftmdl(n, l, a, w);
l <<= 2;
}
}
if ((l << 2) == n) {
for (j = 0; j < l; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = a[j + 1] + a[j1 + 1];
x1r = a[j] - a[j1];
x1i = a[j + 1] - a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
a[j2] = x0r - x2r;
a[j2 + 1] = x0i - x2i;
a[j1] = x1r - x3i;
a[j1 + 1] = x1i + x3r;
a[j3] = x1r + x3i;
a[j3 + 1] = x1i - x3r;
}
} else {
for (j = 0; j < l; j += 2) {
j1 = j + l;
x0r = a[j] - a[j1];
x0i = a[j + 1] - a[j1 + 1];
a[j] += a[j1];
a[j + 1] += a[j1 + 1];
a[j1] = x0r;
a[j1 + 1] = x0i;
}
}
}
void cftbsub(int n, float *a, float *w)
{
void cft1st(int n, float *a, float *w);
void cftmdl(int n, int l, float *a, float *w);
int j, j1, j2, j3, l;
float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
l = 2;
if (n > 8) {
cft1st(n, a, w);
l = 8;
while ((l << 2) < n) {
cftmdl(n, l, a, w);
l <<= 2;
}
}
if ((l << 2) == n) {
for (j = 0; j < l; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = -a[j + 1] - a[j1 + 1];
x1r = a[j] - a[j1];
x1i = -a[j + 1] + a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i - x2i;
a[j2] = x0r - x2r;
a[j2 + 1] = x0i + x2i;
a[j1] = x1r - x3i;
a[j1 + 1] = x1i - x3r;
a[j3] = x1r + x3i;
a[j3 + 1] = x1i + x3r;
}
} else {
for (j = 0; j < l; j += 2) {
j1 = j + l;
x0r = a[j] - a[j1];
x0i = -a[j + 1] + a[j1 + 1];
a[j] += a[j1];
a[j + 1] = -a[j + 1] - a[j1 + 1];
a[j1] = x0r;
a[j1 + 1] = x0i;
}
}
}
void cft1st(int n, float *a, float *w)
{
int j, k1, k2;
float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
x0r = a[0] + a[2];
x0i = a[1] + a[3];
x1r = a[0] - a[2];
x1i = a[1] - a[3];
x2r = a[4] + a[6];
x2i = a[5] + a[7];
x3r = a[4] - a[6];
x3i = a[5] - a[7];
a[0] = x0r + x2r;
a[1] = x0i + x2i;
a[4] = x0r - x2r;
a[5] = x0i - x2i;
a[2] = x1r - x3i;
a[3] = x1i + x3r;
a[6] = x1r + x3i;
a[7] = x1i - x3r;
wk1r = w[2];
x0r = a[8] + a[10];
x0i = a[9] + a[11];
x1r = a[8] - a[10];
x1i = a[9] - a[11];
x2r = a[12] + a[14];
x2i = a[13] + a[15];
x3r = a[12] - a[14];
x3i = a[13] - a[15];
a[8] = x0r + x2r;
a[9] = x0i + x2i;
a[12] = x2i - x0i;
a[13] = x0r - x2r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[10] = wk1r * (x0r - x0i);
a[11] = wk1r * (x0r + x0i);
x0r = x3i + x1r;
x0i = x3r - x1i;
a[14] = wk1r * (x0i - x0r);
a[15] = wk1r * (x0i + x0r);
k1 = 0;
for (j = 16; j < n; j += 16) {
k1 += 2;
k2 = 2 * k1;
wk2r = w[k1];
wk2i = w[k1 + 1];
wk1r = w[k2];
wk1i = w[k2 + 1];
wk3r = wk1r - 2 * wk2i * wk1i;
wk3i = 2 * wk2i * wk1r - wk1i;
x0r = a[j] + a[j + 2];
x0i = a[j + 1] + a[j + 3];
x1r = a[j] - a[j + 2];
x1i = a[j + 1] - a[j + 3];
x2r = a[j + 4] + a[j + 6];
x2i = a[j + 5] + a[j + 7];
x3r = a[j + 4] - a[j + 6];
x3i = a[j + 5] - a[j + 7];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
x0r -= x2r;
x0i -= x2i;
a[j + 4] = wk2r * x0r - wk2i * x0i;
a[j + 5] = wk2r * x0i + wk2i * x0r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[j + 2] = wk1r * x0r - wk1i * x0i;
a[j + 3] = wk1r * x0i + wk1i * x0r;
x0r = x1r + x3i;
x0i = x1i - x3r;
a[j + 6] = wk3r * x0r - wk3i * x0i;
a[j + 7] = wk3r * x0i + wk3i * x0r;
wk1r = w[k2 + 2];
wk1i = w[k2 + 3];
wk3r = wk1r - 2 * wk2r * wk1i;
wk3i = 2 * wk2r * wk1r - wk1i;
x0r = a[j + 8] + a[j + 10];
x0i = a[j + 9] + a[j + 11];
x1r = a[j + 8] - a[j + 10];
x1i = a[j + 9] - a[j + 11];
x2r = a[j + 12] + a[j + 14];
x2i = a[j + 13] + a[j + 15];
x3r = a[j + 12] - a[j + 14];
x3i = a[j + 13] - a[j + 15];
a[j + 8] = x0r + x2r;
a[j + 9] = x0i + x2i;
x0r -= x2r;
x0i -= x2i;
a[j + 12] = -wk2i * x0r - wk2r * x0i;
a[j + 13] = -wk2i * x0i + wk2r * x0r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[j + 10] = wk1r * x0r - wk1i * x0i;
a[j + 11] = wk1r * x0i + wk1i * x0r;
x0r = x1r + x3i;
x0i = x1i - x3r;
a[j + 14] = wk3r * x0r - wk3i * x0i;
a[j + 15] = wk3r * x0i + wk3i * x0r;
}
}
void cftmdl(int n, int l, float *a, float *w)
{
int j, j1, j2, j3, k, k1, k2, m, m2;
float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
m = l << 2;
for (j = 0; j < l; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = a[j + 1] + a[j1 + 1];
x1r = a[j] - a[j1];
x1i = a[j + 1] - a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
a[j2] = x0r - x2r;
a[j2 + 1] = x0i - x2i;
a[j1] = x1r - x3i;
a[j1 + 1] = x1i + x3r;
a[j3] = x1r + x3i;
a[j3 + 1] = x1i - x3r;
}
wk1r = w[2];
for (j = m; j < l + m; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = a[j + 1] + a[j1 + 1];
x1r = a[j] - a[j1];
x1i = a[j + 1] - a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
a[j2] = x2i - x0i;
a[j2 + 1] = x0r - x2r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[j1] = wk1r * (x0r - x0i);
a[j1 + 1] = wk1r * (x0r + x0i);
x0r = x3i + x1r;
x0i = x3r - x1i;
a[j3] = wk1r * (x0i - x0r);
a[j3 + 1] = wk1r * (x0i + x0r);
}
k1 = 0;
m2 = 2 * m;
for (k = m2; k < n; k += m2) {
k1 += 2;
k2 = 2 * k1;
wk2r = w[k1];
wk2i = w[k1 + 1];
wk1r = w[k2];
wk1i = w[k2 + 1];
wk3r = wk1r - 2 * wk2i * wk1i;
wk3i = 2 * wk2i * wk1r - wk1i;
for (j = k; j < l + k; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = a[j + 1] + a[j1 + 1];
x1r = a[j] - a[j1];
x1i = a[j + 1] - a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
x0r -= x2r;
x0i -= x2i;
a[j2] = wk2r * x0r - wk2i * x0i;
a[j2 + 1] = wk2r * x0i + wk2i * x0r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[j1] = wk1r * x0r - wk1i * x0i;
a[j1 + 1] = wk1r * x0i + wk1i * x0r;
x0r = x1r + x3i;
x0i = x1i - x3r;
a[j3] = wk3r * x0r - wk3i * x0i;
a[j3 + 1] = wk3r * x0i + wk3i * x0r;
}
wk1r = w[k2 + 2];
wk1i = w[k2 + 3];
wk3r = wk1r - 2 * wk2r * wk1i;
wk3i = 2 * wk2r * wk1r - wk1i;
for (j = k + m; j < l + (k + m); j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
x0r = a[j] + a[j1];
x0i = a[j + 1] + a[j1 + 1];
x1r = a[j] - a[j1];
x1i = a[j + 1] - a[j1 + 1];
x2r = a[j2] + a[j3];
x2i = a[j2 + 1] + a[j3 + 1];
x3r = a[j2] - a[j3];
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
x0r -= x2r;
x0i -= x2i;
a[j2] = -wk2i * x0r - wk2r * x0i;
a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
x0r = x1r - x3i;
x0i = x1i + x3r;
a[j1] = wk1r * x0r - wk1i * x0i;
a[j1 + 1] = wk1r * x0i + wk1i * x0r;
x0r = x1r + x3i;
x0i = x1i - x3r;
a[j3] = wk3r * x0r - wk3i * x0i;
a[j3 + 1] = wk3r * x0i + wk3i * x0r;
}
}
}
void rftfsub(int n, float *a, int nc, float *c)
{
int j, k, kk, ks, m;
float wkr, wki, xr, xi, yr, yi;
m = n >> 1;
ks = 2 * nc / m;
kk = 0;
for (j = 2; j < m; j += 2) {
k = n - j;
kk += ks;
wkr = 0.5 - c[nc - kk];
wki = c[kk];
xr = a[j] - a[k];
xi = a[j + 1] + a[k + 1];
yr = wkr * xr - wki * xi;
yi = wkr * xi + wki * xr;
a[j] -= yr;
a[j + 1] -= yi;
a[k] += yr;
a[k + 1] -= yi;
}
}
void rftbsub(int n, float *a, int nc, float *c)
{
int j, k, kk, ks, m;
float wkr, wki, xr, xi, yr, yi;
a[1] = -a[1];
m = n >> 1;
ks = 2 * nc / m;
kk = 0;
for (j = 2; j < m; j += 2) {
k = n - j;
kk += ks;
wkr = 0.5 - c[nc - kk];
wki = c[kk];
xr = a[j] - a[k];
xi = a[j + 1] + a[k + 1];
yr = wkr * xr + wki * xi;
yi = wkr * xi - wki * xr;
a[j] -= yr;
a[j + 1] = yi - a[j + 1];
a[k] += yr;
a[k + 1] = yi - a[k + 1];
}
a[m + 1] = -a[m + 1];
}

View File

@@ -1,10 +1,10 @@
#include "ggwave/ggwave.h"
#include "fft.h"
#include "reed-solomon/rs.hpp"
#include <cstdio>
#include <cmath>
#include <map>
#include <ctime>
//#include <random>
@@ -22,8 +22,8 @@
namespace {
FILE * g_fptr = stderr;
std::map<ggwave_Instance, GGWave *> g_instances;
std::map<ggwave_Instance, GGWave::RxProtocols> g_rxProtocols;
std::vector<GGWave *> g_instances;
std::vector<GGWave::RxProtocols> g_rxProtocols;
double linear_interp(double first_number, double second_number, double fraction) {
return (first_number + ((second_number - first_number)*fraction));
@@ -45,6 +45,11 @@ 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_rxProtocols.resize(curId + 1, GGWave::getTxProtocols());
}
g_instances[curId] = new GGWave({
parameters.payloadLength,
parameters.sampleRateInp,
@@ -61,8 +66,10 @@ ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
extern "C"
void ggwave_free(ggwave_Instance instance) {
delete (GGWave *) g_instances[instance];
g_instances.erase(instance);
if ((int) g_instances.size() > instance && g_instances[instance]) {
delete (GGWave *) g_instances[instance];
g_instances[instance] = nullptr;
}
}
extern "C"
@@ -170,11 +177,6 @@ void ggwave_toggleRxProtocol(
ggwave_Instance instance,
ggwave_TxProtocolId rxProtocolId,
int state) {
// if never called - initialize with all available protocols
if (g_rxProtocols.find(instance) == g_rxProtocols.end()) {
g_rxProtocols[instance] = GGWave::getTxProtocols();
}
if (state == 0) {
// disable Rx protocol
g_rxProtocols[instance][rxProtocolId].enabled = false;
@@ -192,90 +194,14 @@ void ggwave_toggleRxProtocol(
namespace {
// FFT routines taken from https://stackoverflow.com/a/37729648/4039976
int log2(int N) {
int k = N, i = 0;
while(k) {
k >>= 1;
i++;
}
return i - 1;
void FFT(float * f, int N, int * ip, float * w) {
rdft(N, 1, f, ip, w);
}
int reverse(int N, int n) {
int j, p = 0;
for(j = 1; j <= log2(N); j++) {
if(n & (1 << (log2(N) - j)))
p |= 1 << (j - 1);
}
return p;
}
void FFT(const float * src, float * dst, int N, int * ip, float * w) {
std::copy(src, src + N, dst);
void ordina(float * f1, int N) {
static thread_local float f2[2*GGWave::kMaxSamplesPerFrame];
for (int i = 0; i < N; i++) {
int ir = reverse(N, i);
f2[2*i + 0] = f1[2*ir + 0];
f2[2*i + 1] = f1[2*ir + 1];
}
for (int j = 0; j < N; j++) {
f1[2*j + 0] = f2[2*j + 0];
f1[2*j + 1] = f2[2*j + 1];
}
}
void transform(float * f, int N) {
ordina(f, N); //first: reverse order
float * W;
W = (float *)malloc(N*sizeof(float));
W[2*1 + 0] = cos(-2.*M_PI/N);
W[2*1 + 1] = sin(-2.*M_PI/N);
W[2*0 + 0] = 1;
W[2*0 + 1] = 0;
for (int i = 2; i < N / 2; i++) {
W[2*i + 0] = cos(-2.*i*M_PI/N);
W[2*i + 1] = sin(-2.*i*M_PI/N);
}
int n = 1;
int a = N / 2;
for(int j = 0; j < log2(N); j++) {
for(int i = 0; i < N; i++) {
if(!(i & n)) {
int wi = (i * a) % (n * a);
int fi = i + n;
float a = W[2*wi + 0];
float b = W[2*wi + 1];
float c = f[2*fi + 0];
float d = f[2*fi + 1];
float temp[2] = { f[2*i + 0], f[2*i + 1] };
float Temp[2] = { a*c - b*d, b*c + a*d };
f[2*i + 0] = temp[0] + Temp[0];
f[2*i + 1] = temp[1] + Temp[1];
f[2*fi + 0] = temp[0] - Temp[0];
f[2*fi + 1] = temp[1] - Temp[1];
}
}
n *= 2;
a = a / 2;
}
free(W);
}
void FFT(float * f, int N, float d) {
transform(f, N);
for (int i = 0; i < N; i++) {
f[2*i + 0] *= d;
f[2*i + 1] *= d;
}
}
void FFT(const float * src, float * dst, int N, float d) {
for (int i = 0; i < N; ++i) {
dst[2*i + 0] = src[i];
dst[2*i + 1] = 0.0f;
}
FFT(dst, N, d);
FFT(dst, N, ip, w);
}
inline void addAmplitudeSmooth(
@@ -336,8 +262,9 @@ struct GGWave::Rx {
int framesToRecord = 0;
int samplesNeeded = 0;
std::vector<float> fftInp; // real
std::vector<float> fftOut; // complex
std::vector<int> m_fftWorkI;
std::vector<float> m_fftWorkF;
bool hasNewSpectrum = false;
bool hasNewAmplitude = false;
@@ -439,6 +366,7 @@ GGWave::GGWave(const Parameters & parameters) :
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),
@@ -477,8 +405,10 @@ GGWave::GGWave(const Parameters & parameters) :
m_rx->samplesNeeded = m_samplesPerFrame;
m_rx->fftInp.resize(m_samplesPerFrame);
m_rx->fftOut.resize(2*m_samplesPerFrame);
m_rx->m_fftWorkI.resize(3 + sqrt(m_samplesPerFrame/2));
m_rx->m_fftWorkF.resize(m_samplesPerFrame/2);
m_rx->m_fftWorkI[0] = 0;
m_rx->sampleSpectrum.resize(m_samplesPerFrame);
m_rx->sampleAmplitude.resize(m_needResampling ? m_samplesPerFrame + 128 : m_samplesPerFrame); // small extra space because sometimes resampling needs a few more samples
@@ -563,6 +493,15 @@ GGWave::GGWave(const Parameters & parameters) :
m_resampler = new Resampler();
}
if (m_isDSSEnabled) {
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("", getDefaultTxProtocol(), 0);
}
@@ -620,23 +559,24 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txPr
}
m_tx->txProtocol = txProtocol;
m_tx->txDataLength = dataSize;
m_tx->txDataLength = m_isFixedPayloadLength ? m_payloadLength : dataSize;
m_tx->sendVolume = ((double)(volume))/100.0f;
m_tx->hasNewTxData = false;
std::fill(m_tx->txData.begin(), m_tx->txData.end(), 0);
std::fill(m_dataEncoded.begin(), m_dataEncoded.end(), 0);
if (m_tx->txDataLength > 0) {
if (dataSize > 0) {
m_tx->txData[0] = m_tx->txDataLength;
for (int i = 0; i < m_tx->txDataLength; ++i) m_tx->txData[i + 1] = dataBuffer[i];
for (int i = 0; i < m_tx->txDataLength; ++i) {
m_tx->txData[i + 1] = i < dataSize ? dataBuffer[i] : 0;
if (m_isDSSEnabled) {
m_tx->txData[i + 1] ^= m_dssMagic[i%m_dssMagic.size()];
}
}
m_tx->hasNewTxData = true;
}
if (m_isFixedPayloadLength) {
m_tx->txDataLength = m_payloadLength;
}
} else {
if (dataSize > 0) {
ggprintf("Tx is disabled - cannot transmit data with this ggwave instance\n");
@@ -661,11 +601,6 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txPr
std::fill(m_rx->rxData.begin(), m_rx->rxData.end(), 0);
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->fftOut[2*i + 0] = 0.0f;
m_rx->fftOut[2*i + 1] = 0.0f;
}
for (auto & s : m_rx->spectrumHistoryFixed) {
std::fill(s.begin(), s.end(), 0);
}
@@ -1148,6 +1083,7 @@ bool GGWave::decode(const void * data, uint32_t nBytes) {
//
bool GGWave::hasTxData() const { return m_tx && m_tx->hasNewTxData; }
bool GGWave::isDSSEnabled() const { return m_isDSSEnabled; }
int GGWave::getSamplesPerFrame() const { return m_samplesPerFrame; }
int GGWave::getSampleSizeBytesInp() const { return m_sampleSizeBytesInp; }
@@ -1183,6 +1119,7 @@ bool GGWave::takeTxAmplitudeI16(AmplitudeDataI16 & dst) {
bool GGWave::isReceiving() const { return m_rx->receivingData; }
bool GGWave::isAnalyzing() const { return m_rx->analyzingData; }
int GGWave::getSamplesNeeded() const { return m_rx->samplesNeeded; }
int GGWave::getFramesToRecord() const { return m_rx->framesToRecord; }
int GGWave::getFramesLeftToRecord() const { return m_rx->framesLeftToRecord; }
int GGWave::getFramesToAnalyze() const { return m_rx->framesToAnalyze; }
@@ -1238,13 +1175,13 @@ bool GGWave::takeRxAmplitude(AmplitudeData & dst) {
return true;
}
bool GGWave::computeFFTR(const float * src, float * dst, int N, float d) {
if (N > kMaxSamplesPerFrame) {
ggprintf("computeFFTR: N (%d) must be <= %d\n", N, GGWave::kMaxSamplesPerFrame);
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, d);
FFT(src, dst, N, m_rx->m_fftWorkI.data(), m_rx->m_fftWorkF.data());
return true;
}
@@ -1426,7 +1363,7 @@ void GGWave::decode_variable() {
}
// calculate spectrum
FFT(m_rx->sampleAmplitudeAverage.data(), m_rx->fftOut.data(), m_samplesPerFrame, 1.0);
FFT(m_rx->sampleAmplitudeAverage.data(), m_rx->fftOut.data(), m_samplesPerFrame, m_rx->m_fftWorkI.data(), m_rx->m_fftWorkF.data());
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->sampleSpectrum[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]);
@@ -1488,16 +1425,16 @@ void GGWave::decode_variable() {
std::copy(
m_rx->recordedAmplitude.begin() + offsetTx*step,
m_rx->recordedAmplitude.begin() + offsetTx*step + m_samplesPerFrame, m_rx->fftInp.data());
m_rx->recordedAmplitude.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 < rxProtocol.framesPerTx; ++k) {
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->fftInp[i] += m_rx->recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i];
m_rx->fftOut[i] += m_rx->recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i];
}
}
FFT(m_rx->fftInp.data(), m_rx->fftOut.data(), m_samplesPerFrame, 1.0);
FFT(m_rx->fftOut.data(), m_samplesPerFrame, m_rx->m_fftWorkI.data(), m_rx->m_fftWorkF.data());
for (int i = 0; i < m_samplesPerFrame; ++i) {
m_rx->sampleSpectrum[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]);
@@ -1559,7 +1496,13 @@ void GGWave::decode_variable() {
RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength), m_workRSData.data());
if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rx->rxData.data()) == 0) {
if (m_rx->rxData[0] != 0) {
if (decodedLength > 0) {
if (m_isDSSEnabled) {
for (int i = 0; i < decodedLength; ++i) {
m_rx->rxData[i] = m_rx->rxData[i] ^ m_dssMagic[i%m_dssMagic.size()];
}
}
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", decodedLength, rxProtocol.name, rxProtocolId);
ggprintf("Received sound data successfully: '%s'\n", m_rx->rxData.data());
@@ -1705,7 +1648,7 @@ void GGWave::decode_fixed() {
m_rx->hasNewSpectrum = true;
// calculate spectrum
FFT(m_rx->sampleAmplitude.data(), m_rx->fftOut.data(), m_samplesPerFrame, 1.0);
FFT(m_rx->sampleAmplitude.data(), m_rx->fftOut.data(), m_samplesPerFrame, m_rx->m_fftWorkI.data(), m_rx->m_fftWorkF.data());
float amax = 0.0f;
for (int i = 0; i < m_samplesPerFrame; ++i) {
@@ -1716,20 +1659,23 @@ void GGWave::decode_fixed() {
amax = std::max(amax, m_rx->sampleSpectrum[i]);
}
// original, floating-point version
//m_rx->spectrumHistoryFixed[m_rx->historyIdFixed] = m_rx->sampleSpectrum;
// in theory, using uint8_t should work alsmost the same and save 4 times the memory, but for some resone
// the result 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.0, std::max(0.0, round(m_rx->sampleSpectrum[i]/amax*255.0f)));
// m_rx->spectrumHistoryFixed[m_rx->historyIdFixed][i] = std::min(255.0f, std::max(0.0f, round(m_rx->sampleSpectrum[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.0, std::max(0.0, round(m_rx->sampleSpectrum[i]/amax*65535.0f)));
m_rx->spectrumHistoryFixed[m_rx->historyIdFixed][i] = std::min(65535.0f, std::max(0.0f, round(m_rx->sampleSpectrum[i]*amax)));
}
if (++m_rx->historyIdFixed >= (int) m_rx->spectrumHistoryFixed.size()) {
@@ -1844,16 +1790,20 @@ void GGWave::decode_fixed() {
}
if (rsData.Decode(m_dataEncoded.data(), m_rx->rxData.data()) == 0) {
if (m_rx->rxData[0] != 0) {
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", m_rx->rxData[0], rxProtocol.name, rxProtocolId);
ggprintf("Received sound data successfully: '%s'\n", m_rx->rxData.data());
isValid = true;
m_rx->hasNewRxData = true;
m_rx->lastRxDataLength = m_payloadLength;
m_rx->rxProtocol = rxProtocol;
m_rx->rxProtocolId = TxProtocolId(rxProtocolId);
if (m_isDSSEnabled) {
for (int i = 0; i < m_payloadLength; ++i) {
m_rx->rxData[i] = m_rx->rxData[i] ^ m_dssMagic[i%m_dssMagic.size()];
}
}
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", m_payloadLength, rxProtocol.name, rxProtocolId);
ggprintf("Received sound data successfully: '%s'\n", m_rx->rxData.data());
isValid = true;
m_rx->hasNewRxData = true;
m_rx->lastRxDataLength = m_payloadLength;
m_rx->rxProtocol = rxProtocol;
m_rx->rxProtocolId = TxProtocolId(rxProtocolId);
}
}