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:
Georgi Gerganov
2021-02-20 19:16:15 +02:00
committed by GitHub
parent ff5c569071
commit 19bf22df0d
17 changed files with 921 additions and 366 deletions

View File

@@ -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++",

View File

@@ -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++",

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:

View File

@@ -310,6 +310,7 @@ int main(int argc, char** argv) {
SDL_GL_DeleteContext(gl_context);
SDL_DestroyWindow(window);
SDL_CloseAudio();
SDL_Quit();
#endif

View File

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

View File

@@ -4,6 +4,7 @@ set(TARGET ggwave)
add_library(${TARGET}
ggwave.cpp
resampler.cpp
)
target_include_directories(${TARGET} PUBLIC

File diff suppressed because it is too large Load Diff

110
src/resampler.cpp Normal file
View 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
View 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;
};

View File

@@ -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]);
}
}
}
}
}