diff --git a/examples/ggwave-gui/main.cpp b/examples/ggwave-gui/main.cpp index 3c48d49..93afc6f 100644 --- a/examples/ggwave-gui/main.cpp +++ b/examples/ggwave-gui/main.cpp @@ -94,9 +94,54 @@ int main(int argc, char** argv) { float volume; }; + struct GGWaveStats { + bool isReceiving; + bool isAnalyzing; + int framesToRecord; + int framesLeftToRecord; + int framesToAnalyze; + int framesLeftToAnalyze; + }; + struct State { bool update = false; + + struct Flags { + bool newMessage = false; + bool newSpectrum = false; + bool newStats = false; + + void clear() { memset(this, 0, sizeof(Flags)); } + } flags; + + void apply(State & dst) { + if (update == false) return; + + if (this->flags.newMessage) { + dst.update = true; + dst.flags.newMessage = true; + dst.message = std::move(this->message); + } + + if (this->flags.newSpectrum) { + dst.update = true; + dst.flags.newSpectrum = true; + dst.spectrum = std::move(this->spectrum); + } + + if (this->flags.newStats) { + dst.update = true; + dst.flags.newStats = true; + dst.stats = std::move(this->stats); + } + + flags.clear(); + update = false; + } + Message message; + GGWave::SpectrumData spectrum; + GGWaveStats stats; }; struct Input { @@ -146,6 +191,7 @@ int main(int argc, char** argv) { lastRxDataLength = ggWave->takeRxData(lastRxData); if (lastRxDataLength > 0) { buffer.stateCore.update = true; + buffer.stateCore.flags.newMessage = true; buffer.stateCore.message = { true, std::time(nullptr), @@ -155,15 +201,28 @@ int main(int argc, char** argv) { }; } - { - std::lock_guard lock(buffer.mutex); - if (buffer.stateCore.update) { - buffer.stateUI = std::move(buffer.stateCore); - buffer.stateCore.update = false; - } + if (ggWave->takeSpectrum(buffer.stateCore.spectrum)) { + buffer.stateCore.update = true; + buffer.stateCore.flags.newSpectrum = true; } - std::this_thread::sleep_for(std::chrono::milliseconds(5)); + if (true) { + buffer.stateCore.update = true; + buffer.stateCore.flags.newStats = true; + buffer.stateCore.stats.isReceiving = ggWave->isReceiving(); + buffer.stateCore.stats.isAnalyzing = ggWave->isAnalyzing(); + buffer.stateCore.stats.framesToRecord = ggWave->getFramesToRecord(); + buffer.stateCore.stats.framesLeftToRecord = ggWave->getFramesLeftToRecord(); + buffer.stateCore.stats.framesToAnalyze = ggWave->getFramesToAnalyze(); + buffer.stateCore.stats.framesLeftToAnalyze = ggWave->getFramesLeftToAnalyze(); + } + + { + std::lock_guard lock(buffer.mutex); + buffer.stateCore.apply(buffer.stateUI); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); @@ -176,16 +235,13 @@ int main(int argc, char** argv) { { std::lock_guard lock(buffer.mutex); - if (buffer.stateUI.update) { - stateCurrent = std::move(buffer.stateUI); - buffer.stateUI.update = false; - } + buffer.stateUI.apply(stateCurrent); } enum class WindowId { Settings, Messages, - Commands, + Spectrum, }; struct Settings { @@ -206,10 +262,22 @@ int main(int argc, char** argv) { static double tStartInput = 0.0f; static double tEndInput = -100.0f; + static GGWaveStats statsCurrent; + static GGWave::SpectrumData spectrumCurrent; static std::vector messageHistory; if (stateCurrent.update) { - messageHistory.push_back(std::move(stateCurrent.message)); + if (stateCurrent.flags.newMessage) { + scrollMessagesToBottom = true; + messageHistory.push_back(std::move(stateCurrent.message)); + } + if (stateCurrent.flags.newSpectrum) { + spectrumCurrent = std::move(stateCurrent.spectrum); + } + if (stateCurrent.flags.newStats) { + statsCurrent = std::move(stateCurrent.stats); + } + stateCurrent.flags.clear(); stateCurrent.update = false; } @@ -251,12 +319,14 @@ int main(int argc, char** argv) { } ImGui::SameLine(); - if (ImGui::Button(ICON_FA_LIST_UL " Commands", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { - windowId = WindowId::Commands; + if (ImGui::Button(ICON_FA_LIST_UL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + windowId = WindowId::Spectrum; } if (windowId == WindowId::Settings) { ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true); + ImGui::Text("%s", ""); + ImGui::Text("%s", ""); ImGui::Text("Waver v0.1"); ImGui::Separator(); @@ -264,7 +334,7 @@ int main(int argc, char** argv) { 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; + const float kLabelWidth = 100.0f; // volume ImGui::Text("%s", ""); @@ -282,7 +352,6 @@ int main(int argc, char** argv) { 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); @@ -301,7 +370,7 @@ int main(int argc, char** argv) { } if (windowId == WindowId::Messages) { - const float messagesInputHeight = ImGui::GetTextLineHeightWithSpacing(); + const float messagesInputHeight = 2*ImGui::GetTextLineHeightWithSpacing(); const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x; float messagesHistoryHeigth = messagesHistoryHeigthMax; @@ -324,6 +393,7 @@ int main(int argc, char** argv) { ImGui::BeginChild("Messages:history", { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth }, true); + ImGui::PushTextWrapPos(); for (int i = 0; i < (int) messageHistory.size(); ++i) { ImGui::PushID(i); const auto & message = messageHistory[i]; @@ -351,6 +421,7 @@ int main(int argc, char** argv) { ImGui::Text("%s", ""); ImGui::PopID(); } + ImGui::PopTextWrapPos(); if (scrollMessagesToBottom) { ImGui::SetScrollHereY(); @@ -361,6 +432,22 @@ int main(int argc, char** argv) { ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); + if (statsCurrent.isReceiving) { + if (statsCurrent.isAnalyzing) { + ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Analyzing ..."); + ImGui::SameLine(); + ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToAnalyze)/statsCurrent.framesToAnalyze, + { ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() }); + } else { + ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Receiving ..."); + ImGui::SameLine(); + ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToRecord)/statsCurrent.framesToRecord, + { ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() }); + } + } else { + ImGui::TextDisabled("Listening for waves ...\n"); + } + if (doInputFocus) { ImGui::SetKeyboardFocusHere(); doInputFocus = false; @@ -394,9 +481,24 @@ int main(int argc, char** argv) { } } - if (windowId == WindowId::Commands) { - ImGui::BeginChild("Commands:main", ImGui::GetContentRegionAvail(), true); - ImGui::Text("Todo"); + if (windowId == WindowId::Spectrum) { + ImGui::BeginChild("Spectrum:main", ImGui::GetContentRegionAvail(), true); + ImGui::Text("FPS: %g\n", ImGui::GetIO().Framerate); + if (spectrumCurrent.empty() == false) { + auto wSize = ImGui::GetContentRegionAvail(); + ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.3f, 0.3f, 0.3f, 0.3f }); + if (statsCurrent.isReceiving) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 1.0f, 0.0f, 0.0f, 1.0f }); + } else { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 0.0f, 1.0f, 0.0f, 1.0f }); + } + ImGui::PlotHistogram("##plotSpectrumCurrent", + spectrumCurrent.data() + 30, + ggWave->getSamplesPerFrame()/2 - 30, 0, + (std::string("Current Spectrum")).c_str(), + 0.0f, FLT_MAX, wSize); + ImGui::PopStyleColor(2); + } ImGui::EndChild(); } diff --git a/include/ggwave/ggwave.h b/include/ggwave/ggwave.h index a94a463..f99694e 100644 --- a/include/ggwave/ggwave.h +++ b/include/ggwave/ggwave.h @@ -56,6 +56,8 @@ public: void receive(const CBDequeueAudio & CBDequeueAudio); const bool & hasTxData() const { return m_hasNewTxData; } + const bool & isReceiving() const { return m_receivingData; } + const bool & isAnalyzing() const { return m_analyzingData; } const int & getFramesToRecord() const { return m_framesToRecord; } const int & getFramesLeftToRecord() const { return m_framesLeftToRecord; } @@ -74,7 +76,9 @@ public: 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); + bool takeSpectrum(SpectrumData & dst); private: int maxFramesPerTx() const; @@ -117,6 +121,7 @@ private: std::vector m_fftIn; // real std::vector m_fftOut; // complex + bool m_hasNewSpectrum; SpectrumData m_sampleSpectrum; AmplitudeData m_sampleAmplitude; diff --git a/src/ggwave.cpp b/src/ggwave.cpp index 3de73d3..b10a1eb 100644 --- a/src/ggwave.cpp +++ b/src/ggwave.cpp @@ -149,8 +149,11 @@ GGWave::GGWave( m_encodedDataOffset(3), m_fftIn(kMaxSamplesPerFrame), m_fftOut(2*kMaxSamplesPerFrame), + m_hasNewSpectrum(false), m_sampleSpectrum(kMaxSamplesPerFrame), m_sampleAmplitude(kMaxSamplesPerFrame), + m_hasNewRxData(false), + m_lastRxDataLength(0), m_rxData(kMaxDataSize), m_sampleAmplitudeAverage(kMaxSamplesPerFrame), m_sampleAmplitudeHistory(kMaxSpectrumHistory), @@ -385,48 +388,47 @@ void GGWave::receive(const CBDequeueAudio & CBDequeueAudio) { auto nBytesRecorded = CBDequeueAudio(m_sampleAmplitude.data(), m_samplesPerFrame*m_sampleSizeBytesIn); if (nBytesRecorded != 0) { - { - m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude; + m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude; - if (++m_historyId >= kMaxSpectrumHistory) { - m_historyId = 0; - } + if (++m_historyId >= kMaxSpectrumHistory) { + m_historyId = 0; + } - 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; + if (m_historyId == 0 || m_receivingData) { + m_hasNewSpectrum = true; + + 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] *= norm; - } - - // calculate spectrum - std::copy(m_sampleAmplitudeAverage.begin(), m_sampleAmplitudeAverage.begin() + m_samplesPerFrame, m_fftIn.data()); - - FFT(m_fftIn.data(), m_fftOut.data(), m_samplesPerFrame, 1.0); - - double fsum = 0.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]); - fsum += m_sampleSpectrum[i]; - } - for (int i = 1; i < m_samplesPerFrame/2; ++i) { - m_sampleSpectrum[i] += m_sampleSpectrum[m_samplesPerFrame - i]; + m_sampleAmplitudeAverage[i] += s[i]; } } - if (m_framesLeftToRecord > 0) { - std::copy(m_sampleAmplitude.begin(), - m_sampleAmplitude.begin() + m_samplesPerFrame, - m_recordedAmplitude.data() + (m_framesToRecord - m_framesLeftToRecord)*m_samplesPerFrame); + float norm = 1.0f/kMaxSpectrumHistory; + for (int i = 0; i < m_samplesPerFrame; ++i) { + m_sampleAmplitudeAverage[i] *= norm; + } - if (--m_framesLeftToRecord <= 0) { - m_analyzingData = true; - } + // calculate spectrum + std::copy(m_sampleAmplitudeAverage.begin(), m_sampleAmplitudeAverage.begin() + m_samplesPerFrame, m_fftIn.data()); + + 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]; + } + } + + if (m_framesLeftToRecord > 0) { + std::copy(m_sampleAmplitude.begin(), + m_sampleAmplitude.begin() + m_samplesPerFrame, + m_recordedAmplitude.data() + (m_framesToRecord - m_framesLeftToRecord)*m_samplesPerFrame); + + if (--m_framesLeftToRecord <= 0) { + m_analyzingData = true; } } @@ -651,6 +653,15 @@ int GGWave::takeRxData(TxRxData & dst) { return res; } +bool GGWave::takeSpectrum(SpectrumData & dst) { + if (m_hasNewSpectrum == false) return false; + + m_hasNewSpectrum = false; + dst = m_sampleSpectrum; + + return true; +} + int GGWave::maxFramesPerTx() const { int res = 0; for (const auto & protocol : kTxProtocols) {