mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-04-20 13:16:31 +08:00
Support for various sample formats (#11)
* wip : support for various sample formats * finalize support for various sample formats * adding more tests * update python bindings * add "string" header
This commit is contained in:
@@ -164,12 +164,6 @@ sudo snap connect waver:audio-record :audio-record
|
|||||||
brew install ggerganov/ggerganov/waver
|
brew install ggerganov/ggerganov/waver
|
||||||
```
|
```
|
||||||
|
|
||||||
## Todo
|
|
||||||
|
|
||||||
- [ ] Improve library interface
|
|
||||||
- [ ] Support for non-float32 input and non-int16 output
|
|
||||||
- [x] Mobile app examples
|
|
||||||
|
|
||||||
[changelog]: ./CHANGELOG.md
|
[changelog]: ./CHANGELOG.md
|
||||||
[changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v0.1-dummy
|
[changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v0.1-dummy
|
||||||
[license]: ./LICENSE
|
[license]: ./LICENSE
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
cdef extern from "ggwave.h" nogil:
|
cdef extern from "ggwave.h" nogil:
|
||||||
|
|
||||||
ctypedef enum ggwave_SampleFormat:
|
ctypedef enum ggwave_SampleFormat:
|
||||||
|
GGWAVE_SAMPLE_FORMAT_UNDEFINED,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_I8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U16,
|
||||||
GGWAVE_SAMPLE_FORMAT_I16,
|
GGWAVE_SAMPLE_FORMAT_I16,
|
||||||
GGWAVE_SAMPLE_FORMAT_F32
|
GGWAVE_SAMPLE_FORMAT_F32
|
||||||
|
|
||||||
@@ -16,12 +20,12 @@ cdef extern from "ggwave.h" nogil:
|
|||||||
int sampleRateIn
|
int sampleRateIn
|
||||||
int sampleRateOut
|
int sampleRateOut
|
||||||
int samplesPerFrame
|
int samplesPerFrame
|
||||||
ggwave_SampleFormat formatIn
|
ggwave_SampleFormat sampleFormatIn
|
||||||
ggwave_SampleFormat formatOut
|
ggwave_SampleFormat sampleFormatOut
|
||||||
|
|
||||||
ctypedef int ggwave_Instance
|
ctypedef int ggwave_Instance
|
||||||
|
|
||||||
ggwave_Parameters ggwave_defaultParameters();
|
ggwave_Parameters ggwave_getDefaultParameters();
|
||||||
|
|
||||||
ggwave_Instance ggwave_init(const ggwave_Parameters parameters);
|
ggwave_Instance ggwave_init(const ggwave_Parameters parameters);
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import struct
|
|||||||
|
|
||||||
cimport cggwave
|
cimport cggwave
|
||||||
|
|
||||||
def defaultParameters():
|
def getDefaultParameters():
|
||||||
return cggwave.ggwave_defaultParameters()
|
return cggwave.ggwave_getDefaultParameters()
|
||||||
|
|
||||||
def init(parameters = None):
|
def init(parameters = None):
|
||||||
if (parameters is None):
|
if (parameters is None):
|
||||||
parameters = defaultParameters()
|
parameters = getDefaultParameters()
|
||||||
|
|
||||||
return cggwave.ggwave_init(parameters)
|
return cggwave.ggwave_init(parameters)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ def encode(payload, txProtocolId = 1, volume = 10, instance = None):
|
|||||||
own = False
|
own = False
|
||||||
if (instance is None):
|
if (instance is None):
|
||||||
own = True
|
own = True
|
||||||
instance = init(defaultParameters())
|
instance = init(getDefaultParameters())
|
||||||
|
|
||||||
n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), txProtocolId, volume, coutput)
|
n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), txProtocolId, volume, coutput)
|
||||||
|
|
||||||
|
|||||||
@@ -188,48 +188,37 @@ bool GGWave_init(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int sampleSizeBytesIn = 4;
|
GGWave::SampleFormat sampleFormatIn = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
|
||||||
int sampleSizeBytesOut = 2;
|
GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
switch (g_obtainedSpecIn.format) {
|
switch (g_obtainedSpecIn.format) {
|
||||||
case AUDIO_U8:
|
case AUDIO_U8: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_U8; break;
|
||||||
case AUDIO_S8:
|
case AUDIO_S8: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_I8; break;
|
||||||
sampleSizeBytesIn = 1;
|
case AUDIO_U16SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_U16; break;
|
||||||
break;
|
case AUDIO_S16SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_I16; break;
|
||||||
case AUDIO_U16SYS:
|
case AUDIO_S32SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_F32; break;
|
||||||
case AUDIO_S16SYS:
|
case AUDIO_F32SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_F32; break;
|
||||||
sampleSizeBytesIn = 2;
|
|
||||||
break;
|
|
||||||
case AUDIO_S32SYS:
|
|
||||||
case AUDIO_F32SYS:
|
|
||||||
sampleSizeBytesIn = 4;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (g_obtainedSpecOut.format) {
|
switch (g_obtainedSpecOut.format) {
|
||||||
case AUDIO_U8:
|
case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break;
|
||||||
case AUDIO_S8:
|
case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break;
|
||||||
sampleSizeBytesOut = 1;
|
case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;
|
||||||
break;
|
case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;
|
||||||
case AUDIO_U16SYS:
|
case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
|
||||||
case AUDIO_S16SYS:
|
case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
|
||||||
sampleSizeBytesOut = 2;
|
|
||||||
break;
|
|
||||||
case AUDIO_S32SYS:
|
|
||||||
case AUDIO_F32SYS:
|
|
||||||
sampleSizeBytesOut = 4;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reinit) {
|
if (reinit) {
|
||||||
if (g_ggWave) delete g_ggWave;
|
if (g_ggWave) delete g_ggWave;
|
||||||
|
|
||||||
g_ggWave = new GGWave(
|
g_ggWave = new GGWave({
|
||||||
g_obtainedSpecIn.freq,
|
g_obtainedSpecIn.freq,
|
||||||
g_obtainedSpecOut.freq,
|
g_obtainedSpecOut.freq,
|
||||||
GGWave::kDefaultSamplesPerFrame,
|
GGWave::kDefaultSamplesPerFrame,
|
||||||
sampleSizeBytesIn,
|
sampleFormatIn,
|
||||||
sampleSizeBytesOut);
|
sampleFormatOut});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -246,7 +235,7 @@ bool GGWave_mainLoop() {
|
|||||||
SDL_QueueAudio(g_devIdOut, data, nBytes);
|
SDL_QueueAudio(g_devIdOut, data, nBytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
|
static GGWave::CBDequeueAudio cbDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
|
||||||
return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes);
|
return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -259,7 +248,7 @@ bool GGWave_mainLoop() {
|
|||||||
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
|
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
|
||||||
SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE);
|
SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE);
|
||||||
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
|
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
|
||||||
g_ggWave->decode(CBDequeueAudio);
|
g_ggWave->decode(cbDequeueAudio);
|
||||||
if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) {
|
if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) {
|
||||||
SDL_ClearQueuedAudio(g_devIdIn);
|
SDL_ClearQueuedAudio(g_devIdIn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str());
|
fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str());
|
||||||
|
|
||||||
GGWave ggWave(GGWave::kBaseSampleRate, sampleRateOut, 1024, 4, 2);
|
GGWave ggWave({ GGWave::kBaseSampleRate, sampleRateOut, 1024, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16 });
|
||||||
ggWave.init(message.size(), message.data(), ggWave.getTxProtocol(protocolId), volume);
|
ggWave.init(message.size(), message.data(), ggWave.getTxProtocol(protocolId), volume);
|
||||||
|
|
||||||
std::vector<char> bufferPCM;
|
std::vector<char> bufferPCM;
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ struct State {
|
|||||||
|
|
||||||
Message message;
|
Message message;
|
||||||
GGWave::SpectrumData spectrum;
|
GGWave::SpectrumData spectrum;
|
||||||
GGWave::AmplitudeData16 txAmplitudeData;
|
GGWave::AmplitudeDataI16 txAmplitudeData;
|
||||||
GGWaveStats stats;
|
GGWaveStats stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -563,7 +563,7 @@ void updateCore() {
|
|||||||
g_buffer.stateCore.flags.newSpectrum = true;
|
g_buffer.stateCore.flags.newSpectrum = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_ggWave->takeTxAmplitudeData16(g_buffer.stateCore.txAmplitudeData)) {
|
if (g_ggWave->takeTxAmplitudeDataI16(g_buffer.stateCore.txAmplitudeData)) {
|
||||||
g_buffer.stateCore.update = true;
|
g_buffer.stateCore.update = true;
|
||||||
g_buffer.stateCore.flags.newTxAmplitudeData = true;
|
g_buffer.stateCore.flags.newTxAmplitudeData = true;
|
||||||
}
|
}
|
||||||
@@ -696,7 +696,7 @@ void renderMain() {
|
|||||||
|
|
||||||
static GGWaveStats statsCurrent;
|
static GGWaveStats statsCurrent;
|
||||||
static GGWave::SpectrumData spectrumCurrent;
|
static GGWave::SpectrumData spectrumCurrent;
|
||||||
static GGWave::AmplitudeData16 txAmplitudeDataCurrent;
|
static GGWave::AmplitudeDataI16 txAmplitudeDataCurrent;
|
||||||
static std::vector<Message> messageHistory;
|
static std::vector<Message> messageHistory;
|
||||||
|
|
||||||
if (stateCurrent.update) {
|
if (stateCurrent.update) {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ extern "C" {
|
|||||||
|
|
||||||
// Data format of the audio samples
|
// Data format of the audio samples
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
GGWAVE_SAMPLE_FORMAT_UNDEFINED,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_I8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U16,
|
||||||
GGWAVE_SAMPLE_FORMAT_I16,
|
GGWAVE_SAMPLE_FORMAT_I16,
|
||||||
GGWAVE_SAMPLE_FORMAT_F32,
|
GGWAVE_SAMPLE_FORMAT_F32,
|
||||||
} ggwave_SampleFormat;
|
} ggwave_SampleFormat;
|
||||||
@@ -41,11 +45,11 @@ extern "C" {
|
|||||||
|
|
||||||
// GGWave instance parameters
|
// GGWave instance parameters
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int sampleRateIn; // capture sample rate
|
int sampleRateIn; // capture sample rate
|
||||||
int sampleRateOut; // playback sample rate
|
int sampleRateOut; // playback sample rate
|
||||||
int samplesPerFrame; // number of samples per audio frame
|
int samplesPerFrame; // number of samples per audio frame
|
||||||
ggwave_SampleFormat formatIn; // format of the captured audio samples
|
ggwave_SampleFormat sampleFormatIn; // format of the captured audio samples
|
||||||
ggwave_SampleFormat formatOut; // format of the playback audio samples
|
ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples
|
||||||
} ggwave_Parameters;
|
} ggwave_Parameters;
|
||||||
|
|
||||||
// GGWave instances are identified with an integer and are stored
|
// GGWave instances are identified with an integer and are stored
|
||||||
@@ -54,7 +58,7 @@ extern "C" {
|
|||||||
typedef int ggwave_Instance;
|
typedef int ggwave_Instance;
|
||||||
|
|
||||||
// Helper method to get default instance parameters
|
// Helper method to get default instance parameters
|
||||||
GGWAVE_API ggwave_Parameters ggwave_defaultParameters(void);
|
GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void);
|
||||||
|
|
||||||
// Create a new GGWave instance with the specified parameters
|
// Create a new GGWave instance with the specified parameters
|
||||||
GGWAVE_API ggwave_Instance ggwave_init(const ggwave_Parameters parameters);
|
GGWAVE_API ggwave_Instance ggwave_init(const ggwave_Parameters parameters);
|
||||||
@@ -120,6 +124,7 @@ extern "C" {
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class GGWave {
|
class GGWave {
|
||||||
public:
|
public:
|
||||||
@@ -133,7 +138,9 @@ public:
|
|||||||
static constexpr auto kMaxSpectrumHistory = 4;
|
static constexpr auto kMaxSpectrumHistory = 4;
|
||||||
static constexpr auto kMaxRecordedFrames = 1024;
|
static constexpr auto kMaxRecordedFrames = 1024;
|
||||||
|
|
||||||
using TxProtocolId = ggwave_TxProtocolId;
|
using Parameters = ggwave_Parameters;
|
||||||
|
using SampleFormat = ggwave_SampleFormat;
|
||||||
|
using TxProtocolId = ggwave_TxProtocolId;
|
||||||
|
|
||||||
struct TxProtocol {
|
struct TxProtocol {
|
||||||
const char * name; // string identifier of the protocol
|
const char * name; // string identifier of the protocol
|
||||||
@@ -160,26 +167,24 @@ public:
|
|||||||
return kTxProtocols;
|
return kTxProtocols;
|
||||||
}
|
}
|
||||||
|
|
||||||
using AmplitudeData = std::vector<float>;
|
using AmplitudeData = std::vector<float>;
|
||||||
using AmplitudeData16 = std::vector<int16_t>;
|
using AmplitudeDataI16 = std::vector<int16_t>;
|
||||||
using SpectrumData = std::vector<float>;
|
using SpectrumData = std::vector<float>;
|
||||||
using RecordedData = std::vector<float>;
|
using RecordedData = std::vector<float>;
|
||||||
using TxRxData = std::vector<std::uint8_t>;
|
using TxRxData = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
using CBEnqueueAudio = std::function<void(const void * data, uint32_t nBytes)>;
|
using CBEnqueueAudio = std::function<void(const void * data, uint32_t nBytes)>;
|
||||||
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
|
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
|
||||||
|
|
||||||
GGWave(
|
GGWave(const Parameters & parameters);
|
||||||
int sampleRateIn,
|
|
||||||
int sampleRateOut,
|
|
||||||
int samplesPerFrame,
|
|
||||||
int sampleSizeBytesIn,
|
|
||||||
int sampleSizeBytesOut);
|
|
||||||
|
|
||||||
~GGWave();
|
~GGWave();
|
||||||
|
|
||||||
|
static const Parameters & getDefaultParameters();
|
||||||
|
|
||||||
|
bool init(const std::string & text, const int volume = kDefaultVolume);
|
||||||
|
bool init(const std::string & text, const TxProtocol & txProtocol, const int volume = kDefaultVolume);
|
||||||
bool init(int dataSize, const char * dataBuffer, const int volume = kDefaultVolume);
|
bool init(int dataSize, const char * dataBuffer, const int volume = kDefaultVolume);
|
||||||
bool init(int dataSize, const char * dataBuffer, const TxProtocol & aProtocol, const int volume = kDefaultVolume);
|
bool init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume = kDefaultVolume);
|
||||||
|
|
||||||
bool encode(const CBEnqueueAudio & cbEnqueueAudio);
|
bool encode(const CBEnqueueAudio & cbEnqueueAudio);
|
||||||
void decode(const CBDequeueAudio & cbDequeueAudio);
|
void decode(const CBDequeueAudio & cbDequeueAudio);
|
||||||
@@ -209,7 +214,7 @@ public:
|
|||||||
const TxProtocolId & getRxProtocolId() const { return m_rxProtocolId; }
|
const TxProtocolId & getRxProtocolId() const { return m_rxProtocolId; }
|
||||||
|
|
||||||
int takeRxData(TxRxData & dst);
|
int takeRxData(TxRxData & dst);
|
||||||
int takeTxAmplitudeData16(AmplitudeData16 & dst);
|
int takeTxAmplitudeDataI16(AmplitudeDataI16 & dst);
|
||||||
bool takeSpectrum(SpectrumData & dst);
|
bool takeSpectrum(SpectrumData & dst);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -226,6 +231,8 @@ private:
|
|||||||
const float m_isamplesPerFrame;
|
const float m_isamplesPerFrame;
|
||||||
const int m_sampleSizeBytesIn;
|
const int m_sampleSizeBytesIn;
|
||||||
const int m_sampleSizeBytesOut;
|
const int m_sampleSizeBytesOut;
|
||||||
|
const SampleFormat m_sampleFormatIn;
|
||||||
|
const SampleFormat m_sampleFormatOut;
|
||||||
|
|
||||||
const float m_hzPerSample;
|
const float m_hzPerSample;
|
||||||
const float m_ihzPerSample;
|
const float m_ihzPerSample;
|
||||||
@@ -249,6 +256,7 @@ private:
|
|||||||
int m_framesLeftToRecord;
|
int m_framesLeftToRecord;
|
||||||
int m_framesToAnalyze;
|
int m_framesToAnalyze;
|
||||||
int m_framesToRecord;
|
int m_framesToRecord;
|
||||||
|
int m_samplesNeeded;
|
||||||
|
|
||||||
std::vector<float> m_fftIn; // real
|
std::vector<float> m_fftIn; // real
|
||||||
std::vector<float> m_fftOut; // complex
|
std::vector<float> m_fftOut; // complex
|
||||||
@@ -256,6 +264,7 @@ private:
|
|||||||
bool m_hasNewSpectrum;
|
bool m_hasNewSpectrum;
|
||||||
SpectrumData m_sampleSpectrum;
|
SpectrumData m_sampleSpectrum;
|
||||||
AmplitudeData m_sampleAmplitude;
|
AmplitudeData m_sampleAmplitude;
|
||||||
|
TxRxData m_sampleAmplitudeTmp;
|
||||||
|
|
||||||
bool m_hasNewRxData;
|
bool m_hasNewRxData;
|
||||||
int m_lastRxDataLength;
|
int m_lastRxDataLength;
|
||||||
@@ -282,8 +291,9 @@ private:
|
|||||||
TxProtocol m_txProtocol;
|
TxProtocol m_txProtocol;
|
||||||
|
|
||||||
AmplitudeData m_outputBlock;
|
AmplitudeData m_outputBlock;
|
||||||
AmplitudeData16 m_outputBlock16;
|
TxRxData m_outputBlockTmp;
|
||||||
AmplitudeData16 m_txAmplitudeData16;
|
AmplitudeDataI16 m_outputBlockI16;
|
||||||
|
AmplitudeDataI16 m_txAmplitudeDataI16;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
264
src/ggwave.cpp
264
src/ggwave.cpp
@@ -3,8 +3,9 @@
|
|||||||
#include "reed-solomon/rs.hpp"
|
#include "reed-solomon/rs.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
//#include <random>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@@ -17,27 +18,20 @@ std::map<ggwave_Instance, GGWave *> g_instances;
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
ggwave_Parameters ggwave_defaultParameters(void) {
|
ggwave_Parameters ggwave_getDefaultParameters(void) {
|
||||||
ggwave_Parameters result {
|
return GGWave::getDefaultParameters();
|
||||||
GGWave::kBaseSampleRate,
|
|
||||||
GGWave::kBaseSampleRate,
|
|
||||||
GGWave::kDefaultSamplesPerFrame,
|
|
||||||
GGWAVE_SAMPLE_FORMAT_F32,
|
|
||||||
GGWAVE_SAMPLE_FORMAT_I16
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
|
ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
|
||||||
static ggwave_Instance curId = 0;
|
static ggwave_Instance curId = 0;
|
||||||
|
|
||||||
g_instances[curId] = new GGWave(
|
g_instances[curId] = new GGWave({
|
||||||
parameters.sampleRateIn,
|
parameters.sampleRateIn,
|
||||||
parameters.sampleRateOut,
|
parameters.sampleRateOut,
|
||||||
parameters.samplesPerFrame,
|
parameters.samplesPerFrame,
|
||||||
4, // todo : hardcoded sample sizes
|
GGWAVE_SAMPLE_FORMAT_F32,
|
||||||
2);
|
GGWAVE_SAMPLE_FORMAT_I16});
|
||||||
|
|
||||||
return curId++;
|
return curId++;
|
||||||
}
|
}
|
||||||
@@ -74,8 +68,7 @@ int ggwave_encode(
|
|||||||
char * p = (char *) data;
|
char * p = (char *) data;
|
||||||
std::copy(p, p + nBytes, outputBuffer);
|
std::copy(p, p + nBytes, outputBuffer);
|
||||||
|
|
||||||
// todo : tmp assume int16
|
nSamples = nBytes/ggWave->getSampleSizeBytesOut();
|
||||||
nSamples = nBytes/2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ggWave->encode(cbEnqueueAudio) == false) {
|
if (ggWave->encode(cbEnqueueAudio) == false) {
|
||||||
@@ -242,21 +235,45 @@ int getECCBytesForLength(int len) {
|
|||||||
return std::max(4, 2*(len/5));
|
return std::max(4, 2*(len/5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int bytesForSampleFormat(GGWave::SampleFormat sampleFormat) {
|
||||||
|
switch (sampleFormat) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: return 0; break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: return sizeof(uint8_t); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: return sizeof(int8_t); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: return sizeof(uint16_t); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: return sizeof(int16_t); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: return sizeof(float); break;
|
||||||
|
};
|
||||||
|
|
||||||
|
fprintf(stderr, "Invalid sample format: %d\n", (int) sampleFormat);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
GGWave::GGWave(
|
}
|
||||||
int sampleRateIn,
|
|
||||||
int sampleRateOut,
|
const GGWave::Parameters & GGWave::getDefaultParameters() {
|
||||||
int samplesPerFrame,
|
static ggwave_Parameters result {
|
||||||
int sampleSizeBytesIn,
|
GGWave::kBaseSampleRate,
|
||||||
int sampleSizeBytesOut) :
|
GGWave::kBaseSampleRate,
|
||||||
m_sampleRateIn(sampleRateIn),
|
GGWave::kDefaultSamplesPerFrame,
|
||||||
m_sampleRateOut(sampleRateOut),
|
GGWAVE_SAMPLE_FORMAT_F32,
|
||||||
m_samplesPerFrame(samplesPerFrame),
|
GGWAVE_SAMPLE_FORMAT_I16
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
GGWave::GGWave(const Parameters & parameters) :
|
||||||
|
m_sampleRateIn(parameters.sampleRateIn),
|
||||||
|
m_sampleRateOut(parameters.sampleRateOut),
|
||||||
|
m_samplesPerFrame(parameters.samplesPerFrame),
|
||||||
m_isamplesPerFrame(1.0f/m_samplesPerFrame),
|
m_isamplesPerFrame(1.0f/m_samplesPerFrame),
|
||||||
m_sampleSizeBytesIn(sampleSizeBytesIn),
|
m_sampleSizeBytesIn(bytesForSampleFormat(parameters.sampleFormatIn)),
|
||||||
m_sampleSizeBytesOut(sampleSizeBytesOut),
|
m_sampleSizeBytesOut(bytesForSampleFormat(parameters.sampleFormatOut)),
|
||||||
m_hzPerSample(m_sampleRateIn/samplesPerFrame),
|
m_sampleFormatIn(parameters.sampleFormatIn),
|
||||||
|
m_sampleFormatOut(parameters.sampleFormatOut),
|
||||||
|
m_hzPerSample(m_sampleRateIn/parameters.samplesPerFrame),
|
||||||
m_ihzPerSample(1.0f/m_hzPerSample),
|
m_ihzPerSample(1.0f/m_hzPerSample),
|
||||||
m_freqDelta_bin(1),
|
m_freqDelta_bin(1),
|
||||||
m_freqDelta_hz(2*m_hzPerSample),
|
m_freqDelta_hz(2*m_hzPerSample),
|
||||||
@@ -264,11 +281,13 @@ GGWave::GGWave(
|
|||||||
m_nMarkerFrames(16),
|
m_nMarkerFrames(16),
|
||||||
m_nPostMarkerFrames(0),
|
m_nPostMarkerFrames(0),
|
||||||
m_encodedDataOffset(3),
|
m_encodedDataOffset(3),
|
||||||
|
m_samplesNeeded(m_samplesPerFrame),
|
||||||
m_fftIn(kMaxSamplesPerFrame),
|
m_fftIn(kMaxSamplesPerFrame),
|
||||||
m_fftOut(2*kMaxSamplesPerFrame),
|
m_fftOut(2*kMaxSamplesPerFrame),
|
||||||
m_hasNewSpectrum(false),
|
m_hasNewSpectrum(false),
|
||||||
m_sampleSpectrum(kMaxSamplesPerFrame),
|
m_sampleSpectrum(kMaxSamplesPerFrame),
|
||||||
m_sampleAmplitude(kMaxSamplesPerFrame),
|
m_sampleAmplitude(kMaxSamplesPerFrame),
|
||||||
|
m_sampleAmplitudeTmp(kMaxSamplesPerFrame*m_sampleSizeBytesIn),
|
||||||
m_hasNewRxData(false),
|
m_hasNewRxData(false),
|
||||||
m_lastRxDataLength(0),
|
m_lastRxDataLength(0),
|
||||||
m_rxData(kMaxDataSize),
|
m_rxData(kMaxDataSize),
|
||||||
@@ -278,23 +297,40 @@ GGWave::GGWave(
|
|||||||
m_txData(kMaxDataSize),
|
m_txData(kMaxDataSize),
|
||||||
m_txDataEncoded(kMaxDataSize),
|
m_txDataEncoded(kMaxDataSize),
|
||||||
m_outputBlock(kMaxSamplesPerFrame),
|
m_outputBlock(kMaxSamplesPerFrame),
|
||||||
m_outputBlock16(kMaxRecordedFrames*kMaxSamplesPerFrame)
|
m_outputBlockTmp(kMaxRecordedFrames*kMaxSamplesPerFrame*m_sampleSizeBytesOut),
|
||||||
{
|
m_outputBlockI16(kMaxRecordedFrames*kMaxSamplesPerFrame) {
|
||||||
if (samplesPerFrame > kMaxSamplesPerFrame) {
|
|
||||||
|
if (m_sampleSizeBytesIn == 0) {
|
||||||
|
throw std::runtime_error("Invalid or unsupported capture sample format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_sampleSizeBytesOut == 0) {
|
||||||
|
throw std::runtime_error("Invalid or unsupported playback sample format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {
|
||||||
throw std::runtime_error("Invalid samples per frame");
|
throw std::runtime_error("Invalid samples per frame");
|
||||||
}
|
}
|
||||||
|
|
||||||
init(0, "", getDefaultTxProtocol(), 0);
|
init("", getDefaultTxProtocol(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GGWave::~GGWave() {
|
GGWave::~GGWave() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GGWave::init(const std::string & text, const int volume) {
|
||||||
|
return init(text.size(), text.data(), getDefaultTxProtocol(), volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GGWave::init(const std::string & text, const TxProtocol & txProtocol, const int volume) {
|
||||||
|
return init(text.size(), text.data(), txProtocol, volume);
|
||||||
|
}
|
||||||
|
|
||||||
bool GGWave::init(int dataSize, const char * dataBuffer, const int volume) {
|
bool GGWave::init(int dataSize, const char * dataBuffer, const int volume) {
|
||||||
return init(dataSize, dataBuffer, getDefaultTxProtocol(), volume);
|
return init(dataSize, dataBuffer, getDefaultTxProtocol(), volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & aProtocol, const int volume) {
|
bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume) {
|
||||||
if (dataSize < 0) {
|
if (dataSize < 0) {
|
||||||
fprintf(stderr, "Negative data size: %d\n", dataSize);
|
fprintf(stderr, "Negative data size: %d\n", dataSize);
|
||||||
return false;
|
return false;
|
||||||
@@ -310,7 +346,7 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & aPro
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_txProtocol = aProtocol;
|
m_txProtocol = txProtocol;
|
||||||
m_txDataLength = dataSize;
|
m_txDataLength = dataSize;
|
||||||
m_sendVolume = ((double)(volume))/100.0f;
|
m_sendVolume = ((double)(volume))/100.0f;
|
||||||
|
|
||||||
@@ -372,10 +408,10 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// note : what is the purpose of this shuffle ? I forgot .. :(
|
// note : what is the purpose of this shuffle ? I forgot .. :(
|
||||||
std::random_device rd;
|
//std::random_device rd;
|
||||||
std::mt19937 g(rd());
|
//std::mt19937 g(rd());
|
||||||
|
|
||||||
std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);
|
//std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);
|
||||||
|
|
||||||
std::vector<bool> dataBits(kMaxDataBits);
|
std::vector<bool> dataBits(kMaxDataBits);
|
||||||
|
|
||||||
@@ -507,19 +543,75 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
|
|||||||
m_outputBlock[i] *= scale;
|
m_outputBlock[i] *= scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo : support for non-int16 output
|
uint32_t offset = frameId*samplesPerFrameOut;
|
||||||
|
|
||||||
|
// default output is in 16-bit signed int so we always compute it
|
||||||
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
m_outputBlock16[frameId*samplesPerFrameOut + i] = std::round(32000.0*m_outputBlock[i]);
|
m_outputBlockI16[offset + i] = 32768*m_outputBlock[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert from 32-bit float
|
||||||
|
switch (m_sampleFormatOut) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
||||||
|
{
|
||||||
|
auto p = reinterpret_cast<uint8_t *>(m_outputBlockTmp.data());
|
||||||
|
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
|
p[offset + i] = 128*(m_outputBlock[i] + 1.0f);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
||||||
|
{
|
||||||
|
auto p = reinterpret_cast<uint8_t *>(m_outputBlockTmp.data());
|
||||||
|
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
|
p[offset + i] = 128*m_outputBlock[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
||||||
|
{
|
||||||
|
auto p = reinterpret_cast<uint16_t *>(m_outputBlockTmp.data());
|
||||||
|
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
|
p[offset + i] = 32768*(m_outputBlock[i] + 1.0f);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
||||||
|
{
|
||||||
|
// skip because we already have the data in m_outputBlockI16
|
||||||
|
//auto p = reinterpret_cast<uint16_t *>(m_outputBlockTmp.data());
|
||||||
|
//for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
|
// p[offset + i] = 32768*m_outputBlock[i];
|
||||||
|
//}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
||||||
|
{
|
||||||
|
auto p = reinterpret_cast<float *>(m_outputBlockTmp.data());
|
||||||
|
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||||
|
p[offset + i] = m_outputBlock[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
++frameId;
|
++frameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
cbEnqueueAudio(m_outputBlock16.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
|
switch (m_sampleFormatOut) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
||||||
|
{
|
||||||
|
cbEnqueueAudio(m_outputBlockI16.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
||||||
|
{
|
||||||
|
cbEnqueueAudio(m_outputBlockTmp.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
m_txAmplitudeData16.resize(frameId*samplesPerFrameOut);
|
m_txAmplitudeDataI16.resize(frameId*samplesPerFrameOut);
|
||||||
for (int i = 0; i < frameId*samplesPerFrameOut; ++i) {
|
for (int i = 0; i < frameId*samplesPerFrameOut; ++i) {
|
||||||
m_txAmplitudeData16[i] = m_outputBlock16[i];
|
m_txAmplitudeDataI16[i] = m_outputBlockI16[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -528,11 +620,84 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
|
|||||||
void GGWave::decode(const CBDequeueAudio & cbDequeueAudio) {
|
void GGWave::decode(const CBDequeueAudio & cbDequeueAudio) {
|
||||||
while (m_hasNewTxData == false) {
|
while (m_hasNewTxData == false) {
|
||||||
// read capture data
|
// read capture data
|
||||||
//
|
uint32_t nBytesNeeded = m_samplesNeeded*m_sampleSizeBytesIn;
|
||||||
// todo : support for non-float input
|
uint32_t nBytesRecorded = 0;
|
||||||
auto nBytesRecorded = cbDequeueAudio(m_sampleAmplitude.data(), m_samplesPerFrame*m_sampleSizeBytesIn);
|
uint32_t offset = m_samplesPerFrame - m_samplesNeeded;
|
||||||
|
|
||||||
if (nBytesRecorded != 0) {
|
switch (m_sampleFormatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
||||||
|
{
|
||||||
|
nBytesRecorded = cbDequeueAudio(m_sampleAmplitudeTmp.data() + offset, nBytesNeeded);
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
||||||
|
{
|
||||||
|
nBytesRecorded = cbDequeueAudio(m_sampleAmplitude.data() + offset, nBytesNeeded);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nBytesRecorded % m_sampleSizeBytesIn != 0) {
|
||||||
|
fprintf(stderr, "Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n",
|
||||||
|
nBytesRecorded, m_sampleSizeBytesIn);
|
||||||
|
m_samplesNeeded = m_samplesPerFrame;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nBytesRecorded > nBytesNeeded) {
|
||||||
|
fprintf(stderr, "Failure during capture - more samples were provided (%d) than requested (%d)\n",
|
||||||
|
nBytesRecorded/m_sampleSizeBytesIn, nBytesNeeded/m_sampleSizeBytesIn);
|
||||||
|
m_samplesNeeded = m_samplesPerFrame;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to 32-bit float
|
||||||
|
switch (m_sampleFormatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
||||||
|
{
|
||||||
|
constexpr float scale = 1.0f/128;
|
||||||
|
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
|
||||||
|
auto p = reinterpret_cast<uint8_t *>(m_sampleAmplitudeTmp.data());
|
||||||
|
for (int i = 0; i < nSamplesRecorded; ++i) {
|
||||||
|
m_sampleAmplitude[offset + i] = float(int16_t(*(p + offset + i)) - 128)*scale;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
||||||
|
{
|
||||||
|
constexpr float scale = 1.0f/128;
|
||||||
|
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
|
||||||
|
auto p = reinterpret_cast<int8_t *>(m_sampleAmplitudeTmp.data());
|
||||||
|
for (int i = 0; i < nSamplesRecorded; ++i) {
|
||||||
|
m_sampleAmplitude[offset + i] = float(*(p + offset + i))*scale;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
||||||
|
{
|
||||||
|
constexpr float scale = 1.0f/32768;
|
||||||
|
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
|
||||||
|
auto p = reinterpret_cast<uint16_t *>(m_sampleAmplitudeTmp.data());
|
||||||
|
for (int i = 0; i < nSamplesRecorded; ++i) {
|
||||||
|
m_sampleAmplitude[offset + i] = float(int32_t(*(p + offset + i)) - 32768)*scale;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
||||||
|
{
|
||||||
|
constexpr float scale = 1.0f/32768;
|
||||||
|
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
|
||||||
|
auto p = reinterpret_cast<int16_t *>(m_sampleAmplitudeTmp.data());
|
||||||
|
for (int i = 0; i < nSamplesRecorded; ++i) {
|
||||||
|
m_sampleAmplitude[offset + i] = float(*(p + offset + i))*scale;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have enough bytes to do analysis
|
||||||
|
if (nBytesRecorded == nBytesNeeded) {
|
||||||
|
m_samplesNeeded = m_samplesPerFrame;
|
||||||
m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude;
|
m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude;
|
||||||
|
|
||||||
if (++m_historyId >= kMaxSpectrumHistory) {
|
if (++m_historyId >= kMaxSpectrumHistory) {
|
||||||
@@ -784,6 +949,7 @@ void GGWave::decode(const CBDequeueAudio & cbDequeueAudio) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
m_samplesNeeded -= nBytesRecorded/m_sampleSizeBytesIn;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -802,11 +968,11 @@ int GGWave::takeRxData(TxRxData & dst) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GGWave::takeTxAmplitudeData16(AmplitudeData16 & dst) {
|
int GGWave::takeTxAmplitudeDataI16(AmplitudeDataI16 & dst) {
|
||||||
if (m_txAmplitudeData16.size() == 0) return 0;
|
if (m_txAmplitudeDataI16.size() == 0) return 0;
|
||||||
|
|
||||||
int res = (int) m_txAmplitudeData16.size();
|
int res = (int) m_txAmplitudeDataI16.size();
|
||||||
dst = std::move(m_txAmplitudeData16);
|
dst = std::move(m_txAmplitudeDataI16);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
#include "ggwave/ggwave.h"
|
#include "ggwave/ggwave.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <typeindex>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#define CHECK(cond) \
|
#define CHECK(cond) \
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
@@ -11,29 +16,198 @@
|
|||||||
#define CHECK_T(cond) CHECK(cond)
|
#define CHECK_T(cond) CHECK(cond)
|
||||||
#define CHECK_F(cond) CHECK(!(cond))
|
#define CHECK_F(cond) CHECK(!(cond))
|
||||||
|
|
||||||
|
const std::map<std::type_index, float> kSampleScale = {
|
||||||
|
{ typeid(uint8_t), std::numeric_limits<uint8_t>::max() },
|
||||||
|
{ typeid(int8_t), std::numeric_limits<int8_t>::max() },
|
||||||
|
{ typeid(uint16_t), std::numeric_limits<uint16_t>::max() },
|
||||||
|
{ typeid(int16_t), std::numeric_limits<int16_t>::max() },
|
||||||
|
{ typeid(float), 1.0f },
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::map<std::type_index, float> kSampleOffset = {
|
||||||
|
{ typeid(uint8_t), 0.5f*std::numeric_limits<uint8_t>::max() },
|
||||||
|
{ typeid(int8_t), 0.0f },
|
||||||
|
{ typeid(uint16_t), 0.5f*std::numeric_limits<uint16_t>::max() },
|
||||||
|
{ typeid(int16_t), 0.0f },
|
||||||
|
{ typeid(float), 0.0f },
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::set<GGWave::SampleFormat> kFormats = {
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_I8,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_U16,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_I16,
|
||||||
|
GGWAVE_SAMPLE_FORMAT_F32,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
GGWave::CBEnqueueAudio getCBEnqueueAudio(uint32_t & nSamples, std::vector<T> & buffer) {
|
||||||
|
return [&nSamples, &buffer](const void * data, uint32_t nBytes) {
|
||||||
|
nSamples = nBytes/sizeof(T);
|
||||||
|
CHECK(nSamples*sizeof(T) == nBytes);
|
||||||
|
buffer.resize(nSamples);
|
||||||
|
std::copy((char *) data, (char *) data + nBytes, (char *) buffer.data());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
GGWave::CBDequeueAudio getCBDequeueAudio(uint32_t & nSamples, std::vector<T> & buffer) {
|
||||||
|
return [&nSamples, &buffer](void * data, uint32_t nMaxBytes) {
|
||||||
|
uint32_t nCopied = std::min((uint32_t) (nSamples*sizeof(T)), nMaxBytes);
|
||||||
|
const char * p = (char *) (buffer.data() + buffer.size() - nSamples);
|
||||||
|
std::copy(p, p + nCopied, (char *) data);
|
||||||
|
nSamples -= nCopied/sizeof(T);
|
||||||
|
|
||||||
|
return nCopied;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename D>
|
||||||
|
void convert(const std::vector<S> & src, std::vector<D> & dst) {
|
||||||
|
int n = src.size();
|
||||||
|
dst.resize(n);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
dst[i] = ((float(src[i]) - kSampleOffset.at(typeid(S)))/kSampleScale.at(typeid(S)))*kSampleScale.at(typeid(D)) + kSampleOffset.at(typeid(D));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
GGWave instance(48000, 48000, 1024, 4, 2);
|
std::vector<uint8_t> bufferU8;
|
||||||
|
std::vector<int8_t> bufferI8;
|
||||||
|
std::vector<uint16_t> bufferU16;
|
||||||
|
std::vector<int16_t> bufferI16;
|
||||||
|
std::vector<float> bufferF32;
|
||||||
|
|
||||||
std::string payload = "hello";
|
auto convertHelper = [&](GGWave::SampleFormat formatOut, GGWave::SampleFormat formatIn) {
|
||||||
|
switch (formatOut) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
||||||
|
{
|
||||||
|
switch (formatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferU8, bufferI8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferU8, bufferU16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferU8, bufferI16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferU8, bufferF32); break;
|
||||||
|
};
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
||||||
|
{
|
||||||
|
switch (formatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferI8, bufferU8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferI8, bufferU16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferI8, bufferI16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferI8, bufferF32); break;
|
||||||
|
};
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
||||||
|
{
|
||||||
|
switch (formatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferU16, bufferU8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferU16, bufferI8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferU16, bufferI16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferU16, bufferF32); break;
|
||||||
|
};
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
||||||
|
{
|
||||||
|
switch (formatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferI16, bufferU8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferI16, bufferI8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferI16, bufferU16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferI16, bufferF32); break;
|
||||||
|
};
|
||||||
|
} break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
||||||
|
{
|
||||||
|
switch (formatIn) {
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferF32, bufferU8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferF32, bufferI8); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferF32, bufferU16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferF32, bufferI16); break;
|
||||||
|
case GGWAVE_SAMPLE_FORMAT_F32: break;
|
||||||
|
};
|
||||||
|
} break;
|
||||||
|
};
|
||||||
|
|
||||||
CHECK(instance.init(payload.size(), payload.c_str()));
|
};
|
||||||
|
|
||||||
// data
|
uint32_t nSamples = 0;
|
||||||
CHECK_F(instance.init(-1, "asd"));
|
|
||||||
CHECK_T(instance.init(0, nullptr));
|
|
||||||
CHECK_T(instance.init(0, "asd"));
|
|
||||||
CHECK_T(instance.init(1, "asd"));
|
|
||||||
CHECK_T(instance.init(2, "asd"));
|
|
||||||
CHECK_T(instance.init(3, "asd"));
|
|
||||||
|
|
||||||
// volume
|
const std::map<GGWave::SampleFormat, GGWave::CBEnqueueAudio> kCBEnqueueAudio = {
|
||||||
CHECK_F(instance.init(payload.size(), payload.c_str(), -1));
|
{ GGWAVE_SAMPLE_FORMAT_U8, getCBEnqueueAudio(nSamples, bufferU8) },
|
||||||
CHECK_T(instance.init(payload.size(), payload.c_str(), 0));
|
{ GGWAVE_SAMPLE_FORMAT_I8, getCBEnqueueAudio(nSamples, bufferI8) },
|
||||||
CHECK_T(instance.init(payload.size(), payload.c_str(), 50));
|
{ GGWAVE_SAMPLE_FORMAT_U16, getCBEnqueueAudio(nSamples, bufferU16) },
|
||||||
CHECK_T(instance.init(payload.size(), payload.c_str(), 100));
|
{ GGWAVE_SAMPLE_FORMAT_I16, getCBEnqueueAudio(nSamples, bufferI16) },
|
||||||
CHECK_F(instance.init(payload.size(), payload.c_str(), 101));
|
{ GGWAVE_SAMPLE_FORMAT_F32, getCBEnqueueAudio(nSamples, bufferF32) },
|
||||||
|
};
|
||||||
|
|
||||||
// todo ..
|
const std::map<GGWave::SampleFormat, GGWave::CBDequeueAudio> kCBDequeueAudio = {
|
||||||
|
{ GGWAVE_SAMPLE_FORMAT_U8, getCBDequeueAudio(nSamples, bufferU8) },
|
||||||
|
{ GGWAVE_SAMPLE_FORMAT_I8, getCBDequeueAudio(nSamples, bufferI8) },
|
||||||
|
{ GGWAVE_SAMPLE_FORMAT_U16, getCBDequeueAudio(nSamples, bufferU16) },
|
||||||
|
{ GGWAVE_SAMPLE_FORMAT_I16, getCBDequeueAudio(nSamples, bufferI16) },
|
||||||
|
{ GGWAVE_SAMPLE_FORMAT_F32, getCBDequeueAudio(nSamples, bufferF32) },
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
GGWave instance(GGWave::getDefaultParameters());
|
||||||
|
|
||||||
|
std::string payload = "hello";
|
||||||
|
|
||||||
|
CHECK(instance.init(payload));
|
||||||
|
|
||||||
|
// data
|
||||||
|
CHECK_F(instance.init(-1, "asd"));
|
||||||
|
CHECK_T(instance.init(0, nullptr));
|
||||||
|
CHECK_T(instance.init(0, "asd"));
|
||||||
|
CHECK_T(instance.init(1, "asd"));
|
||||||
|
CHECK_T(instance.init(2, "asd"));
|
||||||
|
CHECK_T(instance.init(3, "asd"));
|
||||||
|
|
||||||
|
// volume
|
||||||
|
CHECK_F(instance.init(payload.size(), payload.c_str(), -1));
|
||||||
|
CHECK_T(instance.init(payload.size(), payload.c_str(), 0));
|
||||||
|
CHECK_T(instance.init(payload.size(), payload.c_str(), 50));
|
||||||
|
CHECK_T(instance.init(payload.size(), payload.c_str(), 100));
|
||||||
|
CHECK_F(instance.init(payload.size(), payload.c_str(), 101));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto & txProtocol : GGWave::getTxProtocols()) {
|
||||||
|
for (const auto & formatOut : kFormats) {
|
||||||
|
for (const auto & formatIn : kFormats) {
|
||||||
|
printf("Testing: protocol = %s, in = %d, out = %d\n", txProtocol.second.name, formatIn, formatOut);
|
||||||
|
|
||||||
|
auto parameters = GGWave::getDefaultParameters();
|
||||||
|
parameters.sampleFormatIn = formatIn;
|
||||||
|
parameters.sampleFormatOut = formatOut;
|
||||||
|
GGWave instance(parameters);
|
||||||
|
|
||||||
|
std::string payload = "test message xxxxxxxxxxxx";
|
||||||
|
|
||||||
|
instance.init(payload, txProtocol.second, 25);
|
||||||
|
instance.encode(kCBEnqueueAudio.at(formatOut));
|
||||||
|
convertHelper(formatOut, formatIn);
|
||||||
|
instance.decode(kCBDequeueAudio.at(formatIn));
|
||||||
|
|
||||||
|
{
|
||||||
|
GGWave::TxRxData result;
|
||||||
|
CHECK(instance.takeRxData(result) == (int) payload.size());
|
||||||
|
for (int i = 0; i < (int) payload.size(); ++i) {
|
||||||
|
CHECK(payload[i] == result[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user