diff --git a/CMakeLists.txt b/CMakeLists.txt index fdccbda..1473703 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,8 +47,8 @@ if (GGWAVE_SANITIZE_THREAD) endif() if (GGWAVE_SANITIZE_ADDRESS) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -D_GLIBCXX_DEBUG") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -D_GLIBCXX_DEBUG") endif() if (GGWAVE_SANITIZE_UNDEFINED) diff --git a/examples/ggwave-cli/main.cpp b/examples/ggwave-cli/main.cpp index 09c605a..88cff63 100644 --- a/examples/ggwave-cli/main.cpp +++ b/examples/ggwave-cli/main.cpp @@ -14,11 +14,7 @@ int main(int argc, char** argv) { printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf(" -pN - select playback device N\n"); - printf(" -tN - transmission protocol:\n"); - printf(" -t0 : Normal\n"); - printf(" -t1 : Fast (default)\n"); - printf(" -t2 : Fastest\n"); - printf(" -t3 : Ultrasonic\n"); + printf(" -tN - transmission protocol\n"); printf("\n"); auto argm = parseCmdArguments(argc, argv); @@ -33,43 +29,17 @@ int main(int argc, char** argv) { auto ggWave = GGWave_instance(); - ggWave->setTxMode(GGWave::TxMode::VariableLength); + printf("Available Tx protocols:\n"); + for (int i = 0; i < (int) ggWave->getTxProtocols().size(); ++i) { + printf(" -t%d : %s\n", i, ggWave->getTxProtocols()[i].name); + } + + if (txProtocol < 0 || txProtocol > (int) ggWave->getTxProtocols().size()) { + fprintf(stderr, "Unknown Tx protocol %d\n", txProtocol); + return -3; + } printf("Selecting Tx protocol %d\n", txProtocol); - switch (txProtocol) { - case 0: - { - printf("Using 'Normal' Tx Protocol\n"); - ggWave->setParameters(1, 40, 9, 3, 50); - } - break; - case 1: - { - printf("Using 'Fast' Tx Protocol\n"); - ggWave->setParameters(1, 40, 6, 3, 50); - } - break; - case 2: - { - printf("Using 'Fastest' Tx Protocol\n"); - ggWave->setParameters(1, 40, 3, 3, 50); - } - break; - case 3: - { - printf("Using 'Ultrasonic' Tx Protocol\n"); - ggWave->setParameters(1, 320, 9, 3, 50); - } - break; - default: - { - printf("Using 'Fast' Tx Protocol\n"); - ggWave->setParameters(1, 40, 6, 3, 50); - } - }; - printf("\n"); - - ggWave->init(0, ""); std::mutex mutex; std::thread inputThread([&]() { @@ -86,7 +56,7 @@ int main(int argc, char** argv) { } { std::lock_guard lock(mutex); - ggWave->init(input.size(), input.data()); + ggWave->init(input.size(), input.data(), ggWave->getTxProtocols()[txProtocol], 50); } inputOld = input; } diff --git a/examples/ggwave-common-sdl2.cpp b/examples/ggwave-common-sdl2.cpp index 271015d..aeb4a47 100644 --- a/examples/ggwave-common-sdl2.cpp +++ b/examples/ggwave-common-sdl2.cpp @@ -34,8 +34,8 @@ GGWave *g_ggWave = nullptr; // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE - int setText(int textLength, const char * text) { - g_ggWave->init(textLength, text); + int sendData(int textLength, const char * text, int protocolId, int volume) { + g_ggWave->init(textLength, text, g_ggWave->getTxProtocols()[protocolId], volume); return 0; } @@ -48,9 +48,6 @@ extern "C" { EMSCRIPTEN_KEEPALIVE int getSampleRate() { return g_ggWave->getSampleRateIn(); } - EMSCRIPTEN_KEEPALIVE - float getAverageRxTime_ms() { return g_ggWave->getAverageRxTime_ms(); } - EMSCRIPTEN_KEEPALIVE int getFramesToRecord() { return g_ggWave->getFramesToRecord(); } @@ -67,39 +64,12 @@ extern "C" { int hasDeviceOutput() { return g_devIdOut; } EMSCRIPTEN_KEEPALIVE - int hasDeviceCapture() { return (g_ggWave->getTotalBytesCaptured() > 0) ? g_devIdIn : 0; } + int hasDeviceCapture() { return g_devIdIn; } EMSCRIPTEN_KEEPALIVE int doInit() { return GGWave_init(-1, -1); } - - EMSCRIPTEN_KEEPALIVE - int setTxMode(int txMode) { - g_ggWave->setTxMode((GGWave::TxMode)(txMode)); - g_ggWave->init(0, ""); - return 0; - } - - EMSCRIPTEN_KEEPALIVE - void setParameters( - int paramFreqDelta, - int paramFreqStart, - int paramFramesPerTx, - int paramBytesPerTx, - int /*paramECCBytesPerTx*/, - int paramVolume) { - if (g_ggWave == nullptr) return; - - g_ggWave->setParameters( - paramFreqDelta, - paramFreqStart, - paramFramesPerTx, - paramBytesPerTx, - paramVolume); - - g_ggWave->init(0, ""); - } } void GGWave_setDefaultCaptureDeviceName(std::string name) { @@ -266,7 +236,7 @@ bool GGWave_mainLoop() { return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes); }; - if (g_ggWave->getHasData() == false) { + if (g_ggWave->hasTxData() == false) { SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE); static auto tLastNoData = std::chrono::high_resolution_clock::now(); diff --git a/examples/ggwave-gui/main.cpp b/examples/ggwave-gui/main.cpp index 130087c..3c48d49 100644 --- a/examples/ggwave-gui/main.cpp +++ b/examples/ggwave-gui/main.cpp @@ -50,17 +50,11 @@ int main(int argc, char** argv) { printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf(" -pN - select playback device N\n"); - printf(" -tN - transmission protocol:\n"); - printf(" -t0 : Normal\n"); - printf(" -t1 : Fast (default)\n"); - printf(" -t2 : Fastest\n"); - printf(" -t3 : Ultrasonic\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 txProtocol = argm["t"].empty() ? 1 : std::stoi(argm["t"]); if (GGWave_init(playbackId, captureId) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); @@ -69,44 +63,6 @@ int main(int argc, char** argv) { auto ggWave = GGWave_instance(); - ggWave->setTxMode(GGWave::TxMode::VariableLength); - - printf("Selecting Tx protocol %d\n", txProtocol); - switch (txProtocol) { - case 0: - { - printf("Using 'Normal' Tx Protocol\n"); - ggWave->setParameters(1, 40, 9, 3, 50); - } - break; - case 1: - { - printf("Using 'Fast' Tx Protocol\n"); - ggWave->setParameters(1, 40, 6, 3, 50); - } - break; - case 2: - { - printf("Using 'Fastest' Tx Protocol\n"); - ggWave->setParameters(1, 40, 3, 3, 50); - } - break; - case 3: - { - printf("Using 'Ultrasonic' Tx Protocol\n"); - ggWave->setParameters(1, 320, 9, 3, 50); - } - break; - default: - { - printf("Using 'Fast' Tx Protocol\n"); - ggWave->setParameters(1, 40, 6, 3, 50); - } - }; - printf("\n"); - - ggWave->init(0, ""); - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { fprintf(stderr, "Error: %s\n", SDL_GetError()); return -1; @@ -134,7 +90,8 @@ int main(int argc, char** argv) { bool received; std::time_t timestamp; std::string data; - std::string protocol; + int protocolId; + float volume; }; struct State { @@ -175,7 +132,12 @@ int main(int argc, char** argv) { } if (inputCurrent.update) { - ggWave->init(inputCurrent.message.data.size(), inputCurrent.message.data.data()); + ggWave->init( + inputCurrent.message.data.size(), + inputCurrent.message.data.data(), + ggWave->getTxProtocols()[inputCurrent.message.protocolId], + 100*inputCurrent.message.volume); + inputCurrent.update = false; } @@ -184,7 +146,13 @@ int main(int argc, char** argv) { lastRxDataLength = ggWave->takeRxData(lastRxData); if (lastRxDataLength > 0) { buffer.stateCore.update = true; - buffer.stateCore.message = { true, std::time(nullptr), std::string((char *) lastRxData.data(), lastRxDataLength), "" }; + buffer.stateCore.message = { + true, + std::time(nullptr), + std::string((char *) lastRxData.data(), lastRxDataLength), + ggWave->getRxProtocolId(), + 0, + }; } { @@ -214,6 +182,20 @@ int main(int argc, char** argv) { } } + enum class WindowId { + Settings, + Messages, + Commands, + }; + + struct Settings { + int protocolId = 1; + float volume = 0.25f; + }; + + static WindowId windowId = WindowId::Messages; + static Settings settings; + static char inputBuf[256]; static bool doInputFocus = false; @@ -260,106 +242,162 @@ int main(int argc, char** argv) { ImGui::InvisibleButton("StatusBar", { ImGui::GetContentRegionAvailWidth(), statusBarHeight }); if (ImGui::Button(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight } )) { + windowId = WindowId::Settings; } ImGui::SameLine(); if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + windowId = WindowId::Messages; } ImGui::SameLine(); if (ImGui::Button(ICON_FA_LIST_UL " Commands", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + windowId = WindowId::Commands; } - const float messagesInputHeight = ImGui::GetTextLineHeightWithSpacing(); - const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x; - float messagesHistoryHeigth = messagesHistoryHeigthMax; + if (windowId == WindowId::Settings) { + ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true); + ImGui::Text("Waver v0.1"); + ImGui::Separator(); - // no automatic screen resize support for iOS + ImGui::Text("%s", ""); + ImGui::Text("Sample rate (capture): %g, %d B/sample", ggWave->getSampleRateIn(), ggWave->getSampleSizeBytesIn()); + ImGui::Text("Sample rate (playback): %g, %d B/sample", ggWave->getSampleRateOut(), ggWave->getSampleSizeBytesOut()); + + static float kLabelWidth = 100.0f; + + // volume + ImGui::Text("%s", ""); + { + auto posSave = ImGui::GetCursorScreenPos(); + ImGui::Text("Volume: "); + ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); + } + ImGui::SliderFloat("##volume", &settings.volume, 0.0f, 1.0f); + + // protocol + ImGui::Text("%s", ""); + { + auto posSave = ImGui::GetCursorScreenPos(); + ImGui::Text("Tx Protocol: "); + ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); + } + ImGui::SameLine(); + if (ImGui::BeginCombo("##protocol", ggWave->getTxProtocols()[settings.protocolId].name)) { + for (int i = 0; i < (int) ggWave->getTxProtocols().size(); ++i) { + const bool isSelected = (settings.protocolId == i); + if (ImGui::Selectable(ggWave->getTxProtocols()[i].name, isSelected)) { + settings.protocolId = i; + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + ImGui::EndChild(); + } + + if (windowId == WindowId::Messages) { + const float messagesInputHeight = ImGui::GetTextLineHeightWithSpacing(); + const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x; + float messagesHistoryHeigth = messagesHistoryHeigthMax; + + // no automatic screen resize support for iOS #ifdef IOS - if (displaySize.x < displaySize.y) { - if (isTextInput) { - messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + if (displaySize.x < displaySize.y) { + if (isTextInput) { + messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + } else { + messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + } } else { - messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + if (isTextInput) { + messagesHistoryHeigth -= 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + } else { + messagesHistoryHeigth -= 0.5f*displaySize.y - 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + } } - } else { - if (isTextInput) { - messagesHistoryHeigth -= 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; - } else { - messagesHistoryHeigth -= 0.5f*displaySize.y - 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; - } - } #endif - ImGui::BeginChild("Messages:history", { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth }, true); + ImGui::BeginChild("Messages:history", { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth }, true); - for (int i = 0; i < (int) messageHistory.size(); ++i) { - ImGui::PushID(i); - const auto & message = messageHistory[i]; - if (message.received) { - ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "[%s] Recv:", ::toTimeString(message.timestamp)); - ImGui::SameLine(); - if (ImGui::SmallButton("Resend")) { - buffer.inputUI.update = true; - buffer.inputUI.message = { false, std::time(nullptr), message.data, "" }; + for (int i = 0; i < (int) messageHistory.size(); ++i) { + ImGui::PushID(i); + const auto & message = messageHistory[i]; + if (message.received) { + ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "[%s] Recv (%s):", ::toTimeString(message.timestamp), ggWave->getTxProtocols()[message.protocolId].name); + ImGui::SameLine(); + if (ImGui::SmallButton("Resend")) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), message.data, message.protocolId, settings.volume }; - messageHistory.push_back(buffer.inputUI.message); + messageHistory.push_back(buffer.inputUI.message); + } + ImGui::Text("%s", message.data.c_str()); + } else { + ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "[%s] Sent (%s):", ::toTimeString(message.timestamp), ggWave->getTxProtocols()[message.protocolId].name); + ImGui::SameLine(); + if (ImGui::SmallButton("Resend")) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), message.data, message.protocolId, settings.volume }; + + messageHistory.push_back(buffer.inputUI.message); + } + ImGui::Text("%s", message.data.c_str()); } - ImGui::Text("%s", message.data.c_str()); - } else { - ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "[%s] Sent:", ::toTimeString(message.timestamp)); - ImGui::SameLine(); - if (ImGui::SmallButton("Resend")) { - buffer.inputUI.update = true; - buffer.inputUI.message = { false, std::time(nullptr), message.data, "" }; - - messageHistory.push_back(buffer.inputUI.message); - } - ImGui::Text("%s", message.data.c_str()); + ImGui::Text("%s", ""); + ImGui::PopID(); + } + + if (scrollMessagesToBottom) { + ImGui::SetScrollHereY(); + scrollMessagesToBottom = false; + } + + ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; + ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); + ImGui::EndChild(); + + if (doInputFocus) { + ImGui::SetKeyboardFocusHere(); + doInputFocus = false; + } + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x); + ImGui::InputText("##Messages:Input", inputBuf, 256, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + if (ImGui::IsItemActive() && isTextInput == false) { + SDL_StartTextInput(); + isTextInput = true; + tStartInput = ImGui::GetTime(); + } + bool requestStopTextInput = false; + if (ImGui::IsItemDeactivated()) { + requestStopTextInput = true; + } + ImGui::SameLine(); + if (ImGui::Button(sendButtonText) && inputBuf[0] != 0) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), std::string(inputBuf), settings.protocolId, settings.volume }; + + messageHistory.push_back(buffer.inputUI.message); + + inputBuf[0] = 0; + doInputFocus = true; + } + if (!ImGui::IsItemHovered() && requestStopTextInput) { + SDL_StopTextInput(); + isTextInput = false; + tEndInput = ImGui::GetTime(); } - ImGui::Text("%s", ""); - ImGui::PopID(); } - if (scrollMessagesToBottom) { - ImGui::SetScrollHereY(); - scrollMessagesToBottom = false; - } - - ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; - ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); - ImGui::EndChild(); - - if (doInputFocus) { - ImGui::SetKeyboardFocusHere(); - doInputFocus = false; - } - ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x); - ImGui::InputText("##Messages:Input", inputBuf, 256, ImGuiInputTextFlags_EnterReturnsTrue); - ImGui::PopItemWidth(); - if (ImGui::IsItemActive() && isTextInput == false) { - SDL_StartTextInput(); - isTextInput = true; - tStartInput = ImGui::GetTime(); - } - bool requestStopTextInput = false; - if (ImGui::IsItemDeactivated()) { - requestStopTextInput = true; - } - ImGui::SameLine(); - if (ImGui::Button(sendButtonText) && inputBuf[0] != 0) { - buffer.inputUI.update = true; - buffer.inputUI.message = { false, std::time(nullptr), std::string(inputBuf), "" }; - - messageHistory.push_back(buffer.inputUI.message); - - inputBuf[0] = 0; - doInputFocus = true; - } - if (!ImGui::IsItemHovered() && requestStopTextInput) { - SDL_StopTextInput(); - isTextInput = false; - tEndInput = ImGui::GetTime(); + if (windowId == WindowId::Commands) { + ImGui::BeginChild("Commands:main", ImGui::GetContentRegionAvail(), true); + ImGui::Text("Todo"); + ImGui::EndChild(); } ImGui::End(); diff --git a/include/ggwave/ggwave.h b/include/ggwave/ggwave.h index 4a1e27c..c781b55 100644 --- a/include/ggwave/ggwave.h +++ b/include/ggwave/ggwave.h @@ -1,9 +1,10 @@ #pragma once #include -#include #include #include +#include +#include namespace RS { class ReedSolomon; @@ -11,18 +12,33 @@ class ReedSolomon; class GGWave { public: - enum TxMode { - FixedLength = 0, - VariableLength, - }; - static constexpr auto kMaxSamplesPerFrame = 1024; static constexpr auto kMaxDataBits = 256; static constexpr auto kMaxDataSize = 256; static constexpr auto kMaxLength = 140; static constexpr auto kMaxSpectrumHistory = 4; - static constexpr auto kMaxRecordedFrames = 64*10; - static constexpr auto kDefaultFixedLength = 82; + static constexpr auto kMaxRecordedFrames = 1024; + + struct TxProtocol { + const char * name; + + int freqStart; + int framesPerTx; + int bytesPerTx; + + int nDataBitsPerTx() const { return 8*bytesPerTx; } + }; + + using TxProtocols = std::vector; + + const TxProtocols kTxProtocols { + { "Normal", 40, 9, 3, }, + { "Fast", 40, 6, 3, }, + { "Fastest", 40, 3, 3, }, + { "[U] Normal", 320, 9, 3, }, + { "[U] Fast", 320, 6, 3, }, + { "[U] Fastest", 320, 3, 3, }, + }; using AmplitudeData = std::array; using AmplitudeData16 = std::array; @@ -34,135 +50,105 @@ public: using CBDequeueAudio = std::function; GGWave( - int aSampleRateIn, - int aSampleRateOut, - int aSamplesPerFrame, - int aSampleSizeBytesIn, - int aSampleSizeBytesOut); + int sampleRateIn, + int sampleRateOut, + int samplesPerFrame, + int sampleSizeBytesIn, + int sampleSizeBytesOut); ~GGWave(); - void setTxMode(TxMode aTxMode) { txMode = aTxMode; } - - bool setParameters( - int aParamFreqDelta, - int aParamFreqStart, - int aParamFramesPerTx, - int aParamBytesPerTx, - int aParamVolume); - - bool init(int textLength, const char * stext); - + bool init(int textLength, const char * stext, const TxProtocol & aProtocol, const int volume); void send(const CBQueueAudio & cbQueueAudio); void receive(const CBDequeueAudio & CBDequeueAudio); - const bool & getHasData() const { return hasData; } + const bool & hasTxData() const { return m_hasNewTxData; } - const int & getFramesToRecord() const { return framesToRecord; } - const int & getFramesLeftToRecord() const { return framesLeftToRecord; } - const int & getFramesToAnalyze() const { return framesToAnalyze; } - const int & getFramesLeftToAnalyze() const { return framesLeftToAnalyze; } - const int & getSamplesPerFrame() const { return samplesPerFrame; } - const int & getSampleSizeBytesIn() const { return sampleSizeBytesIn; } - const int & getSampleSizeBytesOut() const { return sampleSizeBytesOut; } - const int & getTotalBytesCaptured() const { return totalBytesCaptured; } + const int & getFramesToRecord() const { return m_framesToRecord; } + const int & getFramesLeftToRecord() const { return m_framesLeftToRecord; } + const int & getFramesToAnalyze() const { return m_framesToAnalyze; } + const int & getFramesLeftToAnalyze() const { return m_framesLeftToAnalyze; } + const int & getSamplesPerFrame() const { return m_samplesPerFrame; } + const int & getSampleSizeBytesIn() const { return m_sampleSizeBytesIn; } + const int & getSampleSizeBytesOut() const { return m_sampleSizeBytesOut; } - const float & getSampleRateIn() const { return sampleRateIn; } - const float & getAverageRxTime_ms() const { return averageRxTime_ms; } + const float & getSampleRateIn() const { return m_sampleRateIn; } + const float & getSampleRateOut() const { return m_sampleRateOut; } - const TxRxData & getRxData() const { return rxData; } + const TxProtocol & getDefultTxProtocol() const { return kTxProtocols[1]; } + const TxProtocols & getTxProtocols() const { return kTxProtocols; } - int takeRxData(TxRxData & dst) { - if (lastRxDataLength == 0) return 0; - - auto res = lastRxDataLength; - lastRxDataLength = 0; - dst = rxData; - - return res; - } + const TxRxData & getRxData() const { return m_rxData; } + const TxProtocol & getRxProtocol() const { return m_rxProtocol; } + const int & getRxProtocolId() const { return m_rxProtocolId; } + int takeRxData(TxRxData & dst); private: - int nIterations; + int maxFramesPerTx() const; + int minBytesPerTx() const; - int paramFreqDelta = 6; - int paramFreqStart = 40; - int paramFramesPerTx = 6; - int paramBytesPerTx = 2; - int paramECCBytesPerTx = 32; // used for fixed-length Tx - int paramVolume = 10; + double bitFreq(const TxProtocol & p, int bit) const { + return m_hzPerSample*p.freqStart + m_freqDelta_hz*bit; + } + + const float m_sampleRateIn; + const float m_sampleRateOut; + const int m_samplesPerFrame; + const float m_isamplesPerFrame; + const int m_sampleSizeBytesIn; + const int m_sampleSizeBytesOut; + + const float m_hzPerSample; + const float m_ihzPerSample; + + const int m_freqDelta_bin; + const float m_freqDelta_hz; + + const int m_nBitsInMarker; + const int m_nMarkerFrames; + const int m_nPostMarkerFrames; + const int m_encodedDataOffset; // Rx - bool receivingData; - bool analyzingData; - bool hasNewRxData = false; + bool m_receivingData; + bool m_analyzingData; - int nCalls = 0; - int recvDuration_frames; - int totalBytesCaptured; - int lastRxDataLength = 0; + int m_markerFreqStart; + int m_recvDuration_frames; - float tSum_ms = 0.0f; - float averageRxTime_ms = 0.0; + int m_framesLeftToAnalyze; + int m_framesLeftToRecord; + int m_framesToAnalyze; + int m_framesToRecord; - std::array fftIn; - std::array, kMaxSamplesPerFrame> fftOut; + std::array m_fftIn; // real + std::array m_fftOut; // complex - AmplitudeData sampleAmplitude; - SpectrumData sampleSpectrum; + AmplitudeData m_sampleAmplitude; + SpectrumData m_sampleSpectrum; - TxRxData rxData; - TxRxData txData; - TxRxData txDataEncoded; + bool m_hasNewRxData; + int m_lastRxDataLength; + TxRxData m_rxData; + TxProtocol m_rxProtocol; + int m_rxProtocolId; - int historyId = 0; - AmplitudeData sampleAmplitudeAverage; - std::array sampleAmplitudeHistory; + int m_historyId = 0; + AmplitudeData m_sampleAmplitudeAverage; + std::array m_sampleAmplitudeHistory; - RecordedData recordedAmplitude; + RecordedData m_recordedAmplitude; // Tx - bool hasData; + bool m_hasNewTxData; + int m_nECCBytesPerTx; + int m_sendDataLength; + float m_sendVolume; - float freqDelta_hz; - float freqStart_hz; - float hzPerFrame; - float ihzPerFrame; - float isamplesPerFrame; - float sampleRateIn; - float sampleRateOut; - float sendVolume; + int m_txDataLength; + TxRxData m_txData; + TxRxData m_txDataEncoded; - int frameId; - int framesLeftToAnalyze; - int framesLeftToRecord; - int framesPerTx; - int framesToAnalyze; - int framesToRecord; - int freqDelta_bin = 1; - int nBitsInMarker; - int nDataBitsPerTx; - int nECCBytesPerTx; - int nMarkerFrames; - int nPostMarkerFrames; - int sampleSizeBytesIn; - int sampleSizeBytesOut; - int samplesPerFrame; - int sendDataLength; + TxProtocol m_txProtocol; - std::string textToSend; - - TxMode txMode = TxMode::FixedLength; - - AmplitudeData outputBlock; - AmplitudeData16 outputBlock16; - - std::array dataBits; - std::array phaseOffsets; - std::array dataFreqs_hz; - - std::array bit1Amplitude; - std::array bit0Amplitude; - - RS::ReedSolomon * rsData = nullptr; - RS::ReedSolomon * rsLength = nullptr; + std::unique_ptr m_rsLength; }; diff --git a/src/ggwave.cpp b/src/ggwave.cpp index ce0c086..dd7a7f8 100644 --- a/src/ggwave.cpp +++ b/src/ggwave.cpp @@ -28,31 +28,48 @@ int reverse(int N, int n) { return p; } -void ordina(std::complex* f1, int N) { - std::complex f2[GGWave::kMaxSamplesPerFrame]; - for(int i = 0; i < N; i++) - f2[i] = f1[reverse(N, i)]; - for(int j = 0; j < N; j++) - f1[j] = f2[j]; +void ordina(float * f1, int N) { + 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(std::complex* f, int N) { +void transform(float * f, int N) { ordina(f, N); //first: reverse order - std::complex *W; - W = (std::complex *)malloc(N / 2 * sizeof(std::complex)); - W[1] = std::polar(1., -2. * M_PI / N); - W[0] = 1; - for(int i = 2; i < N / 2; i++) - W[i] = pow(W[1], i); + 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)) { - std::complex temp = f[i]; - std::complex Temp = W[(i * a) % (n * a)] * f[i + n]; - f[i] = temp + Temp; - f[i + n] = temp - Temp; + 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; @@ -61,16 +78,18 @@ void transform(std::complex* f, int N) { free(W); } -void FFT(std::complex* f, int N, float d) { +void FFT(float * f, int N, float d) { transform(f, N); - for(int i = 0; i < N; i++) - f[i] *= d; //multiplying by step + for (int i = 0; i < N; i++) { + f[2*i + 0] *= d; + f[2*i + 1] *= d; + } } -void FFT(float * src, std::complex* dst, int N, float d) { +void FFT(float * src, float * dst, int N, float d) { for (int i = 0; i < N; ++i) { - dst[i].real(src[i]); - dst[i].imag(0); + dst[2*i + 0] = src[i]; + dst[2*i + 1] = 0.0f; } FFT(dst, N, d); } @@ -109,86 +128,96 @@ int getECCBytesForLength(int len) { } GGWave::GGWave( - int aSampleRateIn, - int aSampleRateOut, - int aSamplesPerFrame, - int aSampleSizeBytesIn, - int aSampleSizeBytesOut) { - - sampleRateIn = aSampleRateIn; - sampleRateOut = aSampleRateOut; - samplesPerFrame = aSamplesPerFrame; - sampleSizeBytesIn = aSampleSizeBytesIn; - sampleSizeBytesOut = aSampleSizeBytesOut; - - init(0, ""); + int sampleRateIn, + int sampleRateOut, + int samplesPerFrame, + int sampleSizeBytesIn, + int sampleSizeBytesOut) : + m_sampleRateIn(sampleRateIn), + m_sampleRateOut(sampleRateOut), + m_samplesPerFrame(samplesPerFrame), + m_isamplesPerFrame(1.0f/m_samplesPerFrame), + m_sampleSizeBytesIn(sampleSizeBytesIn), + m_sampleSizeBytesOut(sampleSizeBytesOut), + m_hzPerSample(m_sampleRateIn/samplesPerFrame), + m_ihzPerSample(1.0f/m_hzPerSample), + m_freqDelta_bin(1), + m_freqDelta_hz(2*m_hzPerSample), + m_nBitsInMarker(16), + m_nMarkerFrames(16), + m_nPostMarkerFrames(0), + m_encodedDataOffset(3), + m_rsLength(new RS::ReedSolomon(1, m_encodedDataOffset - 1)) +{ + init(0, "", getDefultTxProtocol(), 0); } GGWave::~GGWave() { - if (rsData) delete rsData; - if (rsLength) delete rsLength; } -bool GGWave::setParameters( - int aParamFreqDelta, - int aParamFreqStart, - int aParamFramesPerTx, - int aParamBytesPerTx, - int aParamVolume) { - - paramFreqDelta = aParamFreqDelta; - paramFreqStart = aParamFreqStart; - paramFramesPerTx = aParamFramesPerTx; - paramBytesPerTx = aParamBytesPerTx; - paramVolume = aParamVolume; - - return true; -} - -bool GGWave::init(int textLength, const char * stext) { +bool GGWave::init(int textLength, const char * stext, const TxProtocol & aProtocol, const int volume) { if (textLength > kMaxLength) { printf("Truncating data from %d to 140 bytes\n", textLength); textLength = kMaxLength; } + m_txProtocol = aProtocol; + m_txDataLength = textLength; + m_sendVolume = ((double)(volume))/100.0f; + const uint8_t * text = reinterpret_cast(stext); - frameId = 0; - nIterations = 0; - hasData = false; - isamplesPerFrame = 1.0f/samplesPerFrame; - sendVolume = ((double)(paramVolume))/100.0f; - hzPerFrame = sampleRateIn/samplesPerFrame; - ihzPerFrame = 1.0/hzPerFrame; - framesPerTx = paramFramesPerTx; + m_hasNewTxData = false; + m_txData.fill(0); + m_txDataEncoded.fill(0); - nDataBitsPerTx = paramBytesPerTx*8; - nECCBytesPerTx = (txMode == TxMode::FixedLength) ? paramECCBytesPerTx : getECCBytesForLength(textLength); + if (m_txDataLength > 0) { + m_txData[0] = m_txDataLength; + for (int i = 0; i < m_txDataLength; ++i) m_txData[i + 1] = text[i]; - framesToAnalyze = 0; - framesLeftToAnalyze = 0; - framesToRecord = 0; - framesLeftToRecord = 0; - nBitsInMarker = 16; - nMarkerFrames = 16; - nPostMarkerFrames = 0; - sendDataLength = (txMode == TxMode::FixedLength) ? kDefaultFixedLength : textLength + 3; - - freqDelta_bin = paramFreqDelta/2; - freqDelta_hz = hzPerFrame*paramFreqDelta; - freqStart_hz = hzPerFrame*paramFreqStart; - if (paramFreqDelta == 1) { - freqDelta_bin = 1; - freqDelta_hz *= 2; + m_hasNewTxData = true; } - outputBlock.fill(0); + // Rx + m_receivingData = false; + m_analyzingData = false; - txData.fill(0); - txDataEncoded.fill(0); + m_framesToAnalyze = 0; + m_framesLeftToAnalyze = 0; + m_framesToRecord = 0; + m_framesLeftToRecord = 0; + + m_sampleAmplitude.fill(0); + m_sampleSpectrum.fill(0); + for (auto & s : m_sampleAmplitudeHistory) { + s.fill(0); + } + + m_rxData.fill(0); + + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_fftOut[2*i + 0] = 0.0f; + m_fftOut[2*i + 1] = 0.0f; + } + + return true; +} + +void GGWave::send(const CBQueueAudio & cbQueueAudio) { + int samplesPerFrameOut = (m_sampleRateOut/m_sampleRateIn)*m_samplesPerFrame; + if (m_sampleRateOut != m_sampleRateIn) { + printf("Resampling from %d Hz to %d Hz\n", (int) m_sampleRateIn, (int) m_sampleRateOut); + } + + int frameId = 0; + + AmplitudeData outputBlock; + AmplitudeData16 outputBlock16; + + std::array phaseOffsets; for (int k = 0; k < (int) phaseOffsets.size(); ++k) { - phaseOffsets[k] = (M_PI*k)/(nDataBitsPerTx); + phaseOffsets[k] = (M_PI*k)/(m_txProtocol.nDataBitsPerTx()); } // note : what is the purpose of this shuffle ? I forgot .. :( @@ -197,183 +226,124 @@ bool GGWave::init(int textLength, const char * stext) { std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g); + std::array dataBits; + + std::array bit1Amplitude; + std::array bit0Amplitude; + for (int k = 0; k < (int) dataBits.size(); ++k) { - double freq = freqStart_hz + freqDelta_hz*k; - dataFreqs_hz[k] = freq; + double freq = bitFreq(m_txProtocol, k); double phaseOffset = phaseOffsets[k]; - double curHzPerFrame = sampleRateOut/samplesPerFrame; - double curIHzPerFrame = 1.0/curHzPerFrame; - for (int i = 0; i < samplesPerFrame; i++) { + double curHzPerSample = m_sampleRateOut/m_samplesPerFrame; + double curIHzPerSample = 1.0/curHzPerSample; + for (int i = 0; i < m_samplesPerFrame; i++) { double curi = i; - bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*(freq*curIHzPerFrame) + phaseOffset); + bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*(freq*curIHzPerSample) + phaseOffset); } - for (int i = 0; i < samplesPerFrame; i++) { + for (int i = 0; i < m_samplesPerFrame; i++) { double curi = i; - bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*((freq + hzPerFrame*freqDelta_bin)*curIHzPerFrame) + phaseOffset); + bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*((freq + m_hzPerSample*m_freqDelta_bin)*curIHzPerSample) + phaseOffset); } } - if (rsData) delete rsData; - if (rsLength) delete rsLength; + m_nECCBytesPerTx = getECCBytesForLength(m_txDataLength); + m_sendDataLength = m_txDataLength + m_encodedDataOffset; - if (txMode == TxMode::FixedLength) { - rsData = new RS::ReedSolomon(kDefaultFixedLength, nECCBytesPerTx); - rsLength = nullptr; - } else { - rsData = new RS::ReedSolomon(textLength, nECCBytesPerTx); - rsLength = new RS::ReedSolomon(1, 2); - } + RS::ReedSolomon rsData = RS::ReedSolomon(m_txDataLength, m_nECCBytesPerTx); - if (textLength > 0) { - if (txMode == TxMode::FixedLength) { - for (int i = 0; i < textLength; ++i) txData[i] = text[i]; - rsData->Encode(txData.data(), txDataEncoded.data()); - } else { - txData[0] = textLength; - for (int i = 0; i < textLength; ++i) txData[i + 1] = text[i]; - rsData->Encode(txData.data() + 1, txDataEncoded.data() + 3); - rsLength->Encode(txData.data(), txDataEncoded.data()); - } + m_rsLength->Encode(m_txData.data(), m_txDataEncoded.data()); + rsData.Encode(m_txData.data() + 1, m_txDataEncoded.data() + m_encodedDataOffset); - hasData = true; - } - - // Rx - receivingData = false; - analyzingData = false; - - sampleAmplitude.fill(0); - - sampleSpectrum.fill(0); - for (auto & s : sampleAmplitudeHistory) { - s.fill(0); - } - - rxData.fill(0); - - for (int i = 0; i < samplesPerFrame; ++i) { - fftOut[i].real(0.0f); - fftOut[i].imag(0.0f); - } - - return true; -} - -void GGWave::send(const CBQueueAudio & cbQueueAudio) { - int samplesPerFrameOut = (sampleRateOut/sampleRateIn)*samplesPerFrame; - if (sampleRateOut != sampleRateIn) { - printf("Resampling from %d Hz to %d Hz\n", (int) sampleRateIn, (int) sampleRateOut); - } - - while (hasData) { - int nBytesPerTx = nDataBitsPerTx/8; + while (m_hasNewTxData) { std::fill(outputBlock.begin(), outputBlock.end(), 0.0f); - std::uint16_t nFreq = 0; - if (sampleRateOut != sampleRateIn) { - for (int k = 0; k < nDataBitsPerTx; ++k) { - double freq = freqStart_hz + freqDelta_hz*k; + if (m_sampleRateOut != m_sampleRateIn) { + for (int k = 0; k < m_txProtocol.nDataBitsPerTx(); ++k) { + double freq = bitFreq(m_txProtocol, k); double phaseOffset = phaseOffsets[k]; - double curHzPerFrame = sampleRateOut/samplesPerFrame; - double curIHzPerFrame = 1.0/curHzPerFrame; + double curHzPerSample = m_sampleRateOut/m_samplesPerFrame; + double curIHzPerSample = 1.0/curHzPerSample; for (int i = 0; i < samplesPerFrameOut; i++) { double curi = (i + frameId*samplesPerFrameOut); - bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*(freq*curIHzPerFrame) + phaseOffset); + bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*(freq*curIHzPerSample) + phaseOffset); } for (int i = 0; i < samplesPerFrameOut; i++) { double curi = (i + frameId*samplesPerFrameOut); - bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*((freq + hzPerFrame*freqDelta_bin)*curIHzPerFrame) + phaseOffset); + bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*((freq + m_hzPerSample*m_freqDelta_bin)*curIHzPerSample) + phaseOffset); } } } - if (frameId < nMarkerFrames) { - nFreq = nBitsInMarker; + std::uint16_t nFreq = 0; + if (frameId < m_nMarkerFrames) { + nFreq = m_nBitsInMarker; - for (int i = 0; i < nBitsInMarker; ++i) { + for (int i = 0; i < m_nBitsInMarker; ++i) { if (i%2 == 0) { - ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames); + ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, frameId, m_nMarkerFrames); } else { - ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames); + ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, frameId, m_nMarkerFrames); } } - } else if (frameId < nMarkerFrames + nPostMarkerFrames) { - nFreq = nBitsInMarker; + } else if (frameId < m_nMarkerFrames + m_nPostMarkerFrames) { + nFreq = m_nBitsInMarker; - for (int i = 0; i < nBitsInMarker; ++i) { + for (int i = 0; i < m_nBitsInMarker; ++i) { if (i%2 == 0) { - ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames); + ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, frameId - m_nMarkerFrames, m_nPostMarkerFrames); } else { - ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames); + ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, frameId - m_nMarkerFrames, m_nPostMarkerFrames); } } } else if (frameId < - (nMarkerFrames + nPostMarkerFrames) + - ((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx) { - int dataOffset = frameId - nMarkerFrames - nPostMarkerFrames; - int cycleModMain = dataOffset%framesPerTx; - dataOffset /= framesPerTx; - dataOffset *= nBytesPerTx; + (m_nMarkerFrames + m_nPostMarkerFrames) + + ((m_sendDataLength + m_nECCBytesPerTx)/m_txProtocol.bytesPerTx + 2)*m_txProtocol.framesPerTx) { + int dataOffset = frameId - m_nMarkerFrames - m_nPostMarkerFrames; + int cycleModMain = dataOffset%m_txProtocol.framesPerTx; + dataOffset /= m_txProtocol.framesPerTx; + dataOffset *= m_txProtocol.bytesPerTx; dataBits.fill(0); - if (paramFreqDelta > 1) { - for (int j = 0; j < nBytesPerTx; ++j) { - for (int i = 0; i < 8; ++i) { - dataBits[j*8 + i] = txDataEncoded[dataOffset + j] & (1 << i); - } + for (int j = 0; j < m_txProtocol.bytesPerTx; ++j) { + { + uint8_t d = m_txDataEncoded[dataOffset + j] & 15; + dataBits[(2*j + 0)*16 + d] = 1; } - - for (int k = 0; k < nDataBitsPerTx; ++k) { - ++nFreq; - if (dataBits[k] == false) { - ::addAmplitudeSmooth(bit0Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); - continue; - } - ::addAmplitudeSmooth(bit1Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); - } - } else { - for (int j = 0; j < nBytesPerTx; ++j) { - { - uint8_t d = txDataEncoded[dataOffset + j] & 15; - dataBits[(2*j + 0)*16 + d] = 1; - } - { - uint8_t d = txDataEncoded[dataOffset + j] & 240; - dataBits[(2*j + 1)*16 + (d >> 4)] = 1; - } - } - - for (int k = 0; k < 2*nBytesPerTx*16; ++k) { - if (dataBits[k] == 0) continue; - - ++nFreq; - if (k%2) { - ::addAmplitudeSmooth(bit0Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); - } else { - ::addAmplitudeSmooth(bit1Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); - } + { + uint8_t d = m_txDataEncoded[dataOffset + j] & 240; + dataBits[(2*j + 1)*16 + (d >> 4)] = 1; } } - } else if (txMode == TxMode::VariableLength && frameId < - (nMarkerFrames + nPostMarkerFrames) + - ((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx + - (nMarkerFrames)) { - nFreq = nBitsInMarker; - int fId = frameId - ((nMarkerFrames + nPostMarkerFrames) + ((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx); - for (int i = 0; i < nBitsInMarker; ++i) { - if (i%2 == 0) { - addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, fId, nMarkerFrames); + for (int k = 0; k < 2*m_txProtocol.bytesPerTx*16; ++k) { + if (dataBits[k] == 0) continue; + + ++nFreq; + if (k%2) { + ::addAmplitudeSmooth(bit0Amplitude[k/2], outputBlock, m_sendVolume, 0, samplesPerFrameOut, cycleModMain, m_txProtocol.framesPerTx); } else { - addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, fId, nMarkerFrames); + ::addAmplitudeSmooth(bit1Amplitude[k/2], outputBlock, m_sendVolume, 0, samplesPerFrameOut, cycleModMain, m_txProtocol.framesPerTx); + } + } + } else if (frameId < + (m_nMarkerFrames + m_nPostMarkerFrames) + + ((m_sendDataLength + m_nECCBytesPerTx)/m_txProtocol.bytesPerTx + 2)*m_txProtocol.framesPerTx + + (m_nMarkerFrames)) { + nFreq = m_nBitsInMarker; + + int fId = frameId - ((m_nMarkerFrames + m_nPostMarkerFrames) + ((m_sendDataLength + m_nECCBytesPerTx)/m_txProtocol.bytesPerTx + 2)*m_txProtocol.framesPerTx); + for (int i = 0; i < m_nBitsInMarker; ++i) { + if (i%2 == 0) { + addAmplitudeSmooth(bit0Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, fId, m_nMarkerFrames); + } else { + addAmplitudeSmooth(bit1Amplitude[i], outputBlock, m_sendVolume, 0, samplesPerFrameOut, fId, m_nMarkerFrames); } } } else { - textToSend = ""; - hasData = false; + m_hasNewTxData = false; } if (nFreq == 0) nFreq = 1; @@ -389,264 +359,298 @@ void GGWave::send(const CBQueueAudio & cbQueueAudio) { ++frameId; } - cbQueueAudio(outputBlock16.data(), frameId*samplesPerFrameOut*sampleSizeBytesOut); + + cbQueueAudio(outputBlock16.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut); } void GGWave::receive(const CBDequeueAudio & CBDequeueAudio) { - auto tCallStart = std::chrono::high_resolution_clock::now(); - - while (hasData == false) { + while (m_hasNewTxData == false) { // read capture data // // todo : support for non-float input - auto nBytesRecorded = CBDequeueAudio(sampleAmplitude.data(), samplesPerFrame*sampleSizeBytesIn); + auto nBytesRecorded = CBDequeueAudio(m_sampleAmplitude.data(), m_samplesPerFrame*m_sampleSizeBytesIn); if (nBytesRecorded != 0) { { - sampleAmplitudeHistory[historyId] = sampleAmplitude; + m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude; - if (++historyId >= kMaxSpectrumHistory) { - historyId = 0; + if (++m_historyId >= kMaxSpectrumHistory) { + m_historyId = 0; } - if (historyId == 0 && (receivingData == false || (receivingData && txMode == TxMode::VariableLength))) { - std::fill(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.end(), 0.0f); - for (auto & s : sampleAmplitudeHistory) { - for (int i = 0; i < samplesPerFrame; ++i) { - sampleAmplitudeAverage[i] += s[i]; + if (m_historyId == 0 && (m_receivingData == false || m_receivingData)) { + std::fill(m_sampleAmplitudeAverage.begin(), m_sampleAmplitudeAverage.end(), 0.0f); + for (auto & s : m_sampleAmplitudeHistory) { + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_sampleAmplitudeAverage[i] += s[i]; } } float norm = 1.0f/kMaxSpectrumHistory; - for (int i = 0; i < samplesPerFrame; ++i) { - sampleAmplitudeAverage[i] *= norm; + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_sampleAmplitudeAverage[i] *= norm; } // calculate spectrum - std::copy(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.begin() + samplesPerFrame, fftIn.data()); + std::copy(m_sampleAmplitudeAverage.begin(), m_sampleAmplitudeAverage.begin() + m_samplesPerFrame, m_fftIn.data()); - FFT(fftIn.data(), fftOut.data(), samplesPerFrame, 1.0); + FFT(m_fftIn.data(), m_fftOut.data(), m_samplesPerFrame, 1.0); double fsum = 0.0; - for (int i = 0; i < samplesPerFrame; ++i) { - sampleSpectrum[i] = (fftOut[i].real()*fftOut[i].real() + fftOut[i].imag()*fftOut[i].imag()); - fsum += sampleSpectrum[i]; + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_sampleSpectrum[i] = (m_fftOut[2*i + 0]*m_fftOut[2*i + 0] + m_fftOut[2*i + 1]*m_fftOut[2*i + 1]); + fsum += m_sampleSpectrum[i]; } - for (int i = 1; i < samplesPerFrame/2; ++i) { - sampleSpectrum[i] += sampleSpectrum[samplesPerFrame - i]; - } - - if (fsum < 1e-10) { - totalBytesCaptured = 0; - } else { - totalBytesCaptured += nBytesRecorded; + for (int i = 1; i < m_samplesPerFrame/2; ++i) { + m_sampleSpectrum[i] += m_sampleSpectrum[m_samplesPerFrame - i]; } } - if (framesLeftToRecord > 0) { - std::copy(sampleAmplitude.begin(), - sampleAmplitude.begin() + samplesPerFrame, - recordedAmplitude.data() + (framesToRecord - framesLeftToRecord)*samplesPerFrame); + if (m_framesLeftToRecord > 0) { + std::copy(m_sampleAmplitude.begin(), + m_sampleAmplitude.begin() + m_samplesPerFrame, + m_recordedAmplitude.data() + (m_framesToRecord - m_framesLeftToRecord)*m_samplesPerFrame); - if (--framesLeftToRecord <= 0) { - std::fill(sampleSpectrum.begin(), sampleSpectrum.end(), 0.0f); - analyzingData = true; + if (--m_framesLeftToRecord <= 0) { + m_analyzingData = true; } } } - if (analyzingData) { - int nBytesPerTx = nDataBitsPerTx/8; - int stepsPerFrame = 16; - int step = samplesPerFrame/stepsPerFrame; + if (m_analyzingData) { + printf("Analyzing captured data ..\n"); + auto tStart = std::chrono::high_resolution_clock::now(); - int offsetStart = 0; + const int stepsPerFrame = 16; + const int step = m_samplesPerFrame/stepsPerFrame; - framesToAnalyze = nMarkerFrames*stepsPerFrame; - framesLeftToAnalyze = framesToAnalyze; + int lastRSLength = -1; + std::unique_ptr rsData; bool isValid = false; - for (int ii = nMarkerFrames*stepsPerFrame - 1; ii >= nMarkerFrames*stepsPerFrame/2; --ii) { - offsetStart = ii; - bool knownLength = txMode == TxMode::FixedLength; - int encodedOffset = (txMode == TxMode::FixedLength) ? 0 : 3; + for (int rxProtocolId = 0; rxProtocolId < (int) kTxProtocols.size(); ++rxProtocolId) { + const auto & rxProtocol = kTxProtocols[rxProtocolId]; - for (int itx = 0; itx < 1024; ++itx) { - int offsetTx = offsetStart + itx*framesPerTx*stepsPerFrame; - if (offsetTx >= recvDuration_frames*stepsPerFrame) { - break; - } + // skip Rx protocol if start frequency is different from detected one + if (rxProtocol.freqStart != m_markerFreqStart) { + continue; + } - std::copy( - recordedAmplitude.begin() + offsetTx*step, - recordedAmplitude.begin() + offsetTx*step + samplesPerFrame, fftIn.data()); + std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0.0f); - for (int k = 1; k < framesPerTx-1; ++k) { - for (int i = 0; i < samplesPerFrame; ++i) { - fftIn[i] += recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i]; + m_framesToAnalyze = m_nMarkerFrames*stepsPerFrame; + m_framesLeftToAnalyze = m_framesToAnalyze; + for (int ii = m_nMarkerFrames*stepsPerFrame - 1; ii >= m_nMarkerFrames*stepsPerFrame/2; --ii) { + bool knownLength = false; + + const int offsetStart = ii; + for (int itx = 0; itx < 1024; ++itx) { + int offsetTx = offsetStart + itx*rxProtocol.framesPerTx*stepsPerFrame; + if (offsetTx >= m_recvDuration_frames*stepsPerFrame || (itx + 1)*rxProtocol.bytesPerTx >= (int) m_txDataEncoded.size()) { + break; } - } - FFT(fftIn.data(), fftOut.data(), samplesPerFrame, 1.0); + std::copy( + m_recordedAmplitude.begin() + offsetTx*step, + m_recordedAmplitude.begin() + offsetTx*step + m_samplesPerFrame, m_fftIn.data()); - for (int i = 0; i < samplesPerFrame; ++i) { - sampleSpectrum[i] = (fftOut[i].real()*fftOut[i].real() + fftOut[i].imag()*fftOut[i].imag()); - } - for (int i = 1; i < samplesPerFrame/2; ++i) { - sampleSpectrum[i] += sampleSpectrum[samplesPerFrame - i]; - } - - uint8_t curByte = 0; - if (paramFreqDelta > 1) { - for (int i = 0; i < nDataBitsPerTx; ++i) { - int k = i%8; - int bin = std::round(dataFreqs_hz[i]*ihzPerFrame); - if (sampleSpectrum[bin] > 1*sampleSpectrum[bin + freqDelta_bin]) { - curByte += 1 << k; - } else if (sampleSpectrum[bin + freqDelta_bin] > 1*sampleSpectrum[bin]) { - } else { - } - if (k == 7) { - txDataEncoded[itx*nBytesPerTx + i/8] = curByte; - curByte = 0; + for (int k = 1; k < rxProtocol.framesPerTx - 1; ++k) { + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_fftIn[i] += m_recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i]; } } - } else { - for (int i = 0; i < 2*nBytesPerTx; ++i) { - int bin = std::round(dataFreqs_hz[0]*ihzPerFrame) + i*16; + + FFT(m_fftIn.data(), m_fftOut.data(), m_samplesPerFrame, 1.0); + + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_sampleSpectrum[i] = (m_fftOut[2*i + 0]*m_fftOut[2*i + 0] + m_fftOut[2*i + 1]*m_fftOut[2*i + 1]); + } + for (int i = 1; i < m_samplesPerFrame/2; ++i) { + m_sampleSpectrum[i] += m_sampleSpectrum[m_samplesPerFrame - i]; + } + + uint8_t curByte = 0; + for (int i = 0; i < 2*rxProtocol.bytesPerTx; ++i) { + double freq = m_hzPerSample*rxProtocol.freqStart; + int bin = std::round(freq*m_ihzPerSample) + 16*i; int kmax = 0; double amax = 0.0; for (int k = 0; k < 16; ++k) { - if (sampleSpectrum[bin + k] > amax) { + if (m_sampleSpectrum[bin + k] > amax) { kmax = k; - amax = sampleSpectrum[bin + k]; + amax = m_sampleSpectrum[bin + k]; } } if (i%2) { curByte += (kmax << 4); - txDataEncoded[itx*nBytesPerTx + i/2] = curByte; + m_txDataEncoded[itx*rxProtocol.bytesPerTx + i/2] = curByte; curByte = 0; } else { curByte = kmax; } } - } - if (txMode == TxMode::VariableLength) { - if (itx*nBytesPerTx > 3 && knownLength == false) { - if ((rsLength->Decode(txDataEncoded.data(), rxData.data()) == 0) && (rxData[0] <= 140)) { + if (itx*rxProtocol.bytesPerTx > m_encodedDataOffset && knownLength == false) { + if ((m_rsLength->Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) && (m_rxData[0] > 0 && m_rxData[0] <= 140)) { knownLength = true; } else { break; } } } - } - if (txMode == TxMode::VariableLength && knownLength) { - if (rsData) delete rsData; - rsData = new RS::ReedSolomon(rxData[0], ::getECCBytesForLength(rxData[0])); - } + if (knownLength) { + int decodedLength = m_rxData[0]; - if (knownLength) { - int decodedLength = rxData[0]; - if (rsData->Decode(txDataEncoded.data() + encodedOffset, rxData.data()) == 0) { - printf("Decoded length = %d\n", decodedLength); - if (txMode == TxMode::FixedLength && rxData[0] == 'A') { - printf("[ANSWER] Received sound data successfully!\n"); - } else if (txMode == TxMode::FixedLength && rxData[0] == 'O') { - printf("[OFFER] Received sound data successfully!\n"); - } else { - std::string s((char *) rxData.data(), decodedLength); - printf("Received sound data successfully: '%s'\n", s.c_str()); + if (decodedLength != lastRSLength) { + rsData.reset(new RS::ReedSolomon(decodedLength, ::getECCBytesForLength(decodedLength))); + lastRSLength = decodedLength; + } + + if (rsData->Decode(m_txDataEncoded.data() + m_encodedDataOffset, m_rxData.data()) == 0) { + if (m_rxData[0] != 0) { + std::string s((char *) m_rxData.data(), decodedLength); + + printf("Decoded length = %d\n", decodedLength); + printf("Received sound data successfully: '%s'\n", s.c_str()); + + isValid = true; + m_hasNewRxData = true; + m_lastRxDataLength = decodedLength; + m_rxProtocol = rxProtocol; + m_rxProtocolId = rxProtocolId; + } } - hasNewRxData = true; - lastRxDataLength = decodedLength; - framesToRecord = 0; - isValid = true; } + + if (isValid) { + break; + } + --m_framesLeftToAnalyze; } - if (isValid) { - break; - } - --framesLeftToAnalyze; + if (isValid) break; } + m_framesToRecord = 0; + if (isValid == false) { printf("Failed to capture sound data. Please try again\n"); - framesToRecord = -1; + m_framesToRecord = -1; } - receivingData = false; - analyzingData = false; + m_receivingData = false; + m_analyzingData = false; - std::fill(sampleSpectrum.begin(), sampleSpectrum.end(), 0.0f); + std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0.0f); - framesToAnalyze = 0; - framesLeftToAnalyze = 0; + m_framesToAnalyze = 0; + m_framesLeftToAnalyze = 0; + + auto tEnd = std::chrono::high_resolution_clock::now(); + printf("Time to analyze: %g ms\n", getTime_ms(tStart, tEnd)); } // check if receiving data - if (receivingData == false) { - bool isReceiving = true; + if (m_receivingData == false) { + bool isReceiving = false; - for (int i = 0; i < nBitsInMarker; ++i) { - int bin = std::round(dataFreqs_hz[i]*ihzPerFrame); + for (const auto & rxProtocol : kTxProtocols) { + bool isReceivingCur = true; - if (i%2 == 0) { - if (sampleSpectrum[bin] <= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isReceiving = false; - } else { - if (sampleSpectrum[bin] >= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isReceiving = false; + for (int i = 0; i < m_nBitsInMarker; ++i) { + double freq = bitFreq(rxProtocol, i); + int bin = std::round(freq*m_ihzPerSample); + + if (i%2 == 0) { + if (m_sampleSpectrum[bin] <= 3.0f*m_sampleSpectrum[bin + m_freqDelta_bin]) isReceivingCur = false; + } else { + if (m_sampleSpectrum[bin] >= 3.0f*m_sampleSpectrum[bin + m_freqDelta_bin]) isReceivingCur = false; + } + } + + if (isReceivingCur) { + m_markerFreqStart = rxProtocol.freqStart; + isReceiving = true; + break; } } if (isReceiving) { std::time_t timestamp = std::time(nullptr); printf("%sReceiving sound data ...\n", std::asctime(std::localtime(×tamp))); - rxData.fill(0); - receivingData = true; - if (txMode == TxMode::FixedLength) { - recvDuration_frames = nMarkerFrames + nPostMarkerFrames + framesPerTx*((kDefaultFixedLength + paramECCBytesPerTx)/paramBytesPerTx + 1); - } else { - recvDuration_frames = nMarkerFrames + nPostMarkerFrames + framesPerTx*((kMaxLength + ::getECCBytesForLength(kMaxLength))/paramBytesPerTx + 1); - } - framesToRecord = recvDuration_frames; - framesLeftToRecord = recvDuration_frames; + + m_rxData.fill(0); + m_receivingData = true; + + // max recieve duration + m_recvDuration_frames = + 2*m_nMarkerFrames + m_nPostMarkerFrames + + maxFramesPerTx()*((kMaxLength + ::getECCBytesForLength(kMaxLength))/minBytesPerTx() + 1); + + m_framesToRecord = m_recvDuration_frames; + m_framesLeftToRecord = m_recvDuration_frames; } - } else if (txMode == TxMode::VariableLength) { - bool isEnded = true; + } else { + bool isEnded = false; - for (int i = 0; i < nBitsInMarker; ++i) { - int bin = std::round(dataFreqs_hz[i]*ihzPerFrame); + for (const auto & rxProtocol : kTxProtocols) { + bool isEndedCur = true; - if (i%2 == 0) { - if (sampleSpectrum[bin] >= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isEnded = false; - } else { - if (sampleSpectrum[bin] <= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isEnded = false; + for (int i = 0; i < m_nBitsInMarker; ++i) { + double freq = bitFreq(rxProtocol, i); + int bin = std::round(freq*m_ihzPerSample); + + if (i%2 == 0) { + if (m_sampleSpectrum[bin] >= 3.0f*m_sampleSpectrum[bin + m_freqDelta_bin]) isEndedCur = false; + } else { + if (m_sampleSpectrum[bin] <= 3.0f*m_sampleSpectrum[bin + m_freqDelta_bin]) isEndedCur = false; + } + } + + if (isEndedCur) { + isEnded = true; + break; } } - if (isEnded && framesToRecord > 1) { + if (isEnded && m_framesToRecord > 1) { std::time_t timestamp = std::time(nullptr); - printf("%sReceived end marker\n", std::asctime(std::localtime(×tamp))); - recvDuration_frames -= framesLeftToRecord - 1; - framesLeftToRecord = 1; + printf("%sReceived end marker. Frames left = %d\n", std::asctime(std::localtime(×tamp)), m_framesLeftToRecord); + m_recvDuration_frames -= m_framesLeftToRecord - 1; + m_framesLeftToRecord = 1; } } } else { break; } - - ++nIterations; - } - - auto tCallEnd = std::chrono::high_resolution_clock::now(); - tSum_ms += getTime_ms(tCallStart, tCallEnd); - if (++nCalls == 10) { - averageRxTime_ms = tSum_ms/nCalls; - tSum_ms = 0.0f; - nCalls = 0; } } + +int GGWave::takeRxData(TxRxData & dst) { + if (m_lastRxDataLength == 0) return 0; + + auto res = m_lastRxDataLength; + m_lastRxDataLength = 0; + dst = m_rxData; + + return res; +} + +int GGWave::maxFramesPerTx() const { + int res = 0; + for (const auto & protocol : kTxProtocols) { + res = std::max(res, protocol.framesPerTx); + } + return res; +} + +int GGWave::minBytesPerTx() const { + int res = kTxProtocols.front().framesPerTx; + for (const auto & protocol : kTxProtocols) { + res = std::min(res, protocol.bytesPerTx); + } + return res; +} +