mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-07 01:11:22 +08:00
Merge pull request #1 from ggerganov/receive-all
Refactor GGWave + able to receive any type of Tx protocol
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<std::mutex> lock(mutex);
|
||||
ggWave->init(input.size(), input.data());
|
||||
ggWave->init(input.size(), input.data(), ggWave->getTxProtocols()[txProtocol], 50);
|
||||
}
|
||||
inputOld = input;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
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<TxProtocol>;
|
||||
|
||||
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<float, kMaxSamplesPerFrame>;
|
||||
using AmplitudeData16 = std::array<int16_t, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
@@ -34,135 +50,105 @@ public:
|
||||
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
|
||||
|
||||
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<float, kMaxSamplesPerFrame> fftIn;
|
||||
std::array<std::complex<float>, kMaxSamplesPerFrame> fftOut;
|
||||
std::array<float, kMaxSamplesPerFrame> m_fftIn; // real
|
||||
std::array<float, 2*kMaxSamplesPerFrame> 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<AmplitudeData, kMaxSpectrumHistory> sampleAmplitudeHistory;
|
||||
int m_historyId = 0;
|
||||
AmplitudeData m_sampleAmplitudeAverage;
|
||||
std::array<AmplitudeData, kMaxSpectrumHistory> 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<bool, kMaxDataBits> dataBits;
|
||||
std::array<double, kMaxDataBits> phaseOffsets;
|
||||
std::array<double, kMaxDataBits> dataFreqs_hz;
|
||||
|
||||
std::array<AmplitudeData, kMaxDataBits> bit1Amplitude;
|
||||
std::array<AmplitudeData, kMaxDataBits> bit0Amplitude;
|
||||
|
||||
RS::ReedSolomon * rsData = nullptr;
|
||||
RS::ReedSolomon * rsLength = nullptr;
|
||||
std::unique_ptr<RS::ReedSolomon> m_rsLength;
|
||||
};
|
||||
|
||||
748
src/ggwave.cpp
748
src/ggwave.cpp
@@ -28,31 +28,48 @@ int reverse(int N, int n) {
|
||||
return p;
|
||||
}
|
||||
|
||||
void ordina(std::complex<float>* f1, int N) {
|
||||
std::complex<float> 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<float>* f, int N) {
|
||||
void transform(float * f, int N) {
|
||||
ordina(f, N); //first: reverse order
|
||||
std::complex<float> *W;
|
||||
W = (std::complex<float> *)malloc(N / 2 * sizeof(std::complex<float>));
|
||||
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<float> temp = f[i];
|
||||
std::complex<float> 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<float>* f, int N) {
|
||||
free(W);
|
||||
}
|
||||
|
||||
void FFT(std::complex<float>* 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<float>* 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<const uint8_t *>(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<double, kMaxDataBits> 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<bool, kMaxDataBits> dataBits;
|
||||
|
||||
std::array<AmplitudeData, kMaxDataBits> bit1Amplitude;
|
||||
std::array<AmplitudeData, kMaxDataBits> 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<RS::ReedSolomon> 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user