mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-04-21 05:36:33 +08:00
wip : pimpled Tx and moved resampler inside GGWave
This commit is contained in:
@@ -38,7 +38,7 @@ setup(
|
|||||||
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
||||||
# Build instructions
|
# Build instructions
|
||||||
ext_modules = [Extension("ggwave",
|
ext_modules = [Extension("ggwave",
|
||||||
[ggwave_module_src, "ggwave/src/ggwave.cpp", "ggwave/src/resampler.cpp"],
|
[ggwave_module_src, "ggwave/src/ggwave.cpp"],
|
||||||
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
||||||
depends=["ggwave/include/ggwave/ggwave.h"],
|
depends=["ggwave/include/ggwave/ggwave.h"],
|
||||||
language="c++",
|
language="c++",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ setup(
|
|||||||
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound",
|
||||||
# Build instructions
|
# Build instructions
|
||||||
ext_modules = [Extension("ggwave",
|
ext_modules = [Extension("ggwave",
|
||||||
[ggwave_module_src, "ggwave/src/ggwave.cpp", "ggwave/src/resampler.cpp"],
|
[ggwave_module_src, "ggwave/src/ggwave.cpp"],
|
||||||
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
include_dirs=["ggwave/include", "ggwave/include/ggwave"],
|
||||||
depends=["ggwave/include/ggwave/ggwave.h"],
|
depends=["ggwave/include/ggwave/ggwave.h"],
|
||||||
language="c++",
|
language="c++",
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave/ggwave.h COPYONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave/ggwave.h COPYONLY)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/resampler.h ${CMAKE_CURRENT_SOURCE_DIR}/resampler.h COPYONLY)
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/resampler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/resampler.cpp COPYONLY)
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ public:
|
|||||||
//
|
//
|
||||||
// Call this method after calling encode() to get a list of the tones participating in the generated waveform
|
// Call this method after calling encode() to get a list of the tones participating in the generated waveform
|
||||||
//
|
//
|
||||||
const WaveformTones & getWaveformTones() { return m_waveformTones; }
|
const WaveformTones & getWaveformTones() const;
|
||||||
|
|
||||||
bool takeTxAmplitudeI16(AmplitudeDataI16 & dst);
|
bool takeTxAmplitudeI16(AmplitudeDataI16 & dst);
|
||||||
|
|
||||||
@@ -487,6 +487,53 @@ public:
|
|||||||
//
|
//
|
||||||
static bool computeFFTR(const float * src, float * dst, int N, float d);
|
static bool computeFFTR(const float * src, float * dst, int N, float d);
|
||||||
|
|
||||||
|
// resample audio waveforms from one sample rate to another using
|
||||||
|
// sinc interpolation
|
||||||
|
class Resampler {
|
||||||
|
public:
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
Resampler();
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
int nSamplesTotal() const { return m_state.nSamplesTotal; }
|
||||||
|
|
||||||
|
int resample(
|
||||||
|
float factor,
|
||||||
|
int nSamples,
|
||||||
|
const float * samplesInp,
|
||||||
|
float * samplesOut);
|
||||||
|
|
||||||
|
private:
|
||||||
|
float getData(int j) const;
|
||||||
|
void newData(float data);
|
||||||
|
void makeSinc();
|
||||||
|
double sinc(double x) const;
|
||||||
|
|
||||||
|
static const int kDelaySize = 140;
|
||||||
|
|
||||||
|
// this defines how finely the sinc function is sampled for storage in the table
|
||||||
|
static const int kSamplesPerZeroCrossing = 32;
|
||||||
|
|
||||||
|
std::vector<float> m_sincTable;
|
||||||
|
std::vector<float> m_delayBuffer;
|
||||||
|
std::vector<float> m_edgeSamples;
|
||||||
|
std::vector<float> m_samplesInp;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
int nSamplesTotal = 0;
|
||||||
|
int timeInt = 0;
|
||||||
|
int timeLast = 0;
|
||||||
|
double timeNow = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
State m_state;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void decode_fixed();
|
void decode_fixed();
|
||||||
void decode_variable();
|
void decode_variable();
|
||||||
@@ -530,25 +577,14 @@ private:
|
|||||||
// common
|
// common
|
||||||
TxRxData m_dataEncoded;
|
TxRxData m_dataEncoded;
|
||||||
|
|
||||||
// Tx
|
|
||||||
bool m_hasNewTxData;
|
|
||||||
float m_sendVolume;
|
|
||||||
|
|
||||||
int m_txDataLength;
|
|
||||||
TxRxData m_txData;
|
|
||||||
TxProtocol m_txProtocol;
|
|
||||||
|
|
||||||
AmplitudeData m_outputBlock;
|
|
||||||
AmplitudeData m_outputBlockResampled;
|
|
||||||
TxRxData m_outputBlockTmp;
|
|
||||||
AmplitudeDataI16 m_outputBlockI16;
|
|
||||||
AmplitudeDataI16 m_txAmplitudeDataI16;
|
|
||||||
WaveformTones m_waveformTones;
|
|
||||||
|
|
||||||
// Impl
|
// Impl
|
||||||
// todo : move all members inside Impl
|
struct Rx;
|
||||||
struct Impl;
|
std::unique_ptr<Rx> m_rx;
|
||||||
std::unique_ptr<Impl> m_impl;
|
|
||||||
|
struct Tx;
|
||||||
|
std::unique_ptr<Tx> m_tx;
|
||||||
|
|
||||||
|
std::unique_ptr<Resampler> m_resampler;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ set(TARGET ggwave)
|
|||||||
|
|
||||||
add_library(${TARGET}
|
add_library(${TARGET}
|
||||||
ggwave.cpp
|
ggwave.cpp
|
||||||
resampler.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(${TARGET} PUBLIC
|
target_include_directories(${TARGET} PUBLIC
|
||||||
|
|||||||
794
src/ggwave.cpp
794
src/ggwave.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,161 +0,0 @@
|
|||||||
#include "resampler.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#ifndef M_PI
|
|
||||||
#define M_PI 3.14159265358979323846
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
double linear_interp(double first_number, double second_number, double fraction) {
|
|
||||||
return (first_number + ((second_number - first_number)*fraction));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Resampler::Resampler() :
|
|
||||||
m_sincTable(kWidth*kSamplesPerZeroCrossing),
|
|
||||||
m_delayBuffer(3*kWidth),
|
|
||||||
m_edgeSamples(kWidth),
|
|
||||||
m_samplesInp(2048) {
|
|
||||||
make_sinc();
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Resampler::reset() {
|
|
||||||
m_state = {};
|
|
||||||
std::fill(m_edgeSamples.begin(), m_edgeSamples.end(), 0.0f);
|
|
||||||
std::fill(m_delayBuffer.begin(), m_delayBuffer.end(), 0.0f);
|
|
||||||
std::fill(m_samplesInp.begin(), m_samplesInp.end(), 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Resampler::resample(
|
|
||||||
float factor,
|
|
||||||
int nSamples,
|
|
||||||
const float * samplesInp,
|
|
||||||
float * samplesOut) {
|
|
||||||
int idxInp = -1;
|
|
||||||
int idxOut = 0;
|
|
||||||
int notDone = 1;
|
|
||||||
float data_in = 0.0f;
|
|
||||||
float data_out = 0.0f;
|
|
||||||
double one_over_factor = 1.0;
|
|
||||||
|
|
||||||
auto stateSave = m_state;
|
|
||||||
|
|
||||||
m_state.nSamplesTotal += nSamples;
|
|
||||||
|
|
||||||
if (samplesOut) {
|
|
||||||
assert(nSamples > kWidth);
|
|
||||||
if ((int) m_samplesInp.size() < nSamples + kWidth) {
|
|
||||||
m_samplesInp.resize(nSamples + kWidth);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < kWidth; ++i) {
|
|
||||||
m_samplesInp[i] = m_edgeSamples[i];
|
|
||||||
m_edgeSamples[i] = samplesInp[nSamples - kWidth + i];
|
|
||||||
}
|
|
||||||
for (int i = 0; i < nSamples; ++i) {
|
|
||||||
m_samplesInp[i + kWidth] = samplesInp[i];
|
|
||||||
}
|
|
||||||
samplesInp = m_samplesInp.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (notDone) {
|
|
||||||
while (m_state.timeLast < m_state.timeInt) {
|
|
||||||
if (++idxInp >= nSamples) {
|
|
||||||
notDone = 0;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
data_in = samplesInp[idxInp];
|
|
||||||
}
|
|
||||||
//printf("xxxx idxInp = %d\n", idxInp);
|
|
||||||
if (samplesOut) new_data(data_in);
|
|
||||||
m_state.timeLast += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notDone == false) break;
|
|
||||||
|
|
||||||
double temp1 = 0.0;
|
|
||||||
int left_limit = m_state.timeNow - kWidth + 1; /* leftmost neighboring sample used for interp.*/
|
|
||||||
int right_limit = m_state.timeNow + kWidth; /* rightmost leftmost neighboring sample used for interp.*/
|
|
||||||
if (left_limit < 0) left_limit = 0;
|
|
||||||
if (right_limit > m_state.nSamplesTotal + kWidth) right_limit = m_state.nSamplesTotal + kWidth;
|
|
||||||
if (factor < 1.0) {
|
|
||||||
for (int j = left_limit; j < right_limit; j++) {
|
|
||||||
temp1 += gimme_data(j - m_state.timeInt)*sinc(m_state.timeNow - (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 - m_state.timeInt)*one_over_factor*sinc(one_over_factor*(m_state.timeNow - (double) j));
|
|
||||||
}
|
|
||||||
data_out = temp1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samplesOut) {
|
|
||||||
//printf("inp = %d, l = %d, r = %d, n = %d, a = %d, b = %d\n", idxInp, left_limit, right_limit, m_state.nSamplesTotal, left_limit - m_state.timeInt, right_limit - m_state.timeInt - 1);
|
|
||||||
samplesOut[idxOut] = data_out;
|
|
||||||
}
|
|
||||||
++idxOut;
|
|
||||||
|
|
||||||
m_state.timeNow += factor;
|
|
||||||
m_state.timeLast = m_state.timeInt;
|
|
||||||
m_state.timeInt = m_state.timeNow;
|
|
||||||
while (m_state.timeLast < m_state.timeInt) {
|
|
||||||
if (++idxInp >= nSamples) {
|
|
||||||
notDone = 0;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
data_in = samplesInp[idxInp];
|
|
||||||
}
|
|
||||||
if (samplesOut) new_data(data_in);
|
|
||||||
m_state.timeLast += 1;
|
|
||||||
}
|
|
||||||
//printf("last idxInp = %d, nSamples = %d\n", idxInp, nSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samplesOut == nullptr) {
|
|
||||||
m_state = stateSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
return idxOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
float Resampler::gimme_data(int 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
class Resampler {
|
|
||||||
public:
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
Resampler();
|
|
||||||
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
int nSamplesTotal() const { return m_state.nSamplesTotal; }
|
|
||||||
|
|
||||||
int resample(
|
|
||||||
float factor,
|
|
||||||
int nSamples,
|
|
||||||
const float * samplesInp,
|
|
||||||
float * samplesOut);
|
|
||||||
|
|
||||||
private:
|
|
||||||
float gimme_data(int j) const;
|
|
||||||
void new_data(float data);
|
|
||||||
void make_sinc();
|
|
||||||
double sinc(double x) const;
|
|
||||||
|
|
||||||
static const int kDelaySize = 140;
|
|
||||||
|
|
||||||
// this defines how finely the sinc function is sampled for storage in the table
|
|
||||||
static const int kSamplesPerZeroCrossing = 32;
|
|
||||||
|
|
||||||
std::vector<float> m_sincTable;
|
|
||||||
std::vector<float> m_delayBuffer;
|
|
||||||
std::vector<float> m_edgeSamples;
|
|
||||||
std::vector<float> m_samplesInp;
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
int nSamplesTotal = 0;
|
|
||||||
int timeInt = 0;
|
|
||||||
int timeLast = 0;
|
|
||||||
double timeNow = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
State m_state;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user