ggwave-gui : add spectrum + other UI stuff

This commit is contained in:
Georgi Gerganov
2020-12-05 19:22:22 +02:00
parent cde32e3e96
commit 7f4cce25fa
3 changed files with 174 additions and 56 deletions

View File

@@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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<Message> 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();
}

View File

@@ -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<float> m_fftIn; // real
std::vector<float> m_fftOut; // complex
bool m_hasNewSpectrum;
SpectrumData m_sampleSpectrum;
AmplitudeData m_sampleAmplitude;

View File

@@ -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) {