mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 16:47:59 +08:00
ggwave v0.2.0 (#20)
* ggwave : add support for fixed length transmissions * spectrogram : add sample rate offset for debugging purposes * gwave : fix decoding bug * waver : wip * wip * remove post-marker frames * add resampler * ggwave : input/output resampling * ggwave : fix python build * ggwave : update spm * ggwave : refactor duplicate encode/decode code * ggwave : fix sound marker detection * waver : fix typo * ggwave : fix uninitialized members * ggwave : more sensitive receive
This commit is contained in:
Submodule bindings/ios updated: 5c56a02ca5...c96067aab0
@@ -38,7 +38,7 @@ setup(
|
||||
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
||||
# Build instructions
|
||||
ext_modules = [Extension("ggwave",
|
||||
[ggwave_module_src, "ggwave/src/ggwave.cpp"],
|
||||
[ggwave_module_src, "ggwave/src/ggwave.cpp", "ggwave/src/resampler.cpp"],
|
||||
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
||||
depends=["ggwave/include/ggwave/ggwave.h"],
|
||||
language="c++",
|
||||
|
||||
@@ -38,7 +38,7 @@ setup(
|
||||
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
||||
# Build instructions
|
||||
ext_modules = [Extension("ggwave",
|
||||
[ggwave_module_src, "ggwave/src/ggwave.cpp"],
|
||||
[ggwave_module_src, "ggwave/src/ggwave.cpp", "ggwave/src/resampler.cpp"],
|
||||
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
||||
depends=["ggwave/include/ggwave/ggwave.h"],
|
||||
language="c++",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "ggwave-common.h"
|
||||
#include "ggwave-common-sdl2.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
@@ -11,18 +13,20 @@
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]);
|
||||
printf("Usage: %s [-cN] [-pN] [-tN] [-lN]\n", argv[0]);
|
||||
printf(" -cN - select capture device N\n");
|
||||
printf(" -pN - select playback device N\n");
|
||||
printf(" -tN - transmission protocol\n");
|
||||
printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed);
|
||||
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"]);
|
||||
int payloadLength = argm["l"].empty() ? -1 : std::stoi(argm["l"]);
|
||||
|
||||
if (GGWave_init(playbackId, captureId) == false) {
|
||||
if (GGWave_init(playbackId, captureId, payloadLength) == false) {
|
||||
fprintf(stderr, "Failed to initialize GGWave\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -75,5 +79,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
GGWave_deinit();
|
||||
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,9 @@ void GGWave_setDefaultCaptureDeviceName(std::string name) {
|
||||
|
||||
bool GGWave_init(
|
||||
const int playbackId,
|
||||
const int captureId) {
|
||||
const int captureId,
|
||||
const int payloadLength,
|
||||
const int sampleRateOffset) {
|
||||
|
||||
if (g_devIdInp && g_devIdOut) {
|
||||
return false;
|
||||
@@ -117,7 +119,7 @@ bool GGWave_init(
|
||||
SDL_AudioSpec playbackSpec;
|
||||
SDL_zero(playbackSpec);
|
||||
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate;
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
|
||||
playbackSpec.format = AUDIO_S16SYS;
|
||||
playbackSpec.channels = 1;
|
||||
playbackSpec.samples = 16*1024;
|
||||
@@ -160,7 +162,7 @@ bool GGWave_init(
|
||||
if (g_devIdInp == 0) {
|
||||
SDL_AudioSpec captureSpec;
|
||||
captureSpec = g_obtainedSpecOut;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
|
||||
captureSpec.format = AUDIO_F32SYS;
|
||||
captureSpec.samples = 4096;
|
||||
|
||||
@@ -214,11 +216,12 @@ bool GGWave_init(
|
||||
if (g_ggWave) delete g_ggWave;
|
||||
|
||||
g_ggWave = new GGWave({
|
||||
g_obtainedSpecInp.freq,
|
||||
g_obtainedSpecOut.freq,
|
||||
GGWave::kDefaultSamplesPerFrame,
|
||||
sampleFormatInp,
|
||||
sampleFormatOut});
|
||||
payloadLength,
|
||||
g_obtainedSpecInp.freq,
|
||||
g_obtainedSpecOut.freq,
|
||||
GGWave::kDefaultSamplesPerFrame,
|
||||
sampleFormatInp,
|
||||
sampleFormatOut});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -280,8 +283,9 @@ bool GGWave_deinit() {
|
||||
SDL_CloseAudioDevice(g_devIdInp);
|
||||
SDL_PauseAudioDevice(g_devIdOut, 1);
|
||||
SDL_CloseAudioDevice(g_devIdOut);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
g_devIdInp = 0;
|
||||
g_devIdOut = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ class GGWave;
|
||||
// GGWave helpers
|
||||
|
||||
void GGWave_setDefaultCaptureDeviceName(std::string name);
|
||||
bool GGWave_init(const int playbackId, const int captureId);
|
||||
bool GGWave_init(const int playbackId, const int captureId, const int payloadLength = -1, const int sampleRateOffset = 0);
|
||||
GGWave * GGWave_instance();
|
||||
bool GGWave_mainLoop();
|
||||
bool GGWave_deinit();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "ggwave-common.h"
|
||||
#include "ggwave-common-sdl2.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
@@ -26,5 +28,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
GGWave_deinit();
|
||||
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str());
|
||||
|
||||
GGWave ggWave({ GGWave::kBaseSampleRate, sampleRateOut, 1024, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16 });
|
||||
GGWave ggWave({ -1, GGWave::kBaseSampleRate, sampleRateOut, 1024, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16 });
|
||||
ggWave.init(message.size(), message.data(), ggWave.getTxProtocol(protocolId), volume);
|
||||
|
||||
std::vector<char> bufferPCM;
|
||||
|
||||
@@ -31,10 +31,10 @@ struct FreqData {
|
||||
bool g_isCapturing = true;
|
||||
constexpr int g_nSamplesPerFrame = 1024;
|
||||
|
||||
int g_binMin = 40;
|
||||
int g_binMax = 40 + 96;
|
||||
int g_binMin = 20;
|
||||
int g_binMax = 60;
|
||||
|
||||
float g_scale = 5.0;
|
||||
float g_scale = 30.0;
|
||||
|
||||
bool g_showControls = true;
|
||||
|
||||
@@ -42,6 +42,8 @@ int g_freqDataHead = 0;
|
||||
int g_freqDataSize = 0;
|
||||
std::vector<FreqData> g_freqData;
|
||||
|
||||
int g_sampleRateOffset = 0;
|
||||
|
||||
}
|
||||
|
||||
void GGWave_setDefaultCaptureDeviceName(std::string name) {
|
||||
@@ -90,7 +92,7 @@ bool GGWave_init(
|
||||
SDL_AudioSpec playbackSpec;
|
||||
SDL_zero(playbackSpec);
|
||||
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate;
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate + g_sampleRateOffset;
|
||||
playbackSpec.format = AUDIO_S16SYS;
|
||||
playbackSpec.channels = 1;
|
||||
playbackSpec.samples = 16*1024;
|
||||
@@ -133,7 +135,7 @@ bool GGWave_init(
|
||||
if (g_devIdInp == 0) {
|
||||
SDL_AudioSpec captureSpec;
|
||||
captureSpec = g_obtainedSpecOut;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate + g_sampleRateOffset;
|
||||
captureSpec.format = AUDIO_F32SYS;
|
||||
captureSpec.samples = g_nSamplesPerFrame;
|
||||
|
||||
@@ -240,8 +242,9 @@ bool GGWave_deinit() {
|
||||
SDL_CloseAudioDevice(g_devIdInp);
|
||||
SDL_PauseAudioDevice(g_devIdOut, 1);
|
||||
SDL_CloseAudioDevice(g_devIdOut);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
g_devIdInp = 0;
|
||||
g_devIdOut = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -474,6 +477,10 @@ int main(int argc, char** argv) {
|
||||
ImGui::DragInt("Min", &g_binMin, 1, 0, g_binMax - 1);
|
||||
ImGui::DragInt("Max", &g_binMax, 1, g_binMin + 1, g_nSamplesPerFrame/2);
|
||||
ImGui::DragFloat("Scale", &g_scale, 1.0f, 1.0f, 1000.0f);
|
||||
if (ImGui::SliderInt("Offset", &g_sampleRateOffset, -2048, 2048)) {
|
||||
GGWave_deinit();
|
||||
GGWave_init(0, 0);
|
||||
}
|
||||
if (ImGui::Button("Pause")) {
|
||||
togglePause = true;
|
||||
}
|
||||
@@ -515,6 +522,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -188,6 +188,10 @@ struct State {
|
||||
struct Input {
|
||||
bool update = false;
|
||||
Message message;
|
||||
|
||||
bool reinit = false;
|
||||
bool isSampleRateOffset = false;
|
||||
int payloadLength = -1;
|
||||
};
|
||||
|
||||
struct Buffer {
|
||||
@@ -509,6 +513,7 @@ void updateCore() {
|
||||
static Input inputCurrent;
|
||||
|
||||
static int lastRxDataLength = 0;
|
||||
static float lastRxTimestamp = 0.0f;
|
||||
static GGWave::TxRxData lastRxData;
|
||||
|
||||
{
|
||||
@@ -519,6 +524,15 @@ void updateCore() {
|
||||
}
|
||||
}
|
||||
|
||||
if (inputCurrent.reinit) {
|
||||
GGWave_deinit();
|
||||
// todo : use the provided cli arguments for playback and capture device
|
||||
GGWave_init(0, 0, inputCurrent.payloadLength, inputCurrent.isSampleRateOffset ? -512 : 0);
|
||||
g_ggWave = GGWave_instance();
|
||||
|
||||
inputCurrent.reinit = false;
|
||||
}
|
||||
|
||||
if (inputCurrent.update) {
|
||||
g_ggWave->init(
|
||||
(int) inputCurrent.message.data.size(),
|
||||
@@ -543,7 +557,7 @@ void updateCore() {
|
||||
0,
|
||||
Message::Error,
|
||||
};
|
||||
} else if (lastRxDataLength > 0) {
|
||||
} else if (lastRxDataLength > 0 && ImGui::GetTime() - lastRxTimestamp > 0.5f) {
|
||||
auto message = std::string((char *) lastRxData.data(), lastRxDataLength);
|
||||
const Message::Type type = isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text;
|
||||
g_buffer.stateCore.update = true;
|
||||
@@ -556,6 +570,7 @@ void updateCore() {
|
||||
0,
|
||||
type,
|
||||
};
|
||||
lastRxTimestamp = ImGui::GetTime();
|
||||
}
|
||||
|
||||
if (g_ggWave->takeSpectrum(g_buffer.stateCore.spectrum)) {
|
||||
@@ -663,6 +678,9 @@ void renderMain() {
|
||||
|
||||
struct Settings {
|
||||
int protocolId = 1;
|
||||
bool isFixedLength = false;
|
||||
int payloadLength = 1;
|
||||
bool isSampleRateOffset = false;
|
||||
float volume = 0.10f;
|
||||
};
|
||||
|
||||
@@ -817,14 +835,14 @@ void renderMain() {
|
||||
ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true);
|
||||
ImGui::Text("%s", "");
|
||||
ImGui::Text("%s", "");
|
||||
ImGui::Text("Waver v1.3.2");
|
||||
ImGui::Text("Waver v1.4.0");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("%s", "");
|
||||
ImGui::Text("Sample rate (capture): %g, %d B/sample", g_ggWave->getSampleRateInp(), g_ggWave->getSampleSizeBytesInp());
|
||||
ImGui::Text("Sample rate (playback): %g, %d B/sample", g_ggWave->getSampleRateOut(), g_ggWave->getSampleSizeBytesOut());
|
||||
|
||||
const float kLabelWidth = ImGui::CalcTextSize("Tx Protocol: ").x;
|
||||
const float kLabelWidth = ImGui::CalcTextSize("Fixed-length: ").x;
|
||||
|
||||
// volume
|
||||
ImGui::Text("%s", "");
|
||||
@@ -879,6 +897,37 @@ void renderMain() {
|
||||
ImGui::SetCursorScreenPos(posSave);
|
||||
}
|
||||
|
||||
// fixed-length
|
||||
ImGui::Text("%s", "");
|
||||
{
|
||||
auto posSave = ImGui::GetCursorScreenPos();
|
||||
ImGui::Text("Fixed-length: ");
|
||||
ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });
|
||||
}
|
||||
if (ImGui::Checkbox("##fixed-length", &settings.isFixedLength)) {
|
||||
g_buffer.inputUI.update = true;
|
||||
g_buffer.inputUI.reinit = true;
|
||||
g_buffer.inputUI.isSampleRateOffset = settings.isSampleRateOffset;
|
||||
g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1;
|
||||
} else {
|
||||
g_buffer.inputUI.reinit = false;
|
||||
g_buffer.inputUI.isSampleRateOffset = false;
|
||||
}
|
||||
|
||||
if (settings.isFixedLength) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth());
|
||||
ImGui::SliderInt("Bytes", &settings.payloadLength, 1, 16);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Offset", &settings.isSampleRateOffset)) {
|
||||
g_buffer.inputUI.update = true;
|
||||
g_buffer.inputUI.reinit = true;
|
||||
g_buffer.inputUI.isSampleRateOffset = settings.isSampleRateOffset;
|
||||
g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// protocol
|
||||
ImGui::Text("%s", "");
|
||||
{
|
||||
@@ -887,6 +936,12 @@ void renderMain() {
|
||||
ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });
|
||||
ImGui::TextDisabled("[U] = ultrasound");
|
||||
}
|
||||
{
|
||||
auto posSave = ImGui::GetCursorScreenPos();
|
||||
ImGui::Text("%s", "");
|
||||
ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });
|
||||
ImGui::TextDisabled("[DT] = dual-tone");
|
||||
}
|
||||
{
|
||||
auto posSave = ImGui::GetCursorScreenPos();
|
||||
ImGui::Text("Tx Protocol: ");
|
||||
@@ -980,7 +1035,7 @@ void renderMain() {
|
||||
{
|
||||
auto col = ImVec4 { 1.0f, 0.0f, 0.0f, 1.0f };
|
||||
col.w = interp;
|
||||
ImGui::TextColored(col, "Failed to received");
|
||||
ImGui::TextColored(col, "Failed to receive");
|
||||
}
|
||||
break;
|
||||
case Message::Text:
|
||||
|
||||
@@ -310,6 +310,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -41,10 +41,22 @@ extern "C" {
|
||||
GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL,
|
||||
GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST,
|
||||
GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST,
|
||||
GGWAVE_TX_PROTOCOL_DT_NORMAL,
|
||||
GGWAVE_TX_PROTOCOL_DT_FAST,
|
||||
GGWAVE_TX_PROTOCOL_DT_FASTEST,
|
||||
} ggwave_TxProtocolId;
|
||||
|
||||
// GGWave instance parameters
|
||||
//
|
||||
// If payloadLength <= 0, then GGWave will transmit with variable payload length
|
||||
// depending on the provided payload. Sound markers are used to identify the
|
||||
// start and end of the transmission.
|
||||
//
|
||||
// If payloadLength > 0, then the transmitted payload will be of the specified
|
||||
// fixed length. In this case, no sound markers are emitted and a slightly
|
||||
// different decoding scheme is applied. This is useful in cases where the
|
||||
// length of the payload is known in advance.
|
||||
//
|
||||
// The sample rates are values typically between 8000 and 96000.
|
||||
// Default value: GGWave::kBaseSampleRate
|
||||
//
|
||||
@@ -53,6 +65,7 @@ extern "C" {
|
||||
// Default value: GGWave::kDefaultSamplesPerFrame
|
||||
//
|
||||
typedef struct {
|
||||
int payloadLength; // payload length
|
||||
int sampleRateInp; // capture sample rate
|
||||
int sampleRateOut; // playback sample rate
|
||||
int samplesPerFrame; // number of samples per audio frame
|
||||
@@ -205,18 +218,20 @@ extern "C" {
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
class GGWave {
|
||||
public:
|
||||
static constexpr auto kBaseSampleRate = 48000;
|
||||
static constexpr auto kDefaultSamplesPerFrame = 1024;
|
||||
static constexpr auto kDefaultVolume = 10;
|
||||
static constexpr auto kMaxSamplesPerFrame = 1024;
|
||||
static constexpr auto kMaxSamplesPerFrame = 2048;
|
||||
static constexpr auto kMaxDataBits = 256;
|
||||
static constexpr auto kMaxDataSize = 256;
|
||||
static constexpr auto kMaxLength = 140;
|
||||
static constexpr auto kMaxLengthVarible = 140;
|
||||
static constexpr auto kMaxLengthFixed = 16;
|
||||
static constexpr auto kMaxSpectrumHistory = 4;
|
||||
static constexpr auto kMaxRecordedFrames = 1024;
|
||||
static constexpr auto kMaxRecordedFrames = 2048;
|
||||
|
||||
using Parameters = ggwave_Parameters;
|
||||
using SampleFormat = ggwave_SampleFormat;
|
||||
@@ -236,12 +251,15 @@ public:
|
||||
|
||||
static const TxProtocols & getTxProtocols() {
|
||||
static const TxProtocols kTxProtocols {
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_NORMAL, { "Normal", 40, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, { "Fast", 40, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FASTEST, { "Fastest", 40, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL, { "[U] Normal", 320, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST, { "[U] Fast", 320, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST, { "[U] Fastest", 320, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_NORMAL, { "Normal", 40, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, { "Fast", 40, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FASTEST, { "Fastest", 40, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL, { "[U] Normal", 320, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST, { "[U] Fast", 320, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST, { "[U] Fastest", 320, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_NORMAL, { "[DT] Normal", 24, 9, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FAST, { "[DT] Fast", 24, 6, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FASTEST, { "[DT] Fastest", 24, 3, 1, } },
|
||||
};
|
||||
|
||||
return kTxProtocols;
|
||||
@@ -321,6 +339,8 @@ public:
|
||||
|
||||
// Rx
|
||||
|
||||
void setRxProtocols(const TxProtocols & rxProtocols) { m_rxProtocols = rxProtocols; }
|
||||
|
||||
const TxRxData & getRxData() const { return m_rxData; }
|
||||
const TxProtocol & getRxProtocol() const { return m_rxProtocol; }
|
||||
const TxProtocolId & getRxProtocolId() const { return m_rxProtocolId; }
|
||||
@@ -329,6 +349,9 @@ public:
|
||||
bool takeSpectrum(SpectrumData & dst);
|
||||
|
||||
private:
|
||||
void decode_fixed();
|
||||
void decode_variable();
|
||||
|
||||
int maxFramesPerTx() const;
|
||||
int minBytesPerTx() const;
|
||||
|
||||
@@ -353,13 +376,18 @@ private:
|
||||
|
||||
const int m_nBitsInMarker;
|
||||
const int m_nMarkerFrames;
|
||||
const int m_nPostMarkerFrames;
|
||||
const int m_encodedDataOffset;
|
||||
|
||||
// common
|
||||
|
||||
bool m_isFixedPayloadLength;
|
||||
int m_payloadLength;
|
||||
|
||||
// Rx
|
||||
bool m_receivingData;
|
||||
bool m_analyzingData;
|
||||
|
||||
int m_nMarkersSuccess;
|
||||
int m_markerFreqStart;
|
||||
int m_recvDuration_frames;
|
||||
|
||||
@@ -369,12 +397,13 @@ private:
|
||||
int m_framesToRecord;
|
||||
int m_samplesNeeded;
|
||||
|
||||
std::vector<float> m_fftInp; // real
|
||||
std::vector<float> m_fftInp; // real
|
||||
std::vector<float> m_fftOut; // complex
|
||||
|
||||
bool m_hasNewSpectrum;
|
||||
SpectrumData m_sampleSpectrum;
|
||||
AmplitudeData m_sampleAmplitude;
|
||||
AmplitudeData m_sampleAmplitudeResampled;
|
||||
TxRxData m_sampleAmplitudeTmp;
|
||||
|
||||
bool m_hasNewRxData;
|
||||
@@ -382,13 +411,17 @@ private:
|
||||
TxRxData m_rxData;
|
||||
TxProtocol m_rxProtocol;
|
||||
TxProtocolId m_rxProtocolId;
|
||||
TxProtocols m_rxProtocols;
|
||||
|
||||
int m_historyId = 0;
|
||||
int m_historyId;
|
||||
AmplitudeData m_sampleAmplitudeAverage;
|
||||
std::vector<AmplitudeData> m_sampleAmplitudeHistory;
|
||||
|
||||
RecordedData m_recordedAmplitude;
|
||||
|
||||
int m_historyIdFixed;
|
||||
std::vector<SpectrumData> m_spectrumHistoryFixed;
|
||||
|
||||
// Tx
|
||||
bool m_hasNewTxData;
|
||||
float m_sendVolume;
|
||||
@@ -403,6 +436,11 @@ private:
|
||||
TxRxData m_outputBlockTmp;
|
||||
AmplitudeDataI16 m_outputBlockI16;
|
||||
AmplitudeDataI16 m_txAmplitudeDataI16;
|
||||
|
||||
// Impl
|
||||
// todo : move all members inside Impl
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,7 @@ set(TARGET ggwave)
|
||||
|
||||
add_library(${TARGET}
|
||||
ggwave.cpp
|
||||
resampler.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET} PUBLIC
|
||||
|
||||
848
src/ggwave.cpp
848
src/ggwave.cpp
File diff suppressed because it is too large
Load Diff
110
src/resampler.cpp
Normal file
110
src/resampler.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "resampler.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
double linear_interp(double first_number, double second_number, double fraction) {
|
||||
return (first_number + ((second_number - first_number)*fraction));
|
||||
}
|
||||
}
|
||||
|
||||
int Resampler::resample(
|
||||
float factor,
|
||||
int nSamples,
|
||||
const float * samplesInp,
|
||||
float * samplesOut) {
|
||||
if (factor != m_lastFactor) {
|
||||
make_sinc();
|
||||
m_lastFactor = factor;
|
||||
}
|
||||
|
||||
int idxInp = 0;
|
||||
int idxOut = 0;
|
||||
int notDone = 1;
|
||||
double time_now = 0.0;
|
||||
long num_samples = nSamples;
|
||||
long int_time = 0;
|
||||
long last_time = 0;
|
||||
float data_in = samplesInp[idxInp];
|
||||
float data_out;
|
||||
double one_over_factor = 1.0;
|
||||
while (notDone) {
|
||||
double temp1 = 0.0;
|
||||
long left_limit = time_now - kWidth + 1; /* leftmost neighboring sample used for interp.*/
|
||||
long right_limit = time_now + kWidth; /* rightmost leftmost neighboring sample used for interp.*/
|
||||
if (left_limit<0) left_limit = 0;
|
||||
if (right_limit>num_samples) right_limit = num_samples;
|
||||
if (factor<1.0) {
|
||||
for (int j=left_limit;j<right_limit;j++) {
|
||||
temp1 += gimme_data(j-int_time)*sinc(time_now - (double) j);
|
||||
}
|
||||
data_out = temp1;
|
||||
}
|
||||
else {
|
||||
one_over_factor = 1.0 / factor;
|
||||
for (int j=left_limit;j<right_limit;j++) {
|
||||
temp1 += gimme_data(j-int_time)*one_over_factor*sinc(one_over_factor * (time_now - (double) j));
|
||||
}
|
||||
data_out = temp1;
|
||||
}
|
||||
|
||||
//printf("%8.8f %8.8f\n", data_in, data_out);
|
||||
samplesOut[idxOut++] = data_out;
|
||||
time_now += factor;
|
||||
last_time = int_time;
|
||||
int_time = time_now;
|
||||
while(last_time<int_time) {
|
||||
if (++idxInp == nSamples) {
|
||||
notDone = 0;
|
||||
} else {
|
||||
data_in = samplesInp[idxInp];
|
||||
}
|
||||
new_data(data_in);
|
||||
last_time += 1;
|
||||
}
|
||||
// if (!(int_time % 1000)) printf("Sample # %li\n",int_time);
|
||||
//if (!(int_time % 1000)) {
|
||||
// printf(".");
|
||||
// fflush(stdout);
|
||||
//}
|
||||
}
|
||||
|
||||
return idxOut;
|
||||
}
|
||||
|
||||
float Resampler::gimme_data(long j) const {
|
||||
return m_delayBuffer[(int) j + kWidth];
|
||||
}
|
||||
|
||||
void Resampler::new_data(float data) {
|
||||
for (int i = 0; i < kDelaySize - 5; i++) {
|
||||
m_delayBuffer[i] = m_delayBuffer[i + 1];
|
||||
}
|
||||
m_delayBuffer[kDelaySize - 5] = data;
|
||||
}
|
||||
|
||||
void Resampler::make_sinc() {
|
||||
double temp, win_freq, win;
|
||||
win_freq = M_PI/kWidth/kSamplesPerZeroCrossing;
|
||||
m_sincTable[0] = 1.0;
|
||||
for (int i = 1; i < kWidth*kSamplesPerZeroCrossing;i++) {
|
||||
temp = (double) i*M_PI/kSamplesPerZeroCrossing;
|
||||
m_sincTable[i] = sin(temp)/temp;
|
||||
win = 0.5 + 0.5*cos(win_freq*i);
|
||||
m_sincTable[i] *= win;
|
||||
}
|
||||
}
|
||||
|
||||
double Resampler::sinc(double x) const {
|
||||
int low;
|
||||
double temp, delta;
|
||||
if (fabs(x) >= kWidth - 1) {
|
||||
return 0.0;
|
||||
} else {
|
||||
temp = fabs(x) * (double) kSamplesPerZeroCrossing;
|
||||
low = temp; /* these are interpolation steps */
|
||||
delta = temp - low; /* and can be ommited if desired */
|
||||
return linear_interp(m_sincTable[low], m_sincTable[low + 1], delta);
|
||||
}
|
||||
}
|
||||
32
src/resampler.h
Normal file
32
src/resampler.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
class Resampler {
|
||||
public:
|
||||
int resample(
|
||||
float factor,
|
||||
int nSamples,
|
||||
const float * samplesInp,
|
||||
float * samplesOut);
|
||||
private:
|
||||
float gimme_data(long j) const;
|
||||
void new_data(float data);
|
||||
void make_sinc();
|
||||
double sinc(double x) const;
|
||||
|
||||
/* this controls the number of neighboring samples
|
||||
which are used to interpolate the new samples. The
|
||||
processing time is linearly related to this width */
|
||||
static const int kWidth = 64;
|
||||
|
||||
static const int kDelaySize = 140;
|
||||
|
||||
/* this defines how finely the sinc function
|
||||
is sampled for storage in the table */
|
||||
static const int kSamplesPerZeroCrossing = 32;
|
||||
|
||||
float m_sincTable[kWidth*kSamplesPerZeroCrossing] = { 0.0 };
|
||||
|
||||
float m_delayBuffer[3*kWidth] = { 0 };
|
||||
|
||||
float m_lastFactor = -1.0f;
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ggwave/ggwave.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
@@ -71,7 +72,14 @@ void convert(const std::vector<S> & src, std::vector<D> & dst) {
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(int argc, char ** argv) {
|
||||
bool full = false;
|
||||
if (argc > 1) {
|
||||
if (strcmp(argv[1], "--full") == 0) {
|
||||
full = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bufferU8;
|
||||
std::vector<int8_t> bufferI8;
|
||||
std::vector<uint16_t> bufferU16;
|
||||
@@ -181,16 +189,15 @@ int main() {
|
||||
CHECK_F(instance.init(payload.size(), payload.c_str(), 101));
|
||||
}
|
||||
|
||||
// capture / playback at different sample rates
|
||||
{
|
||||
// playback / capture at different sample rates
|
||||
for (int srInp = GGWave::kBaseSampleRate/3; srInp <= 2*GGWave::kBaseSampleRate; srInp += 1100) {
|
||||
auto parameters = GGWave::getDefaultParameters();
|
||||
|
||||
std::string payload = "hello";
|
||||
std::string payload = "hello123";
|
||||
|
||||
// encode
|
||||
{
|
||||
parameters.sampleRateInp = 48000;
|
||||
parameters.sampleRateOut = 12000;
|
||||
parameters.sampleRateOut = srInp;
|
||||
GGWave instanceOut(parameters);
|
||||
|
||||
instanceOut.init(payload, 25);
|
||||
@@ -203,10 +210,10 @@ int main() {
|
||||
|
||||
// decode
|
||||
{
|
||||
parameters.samplesPerFrame *= float(parameters.sampleRateOut)/parameters.sampleRateInp;
|
||||
parameters.sampleRateInp = parameters.sampleRateOut;
|
||||
parameters.sampleRateInp = srInp;
|
||||
GGWave instanceInp(parameters);
|
||||
|
||||
instanceInp.setRxProtocols({{instanceInp.getDefaultTxProtocolId(), instanceInp.getDefaultTxProtocol()}});
|
||||
instanceInp.decode(kCBWaveformInp.at(parameters.sampleFormatInp));
|
||||
|
||||
GGWave::TxRxData result;
|
||||
@@ -217,30 +224,67 @@ int main() {
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto & txProtocol : GGWave::getTxProtocols()) {
|
||||
for (const auto & formatOut : kFormats) {
|
||||
for (const auto & formatInp : kFormats) {
|
||||
std::string payload = "a0Z5kR2g";
|
||||
|
||||
// encode / decode using different sample formats and Tx protocols
|
||||
for (const auto & formatOut : kFormats) {
|
||||
for (const auto & formatInp : kFormats) {
|
||||
if (full == false) {
|
||||
if (formatOut != GGWAVE_SAMPLE_FORMAT_I16) continue;
|
||||
if (formatInp != GGWAVE_SAMPLE_FORMAT_F32) continue;
|
||||
}
|
||||
for (const auto & txProtocol : GGWave::getTxProtocols()) {
|
||||
printf("Testing: protocol = %s, in = %d, out = %d\n", txProtocol.second.name, formatInp, formatOut);
|
||||
|
||||
auto parameters = GGWave::getDefaultParameters();
|
||||
parameters.sampleFormatInp = formatInp;
|
||||
parameters.sampleFormatOut = formatOut;
|
||||
GGWave instance(parameters);
|
||||
for (int length = 1; length <= (int) payload.size(); ++length) {
|
||||
// variable payload length
|
||||
{
|
||||
auto parameters = GGWave::getDefaultParameters();
|
||||
parameters.sampleFormatInp = formatInp;
|
||||
parameters.sampleFormatOut = formatOut;
|
||||
GGWave instance(parameters);
|
||||
|
||||
std::string payload = "test";
|
||||
instance.setRxProtocols({{txProtocol.first, txProtocol.second}});
|
||||
instance.init(length, payload.data(), txProtocol.second, 25);
|
||||
auto expectedSize = instance.encodeSize_samples();
|
||||
instance.encode(kCBWaveformOut.at(formatOut));
|
||||
printf("Expected = %d, actual = %d\n", expectedSize, nSamples);
|
||||
CHECK(expectedSize == nSamples);
|
||||
convertHelper(formatOut, formatInp);
|
||||
instance.decode(kCBWaveformInp.at(formatInp));
|
||||
|
||||
instance.init(payload, txProtocol.second, 25);
|
||||
auto expectedSize = instance.encodeSize_samples();
|
||||
instance.encode(kCBWaveformOut.at(formatOut));
|
||||
printf("Expected = %d, actual = %d\n", expectedSize, nSamples);
|
||||
CHECK(expectedSize == nSamples);
|
||||
convertHelper(formatOut, formatInp);
|
||||
instance.decode(kCBWaveformInp.at(formatInp));
|
||||
GGWave::TxRxData result;
|
||||
CHECK(instance.takeRxData(result) == length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
CHECK(payload[i] == result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GGWave::TxRxData result;
|
||||
CHECK(instance.takeRxData(result) == (int) payload.size());
|
||||
for (int i = 0; i < (int) payload.size(); ++i) {
|
||||
CHECK(payload[i] == result[i]);
|
||||
for (int length = 1; length <= (int) payload.size(); ++length) {
|
||||
// fixed payload length
|
||||
{
|
||||
auto parameters = GGWave::getDefaultParameters();
|
||||
parameters.payloadLength = length;
|
||||
parameters.sampleFormatInp = formatInp;
|
||||
parameters.sampleFormatOut = formatOut;
|
||||
GGWave instance(parameters);
|
||||
|
||||
instance.setRxProtocols({{txProtocol.first, txProtocol.second}});
|
||||
instance.init(length, payload.data(), txProtocol.second, 10);
|
||||
auto expectedSize = instance.encodeSize_samples();
|
||||
instance.encode(kCBWaveformOut.at(formatOut));
|
||||
printf("Expected = %d, actual = %d\n", expectedSize, nSamples);
|
||||
CHECK(expectedSize == nSamples);
|
||||
convertHelper(formatOut, formatInp);
|
||||
instance.decode(kCBWaveformInp.at(formatInp));
|
||||
|
||||
GGWave::TxRxData result;
|
||||
CHECK(instance.takeRxData(result) == length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
CHECK(payload[i] == result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user