mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 16:47:59 +08:00
ggwave-gui : add spectrum + other UI stuff
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user