mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-07 01:11:22 +08:00
wip
This commit is contained in:
@@ -89,6 +89,8 @@ if (EMSCRIPTEN)
|
||||
add_subdirectory(buttons)
|
||||
else()
|
||||
add_subdirectory(ggwave-to-file)
|
||||
|
||||
add_subdirectory(arduino-rx)
|
||||
endif()
|
||||
|
||||
if (GGWAVE_SUPPORT_SDL2)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#
|
||||
# arduino-rx
|
||||
# arduino-rx-web
|
||||
|
||||
set(TARGET arduino-rx)
|
||||
set(TARGET arduino-rx-web)
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
add_executable(${TARGET}
|
||||
arduino-rx.cpp
|
||||
arduino-rx-web.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET} PRIVATE
|
||||
|
||||
10
examples/arduino-rx/CMakeLists.txt
Normal file
10
examples/arduino-rx/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# arduino-rx
|
||||
|
||||
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/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/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)
|
||||
@@ -6,7 +6,7 @@
|
||||
static const char channels = 1;
|
||||
|
||||
// default PCM output frequency
|
||||
static const int frequency = GGWave::kBaseSampleRate;
|
||||
static const int frequency = 6000;
|
||||
|
||||
const int qmax = 1024;
|
||||
volatile int qhead = 0;
|
||||
@@ -51,9 +51,14 @@ void loop() {
|
||||
//delay(1000);
|
||||
auto p = GGWave::getDefaultParameters();
|
||||
p.sampleRateInp = frequency;
|
||||
p.sampleRate = frequency;
|
||||
p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;
|
||||
p.samplesPerFrame = 128;
|
||||
p.payloadLength = 16;
|
||||
p.operatingMode = GGWAVE_OPERATING_MODE_ONLY_RX;
|
||||
GGWave instance(p);
|
||||
instance.setRxProtocols({{GGWAVE_TX_PROTOCOL_DT_FASTEST, instance.getTxProtocol(GGWAVE_TX_PROTOCOL_DT_FASTEST)}});
|
||||
Serial.println("Instance initialized");
|
||||
|
||||
static GGWave::CBWaveformInp cbWaveformInp = [&](void * data, uint32_t nMaxBytes) {
|
||||
if (2*qsize < nMaxBytes) {
|
||||
|
||||
@@ -46,10 +46,12 @@ ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
|
||||
parameters.payloadLength,
|
||||
parameters.sampleRateInp,
|
||||
parameters.sampleRateOut,
|
||||
parameters.sampleRate,
|
||||
parameters.samplesPerFrame,
|
||||
parameters.soundMarkerThreshold,
|
||||
parameters.sampleFormatInp,
|
||||
parameters.sampleFormatOut});
|
||||
parameters.sampleFormatOut,
|
||||
parameters.operatingMode});
|
||||
|
||||
return curId++;
|
||||
}
|
||||
@@ -351,73 +353,84 @@ void GGWave::setLogFile(FILE * fptr) {
|
||||
const GGWave::Parameters & GGWave::getDefaultParameters() {
|
||||
static ggwave_Parameters result {
|
||||
-1, // vaiable payload length
|
||||
kBaseSampleRate,
|
||||
kBaseSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSamplesPerFrame,
|
||||
kDefaultSoundMarkerThreshold,
|
||||
GGWAVE_SAMPLE_FORMAT_F32,
|
||||
GGWAVE_SAMPLE_FORMAT_F32,
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GGWave::GGWave(const Parameters & parameters) :
|
||||
m_sampleRateInp(parameters.sampleRateInp),
|
||||
m_sampleRateOut(parameters.sampleRateOut),
|
||||
m_samplesPerFrame(parameters.samplesPerFrame),
|
||||
m_isamplesPerFrame(1.0f/m_samplesPerFrame),
|
||||
m_sampleSizeBytesInp(bytesForSampleFormat(parameters.sampleFormatInp)),
|
||||
m_sampleSizeBytesOut(bytesForSampleFormat(parameters.sampleFormatOut)),
|
||||
m_sampleFormatInp(parameters.sampleFormatInp),
|
||||
m_sampleFormatOut(parameters.sampleFormatOut),
|
||||
m_hzPerSample(kBaseSampleRate/parameters.samplesPerFrame),
|
||||
m_ihzPerSample(1.0f/m_hzPerSample),
|
||||
m_freqDelta_bin(1),
|
||||
m_freqDelta_hz(2*m_hzPerSample),
|
||||
m_nBitsInMarker(16),
|
||||
m_nMarkerFrames(parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames),
|
||||
m_encodedDataOffset(parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset),
|
||||
m_sampleRateInp (parameters.sampleRateInp),
|
||||
m_sampleRateOut (parameters.sampleRateOut),
|
||||
m_sampleRate (parameters.sampleRate),
|
||||
m_samplesPerFrame (parameters.samplesPerFrame),
|
||||
m_isamplesPerFrame (1.0f/m_samplesPerFrame),
|
||||
m_sampleSizeBytesInp (bytesForSampleFormat(parameters.sampleFormatInp)),
|
||||
m_sampleSizeBytesOut (bytesForSampleFormat(parameters.sampleFormatOut)),
|
||||
m_sampleFormatInp (parameters.sampleFormatInp),
|
||||
m_sampleFormatOut (parameters.sampleFormatOut),
|
||||
m_hzPerSample (m_sampleRate/m_samplesPerFrame),
|
||||
m_ihzPerSample (1.0f/m_hzPerSample),
|
||||
m_freqDelta_bin (1),
|
||||
m_freqDelta_hz (2*m_hzPerSample),
|
||||
m_nBitsInMarker (16),
|
||||
m_nMarkerFrames (parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames),
|
||||
m_encodedDataOffset (parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset),
|
||||
m_soundMarkerThreshold(parameters.soundMarkerThreshold),
|
||||
|
||||
// common
|
||||
m_isFixedPayloadLength(parameters.payloadLength > 0),
|
||||
m_payloadLength(parameters.payloadLength),
|
||||
m_payloadLength (parameters.payloadLength),
|
||||
m_dataEncoded (kMaxDataSize),
|
||||
|
||||
// Rx
|
||||
m_samplesNeeded(m_samplesPerFrame),
|
||||
m_fftInp(kMaxSamplesPerFrame),
|
||||
m_fftOut(2*kMaxSamplesPerFrame),
|
||||
m_hasNewSpectrum(false),
|
||||
m_hasNewAmplitude(false),
|
||||
m_sampleSpectrum(kMaxSamplesPerFrame),
|
||||
m_sampleAmplitude(kMaxSamplesPerFrame + 128), // small extra space because sometimes resampling needs a few more samples
|
||||
m_sampleAmplitudeResampled(8*kMaxSamplesPerFrame), // min input sampling rate is 0.125*kBaseSampleRate
|
||||
m_sampleAmplitudeTmp(8*kMaxSamplesPerFrame*m_sampleSizeBytesInp),
|
||||
m_hasNewRxData(false),
|
||||
m_lastRxDataLength(0),
|
||||
m_rxData(kMaxDataSize),
|
||||
m_rxProtocol(getDefaultTxProtocol()),
|
||||
m_rxProtocolId(getDefaultTxProtocolId()),
|
||||
m_rxProtocols(getTxProtocols()),
|
||||
m_historyId(0),
|
||||
//m_sampleAmplitudeAverage(kMaxSamplesPerFrame),
|
||||
//m_sampleAmplitudeHistory(kMaxSpectrumHistory),
|
||||
m_historyIdFixed(0),
|
||||
m_isRxEnabled(parameters.operatingMode == GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX ||
|
||||
parameters.operatingMode == GGWAVE_OPERATING_MODE_ONLY_RX),
|
||||
|
||||
m_samplesNeeded (m_samplesPerFrame),
|
||||
m_fftInp (m_samplesPerFrame),
|
||||
m_fftOut (2*m_samplesPerFrame),
|
||||
m_hasNewSpectrum (false),
|
||||
m_hasNewAmplitude (false),
|
||||
m_sampleSpectrum (m_samplesPerFrame),
|
||||
m_sampleAmplitude (m_sampleRateInp == m_sampleRate ? m_samplesPerFrame : m_samplesPerFrame + 128), // small extra space because sometimes resampling needs a few more samples
|
||||
m_sampleAmplitudeResampled(m_sampleRateInp == m_sampleRate ? m_samplesPerFrame : 8*m_samplesPerFrame), // min input sampling rate is 0.125*m_sampleRate
|
||||
m_sampleAmplitudeTmp (m_sampleRateInp == m_sampleRate ? m_samplesPerFrame*m_sampleSizeBytesInp : 8*m_samplesPerFrame*m_sampleSizeBytesInp),
|
||||
m_hasNewRxData (false),
|
||||
m_lastRxDataLength (0),
|
||||
m_rxData (kMaxDataSize),
|
||||
m_rxProtocol (getDefaultTxProtocol()),
|
||||
m_rxProtocolId (getDefaultTxProtocolId()),
|
||||
m_rxProtocols (getTxProtocols()),
|
||||
m_historyId (0),
|
||||
m_sampleAmplitudeAverage (parameters.payloadLength > 0 ? 0 : m_samplesPerFrame),
|
||||
m_sampleAmplitudeHistory (parameters.payloadLength > 0 ? 0 : kMaxSpectrumHistory),
|
||||
m_historyIdFixed (0),
|
||||
|
||||
// Tx
|
||||
m_hasNewTxData(false),
|
||||
m_sendVolume(0.1),
|
||||
m_txDataLength(0),
|
||||
m_txData(kMaxDataSize),
|
||||
m_txDataEncoded(kMaxDataSize),
|
||||
//m_outputBlock(kMaxSamplesPerFrame),
|
||||
//m_outputBlockResampled(2*kMaxSamplesPerFrame),
|
||||
//m_outputBlockTmp(kMaxRecordedFrames*kMaxSamplesPerFrame*m_sampleSizeBytesOut),
|
||||
//m_outputBlockI16(kMaxRecordedFrames*kMaxSamplesPerFrame),
|
||||
m_isTxEnabled(parameters.operatingMode == GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX),
|
||||
|
||||
m_hasNewTxData (false),
|
||||
m_sendVolume (0.1),
|
||||
m_txDataLength (0),
|
||||
m_txData (m_isTxEnabled ? kMaxDataSize : 0),
|
||||
m_outputBlock (m_isTxEnabled ? m_samplesPerFrame : 0),
|
||||
m_outputBlockResampled(m_isTxEnabled ? 2*m_samplesPerFrame : 0),
|
||||
m_outputBlockTmp (m_isTxEnabled ? kMaxRecordedFrames*m_samplesPerFrame*m_sampleSizeBytesOut : 0),
|
||||
m_outputBlockI16 (m_isTxEnabled ? kMaxRecordedFrames*m_samplesPerFrame : 0),
|
||||
m_impl(new Impl()) {
|
||||
|
||||
if (m_payloadLength > 0) {
|
||||
// fixed payload length
|
||||
if (m_payloadLength > kMaxLengthFixed) {
|
||||
//throw std::runtime_error("Invalid payload legnth");
|
||||
ggprintf("Invalid payload legnth: %d, max: %d\n", m_payloadLength, kMaxLengthFixed);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,33 +442,31 @@ GGWave::GGWave(const Parameters & parameters) :
|
||||
m_spectrumHistoryFixed.resize(totalTxs*maxFramesPerTx());
|
||||
} else {
|
||||
// variable payload length
|
||||
m_recordedAmplitude.resize(kMaxRecordedFrames*kMaxSamplesPerFrame);
|
||||
m_recordedAmplitude.resize(kMaxRecordedFrames*m_samplesPerFrame);
|
||||
}
|
||||
|
||||
if (m_sampleSizeBytesInp == 0) {
|
||||
//throw std::runtime_error("Invalid or unsupported capture sample format");
|
||||
ggprintf("Invalid or unsupported capture sample format: %d\n", (int) parameters.sampleFormatInp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleSizeBytesOut == 0) {
|
||||
//throw std::runtime_error("Invalid or unsupported playback sample format");
|
||||
ggprintf("Invalid or unsupported playback sample format: %d\n", (int) parameters.sampleFormatOut);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {
|
||||
//throw std::runtime_error("Invalid samples per frame");
|
||||
ggprintf("Invalid samples per frame: %d, max: %d\n", parameters.samplesPerFrame, kMaxSamplesPerFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleRateInp < kSampleRateMin) {
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin);
|
||||
//throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleRateInp > kSampleRateMax) {
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax);
|
||||
//throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -494,53 +505,58 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txPr
|
||||
return false;
|
||||
}
|
||||
|
||||
m_txProtocol = txProtocol;
|
||||
m_txDataLength = dataSize;
|
||||
m_sendVolume = ((double)(volume))/100.0f;
|
||||
// Tx
|
||||
if (m_isTxEnabled) {
|
||||
m_txProtocol = txProtocol;
|
||||
m_txDataLength = dataSize;
|
||||
m_sendVolume = ((double)(volume))/100.0f;
|
||||
|
||||
const uint8_t * text = reinterpret_cast<const uint8_t *>(dataBuffer);
|
||||
const uint8_t * text = reinterpret_cast<const uint8_t *>(dataBuffer);
|
||||
|
||||
m_hasNewTxData = false;
|
||||
//std::fill(m_txData.begin(), m_txData.end(), 0);
|
||||
//std::fill(m_txDataEncoded.begin(), m_txDataEncoded.end(), 0);
|
||||
m_hasNewTxData = false;
|
||||
std::fill(m_txData.begin(), m_txData.end(), 0);
|
||||
std::fill(m_dataEncoded.begin(), m_dataEncoded.end(), 0);
|
||||
|
||||
//if (m_txDataLength > 0) {
|
||||
// m_txData[0] = m_txDataLength;
|
||||
// for (int i = 0; i < m_txDataLength; ++i) m_txData[i + 1] = text[i];
|
||||
if (m_txDataLength > 0) {
|
||||
m_txData[0] = m_txDataLength;
|
||||
for (int i = 0; i < m_txDataLength; ++i) m_txData[i + 1] = text[i];
|
||||
|
||||
// m_hasNewTxData = true;
|
||||
//}
|
||||
m_hasNewTxData = true;
|
||||
}
|
||||
|
||||
//if (m_isFixedPayloadLength) {
|
||||
// m_txDataLength = m_payloadLength;
|
||||
//}
|
||||
|
||||
// Rx
|
||||
m_receivingData = false;
|
||||
m_analyzingData = false;
|
||||
|
||||
m_framesToAnalyze = 0;
|
||||
m_framesLeftToAnalyze = 0;
|
||||
m_framesToRecord = 0;
|
||||
m_framesLeftToRecord = 0;
|
||||
|
||||
std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0);
|
||||
std::fill(m_sampleAmplitude.begin(), m_sampleAmplitude.end(), 0);
|
||||
//for (auto & s : m_sampleAmplitudeHistory) {
|
||||
// s.resize(kMaxSamplesPerFrame);
|
||||
// std::fill(s.begin(), s.end(), 0);
|
||||
//}
|
||||
|
||||
std::fill(m_rxData.begin(), m_rxData.end(), 0);
|
||||
|
||||
for (int i = 0; i < m_samplesPerFrame; ++i) {
|
||||
m_fftOut[2*i + 0] = 0.0f;
|
||||
m_fftOut[2*i + 1] = 0.0f;
|
||||
if (m_isFixedPayloadLength) {
|
||||
m_txDataLength = m_payloadLength;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & s : m_spectrumHistoryFixed) {
|
||||
s.resize(kMaxSamplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
// Rx
|
||||
if (m_isRxEnabled) {
|
||||
m_receivingData = false;
|
||||
m_analyzingData = false;
|
||||
|
||||
m_framesToAnalyze = 0;
|
||||
m_framesLeftToAnalyze = 0;
|
||||
m_framesToRecord = 0;
|
||||
m_framesLeftToRecord = 0;
|
||||
|
||||
std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0);
|
||||
std::fill(m_sampleAmplitude.begin(), m_sampleAmplitude.end(), 0);
|
||||
for (auto & s : m_sampleAmplitudeHistory) {
|
||||
s.resize(m_samplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
}
|
||||
|
||||
std::fill(m_rxData.begin(), m_rxData.end(), 0);
|
||||
|
||||
for (int i = 0; i < m_samplesPerFrame; ++i) {
|
||||
m_fftOut[2*i + 0] = 0.0f;
|
||||
m_fftOut[2*i + 1] = 0.0f;
|
||||
}
|
||||
|
||||
for (auto & s : m_spectrumHistoryFixed) {
|
||||
s.resize(m_samplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -557,8 +573,8 @@ uint32_t GGWave::encodeSize_samples() const {
|
||||
|
||||
float factor = 1.0f;
|
||||
int samplesPerFrameOut = m_samplesPerFrame;
|
||||
if (m_sampleRateOut != kBaseSampleRate) {
|
||||
factor = kBaseSampleRate/m_sampleRateOut;
|
||||
if (m_sampleRateOut != m_sampleRate) {
|
||||
factor = m_sampleRate/m_sampleRateOut;
|
||||
// note : +1 extra sample in order to overestimate the buffer size
|
||||
samplesPerFrameOut = m_impl->resampler.resample(factor, m_samplesPerFrame, m_outputBlock.data(), nullptr) + 1;
|
||||
}
|
||||
@@ -597,8 +613,8 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
for (int k = 0; k < (int) dataBits.size(); ++k) {
|
||||
double freq = bitFreq(m_txProtocol, k);
|
||||
|
||||
bit1Amplitude[k].resize(kMaxSamplesPerFrame);
|
||||
bit0Amplitude[k].resize(kMaxSamplesPerFrame);
|
||||
bit1Amplitude[k].resize(m_samplesPerFrame);
|
||||
bit0Amplitude[k].resize(m_samplesPerFrame);
|
||||
|
||||
double phaseOffset = phaseOffsets[k];
|
||||
double curHzPerSample = m_hzPerSample;
|
||||
@@ -620,16 +636,16 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
if (m_isFixedPayloadLength == false) {
|
||||
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1);
|
||||
rsLength.Encode(m_txData.data(), m_txDataEncoded.data());
|
||||
rsLength.Encode(m_txData.data(), m_dataEncoded.data());
|
||||
}
|
||||
|
||||
// first byte of m_txData contains the length of the payload, so we skip it:
|
||||
RS::ReedSolomon rsData = RS::ReedSolomon(m_txDataLength, nECCBytesPerTx);
|
||||
rsData.Encode(m_txData.data() + 1, m_txDataEncoded.data() + m_encodedDataOffset);
|
||||
rsData.Encode(m_txData.data() + 1, m_dataEncoded.data() + m_encodedDataOffset);
|
||||
|
||||
const float factor = m_sampleRate/m_sampleRateOut;
|
||||
|
||||
float factor = kBaseSampleRate/m_sampleRateOut;
|
||||
uint32_t offset = 0;
|
||||
|
||||
m_waveformTones.clear();
|
||||
|
||||
while (m_hasNewTxData) {
|
||||
@@ -643,7 +659,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
for (int i = 0; i < m_nBitsInMarker; ++i) {
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (i%2 == 0) {
|
||||
::addAmplitudeSmooth(bit1Amplitude[i], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, i);
|
||||
@@ -662,11 +678,11 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
for (int j = 0; j < m_txProtocol.bytesPerTx; ++j) {
|
||||
{
|
||||
uint8_t d = m_txDataEncoded[dataOffset + j] & 15;
|
||||
uint8_t d = m_dataEncoded[dataOffset + j] & 15;
|
||||
dataBits[(2*j + 0)*16 + d] = 1;
|
||||
}
|
||||
{
|
||||
uint8_t d = m_txDataEncoded[dataOffset + j] & 240;
|
||||
uint8_t d = m_dataEncoded[dataOffset + j] & 240;
|
||||
dataBits[(2*j + 1)*16 + (d >> 4)] = 1;
|
||||
}
|
||||
}
|
||||
@@ -676,7 +692,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
++nFreq;
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (k%2) {
|
||||
::addAmplitudeSmooth(bit0Amplitude[k/2], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, cycleModMain, m_txProtocol.framesPerTx);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, k/2) + m_hzPerSample;
|
||||
@@ -691,7 +707,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
int fId = frameId - (m_nMarkerFrames + totalDataFrames);
|
||||
for (int i = 0; i < m_nBitsInMarker; ++i) {
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (i%2 == 0) {
|
||||
addAmplitudeSmooth(bit0Amplitude[i], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, i) + m_hzPerSample;
|
||||
@@ -712,7 +728,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
}
|
||||
|
||||
int samplesPerFrameOut = m_samplesPerFrame;
|
||||
if (m_sampleRateOut != kBaseSampleRate) {
|
||||
if (m_sampleRateOut != m_sampleRate) {
|
||||
samplesPerFrameOut = m_impl->resampler.resample(factor, m_samplesPerFrame, m_outputBlock.data(), m_outputBlockResampled.data());
|
||||
} else {
|
||||
m_outputBlockResampled = m_outputBlock;
|
||||
@@ -794,10 +810,10 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
void GGWave::decode(const CBWaveformInp & cbWaveformInp) {
|
||||
while (m_hasNewTxData == false) {
|
||||
// read capture data
|
||||
float factor = m_sampleRateInp/kBaseSampleRate;
|
||||
float factor = m_sampleRateInp/m_sampleRate;
|
||||
uint32_t nBytesNeeded = m_samplesNeeded*m_sampleSizeBytesInp;
|
||||
|
||||
if (m_sampleRateInp != kBaseSampleRate) {
|
||||
if (m_sampleRateInp != m_sampleRate) {
|
||||
// note : predict 4 extra samples just to make sure we have enough data
|
||||
nBytesNeeded = (m_impl->resampler.resample(1.0f/factor, m_samplesNeeded, m_sampleAmplitudeResampled.data(), nullptr) + 4)*m_sampleSizeBytesInp;
|
||||
}
|
||||
@@ -878,14 +894,14 @@ void GGWave::decode(const CBWaveformInp & cbWaveformInp) {
|
||||
|
||||
uint32_t offset = m_samplesPerFrame - m_samplesNeeded;
|
||||
|
||||
if (m_sampleRateInp != kBaseSampleRate) {
|
||||
if (m_sampleRateInp != m_sampleRate) {
|
||||
if (nSamplesRecorded <= 2*Resampler::kWidth) {
|
||||
m_samplesNeeded = m_samplesPerFrame;
|
||||
break;
|
||||
}
|
||||
|
||||
// reset resampler state every minute
|
||||
if (!m_receivingData && m_impl->resampler.nSamplesTotal() > 60.0f*factor*kBaseSampleRate) {
|
||||
if (!m_receivingData && m_impl->resampler.nSamplesTotal() > 60.0f*factor*m_sampleRate) {
|
||||
m_impl->resampler.reset();
|
||||
}
|
||||
|
||||
@@ -1057,7 +1073,7 @@ void GGWave::decode_variable() {
|
||||
const int offsetStart = ii;
|
||||
for (int itx = 0; itx < 1024; ++itx) {
|
||||
int offsetTx = offsetStart + itx*rxProtocol.framesPerTx*stepsPerFrame;
|
||||
if (offsetTx >= m_recvDuration_frames*stepsPerFrame || (itx + 1)*rxProtocol.bytesPerTx >= (int) m_txDataEncoded.size()) {
|
||||
if (offsetTx >= m_recvDuration_frames*stepsPerFrame || (itx + 1)*rxProtocol.bytesPerTx >= (int) m_dataEncoded.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1097,7 +1113,7 @@ void GGWave::decode_variable() {
|
||||
|
||||
if (i%2) {
|
||||
curByte += (kmax << 4);
|
||||
m_txDataEncoded[itx*rxProtocol.bytesPerTx + i/2] = curByte;
|
||||
m_dataEncoded[itx*rxProtocol.bytesPerTx + i/2] = curByte;
|
||||
curByte = 0;
|
||||
} else {
|
||||
curByte = kmax;
|
||||
@@ -1106,7 +1122,7 @@ void GGWave::decode_variable() {
|
||||
|
||||
if (itx*rxProtocol.bytesPerTx > m_encodedDataOffset && knownLength == false) {
|
||||
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1);
|
||||
if ((rsLength.Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) && (m_rxData[0] > 0 && m_rxData[0] <= 140)) {
|
||||
if ((rsLength.Decode(m_dataEncoded.data(), m_rxData.data()) == 0) && (m_rxData[0] > 0 && m_rxData[0] <= 140)) {
|
||||
knownLength = true;
|
||||
decodedLength = m_rxData[0];
|
||||
|
||||
@@ -1133,7 +1149,7 @@ void GGWave::decode_variable() {
|
||||
if (knownLength) {
|
||||
RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength));
|
||||
|
||||
if (rsData.Decode(m_txDataEncoded.data() + m_encodedDataOffset, m_rxData.data()) == 0) {
|
||||
if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rxData.data()) == 0) {
|
||||
if (m_rxData[0] != 0) {
|
||||
std::string s((char *) m_rxData.data(), decodedLength);
|
||||
|
||||
@@ -1300,6 +1316,10 @@ void GGWave::decode_fixed() {
|
||||
const int binStart = rxProtocol.freqStart;
|
||||
const int binDelta = 16;
|
||||
|
||||
if (binStart > m_samplesPerFrame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int totalLength = m_payloadLength + getECCBytesForLength(m_payloadLength);
|
||||
const int totalTxs = (totalLength + rxProtocol.bytesPerTx - 1)/rxProtocol.bytesPerTx;
|
||||
|
||||
@@ -1384,10 +1404,6 @@ void GGWave::decode_fixed() {
|
||||
txNeededTotal += txNeeded;
|
||||
}
|
||||
|
||||
//if (rxProtocolId == GGWAVE_TX_PROTOCOL_DT_FAST) {
|
||||
// printf("detected = %d, needed = %d\n", txDetectedTotal, txNeededTotal);
|
||||
//}
|
||||
|
||||
if (txDetectedTotal < 0.75*txNeededTotal) {
|
||||
detectedSignal = false;
|
||||
}
|
||||
@@ -1396,10 +1412,10 @@ void GGWave::decode_fixed() {
|
||||
RS::ReedSolomon rsData(m_payloadLength, getECCBytesForLength(m_payloadLength));
|
||||
|
||||
for (int j = 0; j < totalLength; ++j) {
|
||||
m_txDataEncoded[j] = (detectedBins[2*j + 1] << 4) + detectedBins[2*j + 0];
|
||||
m_dataEncoded[j] = (detectedBins[2*j + 1] << 4) + detectedBins[2*j + 0];
|
||||
}
|
||||
|
||||
if (rsData.Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) {
|
||||
if (rsData.Decode(m_dataEncoded.data(), m_rxData.data()) == 0) {
|
||||
if (m_rxData[0] != 0) {
|
||||
ggprintf("Received sound data successfully: '%s'\n", m_rxData.data());
|
||||
|
||||
|
||||
@@ -57,6 +57,12 @@ extern "C" {
|
||||
GGWAVE_TX_PROTOCOL_CUSTOM_9,
|
||||
} ggwave_TxProtocolId;
|
||||
|
||||
typedef enum {
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
GGWAVE_OPERATING_MODE_ONLY_RX,
|
||||
//GGWAVE_OPERATING_MODE_ONLY_TX, // Not supported yet
|
||||
} ggwave_OperatingMode;
|
||||
|
||||
// GGWave instance parameters
|
||||
//
|
||||
// If payloadLength <= 0, then GGWave will transmit with variable payload length
|
||||
@@ -68,21 +74,31 @@ extern "C" {
|
||||
// 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
|
||||
// The sample rates are values typically between 1000 and 96000.
|
||||
// Default value: GGWave::kDefaultSampleRate
|
||||
//
|
||||
// The captured audio is resampled to the specified sampleRate if sampleRatInp
|
||||
// is different from sampleRate. Same applies to the transmitted audio.
|
||||
//
|
||||
// The samplesPerFrame is the number of samples on which ggwave performs FFT.
|
||||
// This affects the number of bins in the Fourier spectrum.
|
||||
// Default value: GGWave::kDefaultSamplesPerFrame
|
||||
//
|
||||
// The operatingMode controls which functions of the ggwave instance are enabled.
|
||||
// Use this parameter to reduce the memory footprint of the ggwave instance. For
|
||||
// example, if only Rx is enabled, then the memory buffers needed for the Tx will
|
||||
// not be allocated.
|
||||
//
|
||||
typedef struct {
|
||||
int payloadLength; // payload length
|
||||
float sampleRateInp; // capture sample rate
|
||||
float sampleRateOut; // playback sample rate
|
||||
float sampleRate; // the operating sample rate
|
||||
int samplesPerFrame; // number of samples per audio frame
|
||||
float soundMarkerThreshold; // sound marker detection threshold
|
||||
ggwave_SampleFormat sampleFormatInp; // format of the captured audio samples
|
||||
ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples
|
||||
ggwave_OperatingMode operatingMode; // operating mode
|
||||
} ggwave_Parameters;
|
||||
|
||||
// GGWave instances are identified with an integer and are stored
|
||||
@@ -286,15 +302,15 @@ extern "C" {
|
||||
|
||||
class GGWave {
|
||||
public:
|
||||
static constexpr auto kBaseSampleRate = 6000.0f;
|
||||
static constexpr auto kSampleRateMin = 1000.0f;
|
||||
static constexpr auto kSampleRateMax = 10000.0f;
|
||||
static constexpr auto kDefaultSamplesPerFrame = 128;
|
||||
static constexpr auto kSampleRateMax = 96000.0f;
|
||||
static constexpr auto kDefaultSampleRate = 48000.0f;
|
||||
static constexpr auto kDefaultSamplesPerFrame = 1024;
|
||||
static constexpr auto kDefaultVolume = 10;
|
||||
static constexpr auto kDefaultSoundMarkerThreshold = 3.0f;
|
||||
static constexpr auto kDefaultMarkerFrames = 16;
|
||||
static constexpr auto kDefaultEncodedDataOffset = 3;
|
||||
static constexpr auto kMaxSamplesPerFrame = 128;
|
||||
static constexpr auto kMaxSamplesPerFrame = 1024;
|
||||
static constexpr auto kMaxDataBits = 256;
|
||||
static constexpr auto kMaxDataSize = 256;
|
||||
static constexpr auto kMaxLengthVarible = 140;
|
||||
@@ -324,14 +340,14 @@ 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_DT_NORMAL, { "[DT] Normal", 24, 9, 1, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_DT_FAST, { "[DT] Fast", 24, 6, 1, } },
|
||||
{ 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, } },
|
||||
};
|
||||
|
||||
@@ -383,14 +399,14 @@ public:
|
||||
|
||||
// expected waveform size of the encoded Tx data in bytes
|
||||
//
|
||||
// When the output sampling rate is not equal to kBaseSampleRate the result of this method is overestimation of
|
||||
// When the output sampling rate is not equal to operating sample rate the result of this method is overestimation of
|
||||
// the actual number of bytes that would be produced
|
||||
//
|
||||
uint32_t encodeSize_bytes() const;
|
||||
|
||||
// expected waveform size of the encoded Tx data in samples
|
||||
//
|
||||
// When the output sampling rate is not equal to kBaseSampleRate the result of this method is overestimation of
|
||||
// When the output sampling rate is not equal to operating sample rate the result of this method is overestimation of
|
||||
// the actual number of samples that would be produced
|
||||
//
|
||||
uint32_t encodeSize_samples() const;
|
||||
@@ -430,7 +446,7 @@ public:
|
||||
|
||||
// Tx
|
||||
|
||||
static TxProtocolId getDefaultTxProtocolId() { return getTxProtocols().begin()->first; }
|
||||
static TxProtocolId getDefaultTxProtocolId() { return GGWAVE_TX_PROTOCOL_AUDIBLE_FAST; }
|
||||
static const TxProtocol & getDefaultTxProtocol() { return getTxProtocols().at(getDefaultTxProtocolId()); }
|
||||
static const TxProtocol & getTxProtocol(int id) { return getTxProtocols().at(TxProtocolId(id)); }
|
||||
static const TxProtocol & getTxProtocol(TxProtocolId id) { return getTxProtocols().at(id); }
|
||||
@@ -482,6 +498,7 @@ private:
|
||||
|
||||
const float m_sampleRateInp;
|
||||
const float m_sampleRateOut;
|
||||
const float m_sampleRate;
|
||||
const int m_samplesPerFrame;
|
||||
const float m_isamplesPerFrame;
|
||||
const int m_sampleSizeBytesInp;
|
||||
@@ -505,8 +522,10 @@ private:
|
||||
|
||||
bool m_isFixedPayloadLength;
|
||||
int m_payloadLength;
|
||||
TxRxData m_dataEncoded;
|
||||
|
||||
// Rx
|
||||
bool m_isRxEnabled;
|
||||
bool m_receivingData;
|
||||
bool m_analyzingData;
|
||||
|
||||
@@ -547,13 +566,12 @@ private:
|
||||
std::vector<SpectrumData> m_spectrumHistoryFixed;
|
||||
|
||||
// Tx
|
||||
bool m_isTxEnabled;
|
||||
bool m_hasNewTxData;
|
||||
float m_sendVolume;
|
||||
|
||||
int m_txDataLength;
|
||||
TxRxData m_txData;
|
||||
TxRxData m_txDataEncoded;
|
||||
|
||||
TxProtocol m_txProtocol;
|
||||
|
||||
AmplitudeData m_outputBlock;
|
||||
|
||||
@@ -119,10 +119,10 @@ bool GGWave_init(
|
||||
SDL_AudioSpec playbackSpec;
|
||||
SDL_zero(playbackSpec);
|
||||
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
|
||||
playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;
|
||||
playbackSpec.format = AUDIO_S16SYS;
|
||||
playbackSpec.channels = 1;
|
||||
playbackSpec.samples = (16*1024*GGWave::kBaseSampleRate)/48000;
|
||||
playbackSpec.samples = 16*1024;
|
||||
playbackSpec.callback = NULL;
|
||||
|
||||
SDL_zero(g_obtainedSpecOut);
|
||||
@@ -162,7 +162,7 @@ bool GGWave_init(
|
||||
if (g_devIdInp == 0) {
|
||||
SDL_AudioSpec captureSpec;
|
||||
captureSpec = g_obtainedSpecOut;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
|
||||
captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;
|
||||
captureSpec.format = AUDIO_F32SYS;
|
||||
captureSpec.samples = 1024;
|
||||
|
||||
@@ -217,10 +217,12 @@ bool GGWave_init(
|
||||
payloadLength,
|
||||
(float) g_obtainedSpecInp.freq,
|
||||
(float) g_obtainedSpecOut.freq,
|
||||
GGWave::kDefaultSampleRate,
|
||||
GGWave::kDefaultSamplesPerFrame,
|
||||
GGWave::kDefaultSoundMarkerThreshold,
|
||||
sampleFormatInp,
|
||||
sampleFormatOut});
|
||||
sampleFormatOut,
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr, "Usage: %s [-vN] [-sN] [-pN] [-lN]\n", argv[0]);
|
||||
fprintf(stderr, " -vN - output volume, N in (0, 100], (default: 50)\n");
|
||||
fprintf(stderr, " -sN - output sample rate, N in [%d, %d], (default: %d)\n", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kBaseSampleRate);
|
||||
fprintf(stderr, " -sN - output sample rate, N in [%d, %d], (default: %d)\n", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kDefaultSampleRate);
|
||||
fprintf(stderr, " -pN - select the transmission protocol id (default: 1)\n");
|
||||
fprintf(stderr, " -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed);
|
||||
fprintf(stderr, "\n");
|
||||
@@ -35,7 +35,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
int volume = argm["v"].empty() ? 50 : std::stoi(argm["v"]);
|
||||
float sampleRateOut = argm["s"].empty() ? GGWave::kBaseSampleRate : std::stof(argm["s"]);
|
||||
float sampleRateOut = argm["s"].empty() ? GGWave::kDefaultSampleRate : std::stof(argm["s"]);
|
||||
int protocolId = argm["p"].empty() ? 1 : std::stoi(argm["p"]);
|
||||
int payloadLength = argm["l"].empty() ? -1 : std::stoi(argm["l"]);
|
||||
|
||||
@@ -71,7 +71,17 @@ int main(int argc, char** argv) {
|
||||
|
||||
fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str());
|
||||
|
||||
GGWave ggWave({ payloadLength, GGWave::kBaseSampleRate, sampleRateOut, 1024, GGWave::kDefaultSamplesPerFrame, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16 });
|
||||
GGWave ggWave({
|
||||
payloadLength,
|
||||
GGWave::kDefaultSampleRate,
|
||||
sampleRateOut,
|
||||
GGWave::kDefaultSampleRate,
|
||||
GGWave::kDefaultSamplesPerFrame,
|
||||
GGWave::kDefaultSoundMarkerThreshold,
|
||||
GGWAVE_SAMPLE_FORMAT_F32,
|
||||
GGWAVE_SAMPLE_FORMAT_I16,
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
});
|
||||
ggWave.init(message.size(), message.data(), ggWave.getTxProtocol(protocolId), volume);
|
||||
|
||||
std::vector<char> bufferPCM;
|
||||
|
||||
@@ -98,7 +98,7 @@ bool GGWave_init(
|
||||
SDL_AudioSpec playbackSpec;
|
||||
SDL_zero(playbackSpec);
|
||||
|
||||
playbackSpec.freq = GGWave::kBaseSampleRate + g_sampleRateOffset;
|
||||
playbackSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset;
|
||||
playbackSpec.format = AUDIO_S16SYS;
|
||||
playbackSpec.channels = 1;
|
||||
playbackSpec.samples = 16*1024;
|
||||
@@ -139,7 +139,7 @@ bool GGWave_init(
|
||||
if (g_devIdInp == 0) {
|
||||
SDL_AudioSpec captureSpec;
|
||||
captureSpec = g_obtainedSpecOut;
|
||||
captureSpec.freq = GGWave::kBaseSampleRate + g_sampleRateOffset;
|
||||
captureSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset;
|
||||
captureSpec.format = AUDIO_F32SYS;
|
||||
captureSpec.samples = g_nSamplesPerFrame;
|
||||
|
||||
@@ -403,9 +403,9 @@ int main(int argc, char** argv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_freqDataSize = (3*GGWave::kBaseSampleRate)/g_nSamplesPerFrame;
|
||||
g_freqDataSize = (3*GGWave::kDefaultSampleRate)/g_nSamplesPerFrame;
|
||||
|
||||
float df = float(GGWave::kBaseSampleRate)/g_nSamplesPerFrame;
|
||||
float df = float(GGWave::kDefaultSampleRate)/g_nSamplesPerFrame;
|
||||
g_freqData.resize(g_nSamplesPerFrame/2);
|
||||
for (int i = 0; i < g_nSamplesPerFrame/2; ++i) {
|
||||
g_freqData[i].freq = df*i;
|
||||
|
||||
@@ -153,7 +153,7 @@ struct GGWaveStats {
|
||||
int samplesPerFrame;
|
||||
float sampleRateInp;
|
||||
float sampleRateOut;
|
||||
float sampleRateBase;
|
||||
float sampleRate;
|
||||
int sampleSizeBytesInp;
|
||||
int sampleSizeBytesOut;
|
||||
};
|
||||
@@ -650,10 +650,12 @@ void updateCore() {
|
||||
inputCurrent.payloadLength,
|
||||
sampleRateInpOld,
|
||||
sampleRateOutOld + inputCurrent.sampleRateOffset,
|
||||
GGWave::kDefaultSampleRate,
|
||||
GGWave::kDefaultSamplesPerFrame,
|
||||
GGWave::kDefaultSoundMarkerThreshold,
|
||||
sampleFormatInpOld,
|
||||
sampleFormatOutOld
|
||||
sampleFormatOutOld,
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
};
|
||||
|
||||
GGWave_reset(¶meters);
|
||||
@@ -758,7 +760,7 @@ void updateCore() {
|
||||
g_buffer.stateCore.stats.samplesPerFrame = ggWave->getSamplesPerFrame();
|
||||
g_buffer.stateCore.stats.sampleRateInp = ggWave->getSampleRateInp();
|
||||
g_buffer.stateCore.stats.sampleRateOut = ggWave->getSampleRateOut();
|
||||
g_buffer.stateCore.stats.sampleRateBase = GGWave::kBaseSampleRate;
|
||||
g_buffer.stateCore.stats.sampleRate = GGWave::kDefaultSampleRate;
|
||||
g_buffer.stateCore.stats.sampleSizeBytesInp = ggWave->getSampleSizeBytesInp();
|
||||
g_buffer.stateCore.stats.sampleSizeBytesOut = ggWave->getSampleSizeBytesOut();
|
||||
}
|
||||
@@ -1170,7 +1172,7 @@ void renderMain() {
|
||||
}
|
||||
{
|
||||
const auto & protocol = settings.txProtocols.at(GGWave::TxProtocolId(settings.protocolId));
|
||||
ImGui::Text("%4.2f B/s", (float(0.715f*protocol.bytesPerTx)/(protocol.framesPerTx*statsCurrent.samplesPerFrame))*statsCurrent.sampleRateBase);
|
||||
ImGui::Text("%4.2f B/s", (float(0.715f*protocol.bytesPerTx)/(protocol.framesPerTx*statsCurrent.samplesPerFrame))*statsCurrent.sampleRate);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1179,7 +1181,7 @@ void renderMain() {
|
||||
ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });
|
||||
}
|
||||
{
|
||||
const float df = statsCurrent.sampleRateBase/statsCurrent.samplesPerFrame;
|
||||
const float df = statsCurrent.sampleRate/statsCurrent.samplesPerFrame;
|
||||
const auto & protocol = settings.txProtocols.at(GGWave::TxProtocolId(settings.protocolId));
|
||||
ImGui::Text("%6.2f Hz - %6.2f Hz", df*protocol.freqStart, df*(protocol.freqStart + 2*16*protocol.bytesPerTx));
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ extern "C" {
|
||||
GGWAVE_TX_PROTOCOL_CUSTOM_9,
|
||||
} ggwave_TxProtocolId;
|
||||
|
||||
typedef enum {
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
GGWAVE_OPERATING_MODE_ONLY_RX,
|
||||
//GGWAVE_OPERATING_MODE_ONLY_TX, // Not supported yet
|
||||
} ggwave_OperatingMode;
|
||||
|
||||
// GGWave instance parameters
|
||||
//
|
||||
// If payloadLength <= 0, then GGWave will transmit with variable payload length
|
||||
@@ -68,21 +74,31 @@ extern "C" {
|
||||
// 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
|
||||
// The sample rates are values typically between 1000 and 96000.
|
||||
// Default value: GGWave::kDefaultSampleRate
|
||||
//
|
||||
// The captured audio is resampled to the specified sampleRate if sampleRatInp
|
||||
// is different from sampleRate. Same applies to the transmitted audio.
|
||||
//
|
||||
// The samplesPerFrame is the number of samples on which ggwave performs FFT.
|
||||
// This affects the number of bins in the Fourier spectrum.
|
||||
// Default value: GGWave::kDefaultSamplesPerFrame
|
||||
//
|
||||
// The operatingMode controls which functions of the ggwave instance are enabled.
|
||||
// Use this parameter to reduce the memory footprint of the ggwave instance. For
|
||||
// example, if only Rx is enabled, then the memory buffers needed for the Tx will
|
||||
// not be allocated.
|
||||
//
|
||||
typedef struct {
|
||||
int payloadLength; // payload length
|
||||
float sampleRateInp; // capture sample rate
|
||||
float sampleRateOut; // playback sample rate
|
||||
float sampleRate; // the operating sample rate
|
||||
int samplesPerFrame; // number of samples per audio frame
|
||||
float soundMarkerThreshold; // sound marker detection threshold
|
||||
ggwave_SampleFormat sampleFormatInp; // format of the captured audio samples
|
||||
ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples
|
||||
ggwave_OperatingMode operatingMode; // operating mode
|
||||
} ggwave_Parameters;
|
||||
|
||||
// GGWave instances are identified with an integer and are stored
|
||||
@@ -286,9 +302,9 @@ extern "C" {
|
||||
|
||||
class GGWave {
|
||||
public:
|
||||
static constexpr auto kBaseSampleRate = 48000.0f;
|
||||
static constexpr auto kSampleRateMin = 1000.0f;
|
||||
static constexpr auto kSampleRateMax = 96000.0f;
|
||||
static constexpr auto kDefaultSampleRate = 48000.0f;
|
||||
static constexpr auto kDefaultSamplesPerFrame = 1024;
|
||||
static constexpr auto kDefaultVolume = 10;
|
||||
static constexpr auto kDefaultSoundMarkerThreshold = 3.0f;
|
||||
@@ -383,14 +399,14 @@ public:
|
||||
|
||||
// expected waveform size of the encoded Tx data in bytes
|
||||
//
|
||||
// When the output sampling rate is not equal to kBaseSampleRate the result of this method is overestimation of
|
||||
// When the output sampling rate is not equal to operating sample rate the result of this method is overestimation of
|
||||
// the actual number of bytes that would be produced
|
||||
//
|
||||
uint32_t encodeSize_bytes() const;
|
||||
|
||||
// expected waveform size of the encoded Tx data in samples
|
||||
//
|
||||
// When the output sampling rate is not equal to kBaseSampleRate the result of this method is overestimation of
|
||||
// When the output sampling rate is not equal to operating sample rate the result of this method is overestimation of
|
||||
// the actual number of samples that would be produced
|
||||
//
|
||||
uint32_t encodeSize_samples() const;
|
||||
@@ -482,6 +498,7 @@ private:
|
||||
|
||||
const float m_sampleRateInp;
|
||||
const float m_sampleRateOut;
|
||||
const float m_sampleRate;
|
||||
const int m_samplesPerFrame;
|
||||
const float m_isamplesPerFrame;
|
||||
const int m_sampleSizeBytesInp;
|
||||
@@ -505,8 +522,10 @@ private:
|
||||
|
||||
bool m_isFixedPayloadLength;
|
||||
int m_payloadLength;
|
||||
TxRxData m_dataEncoded;
|
||||
|
||||
// Rx
|
||||
bool m_isRxEnabled;
|
||||
bool m_receivingData;
|
||||
bool m_analyzingData;
|
||||
|
||||
@@ -547,13 +566,12 @@ private:
|
||||
std::vector<SpectrumData> m_spectrumHistoryFixed;
|
||||
|
||||
// Tx
|
||||
bool m_isTxEnabled;
|
||||
bool m_hasNewTxData;
|
||||
float m_sendVolume;
|
||||
|
||||
int m_txDataLength;
|
||||
TxRxData m_txData;
|
||||
TxRxData m_txDataEncoded;
|
||||
|
||||
TxProtocol m_txProtocol;
|
||||
|
||||
AmplitudeData m_outputBlock;
|
||||
|
||||
252
src/ggwave.cpp
252
src/ggwave.cpp
@@ -46,10 +46,12 @@ ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
|
||||
parameters.payloadLength,
|
||||
parameters.sampleRateInp,
|
||||
parameters.sampleRateOut,
|
||||
parameters.sampleRate,
|
||||
parameters.samplesPerFrame,
|
||||
parameters.soundMarkerThreshold,
|
||||
parameters.sampleFormatInp,
|
||||
parameters.sampleFormatOut});
|
||||
parameters.sampleFormatOut,
|
||||
parameters.operatingMode});
|
||||
|
||||
return curId++;
|
||||
}
|
||||
@@ -351,73 +353,85 @@ void GGWave::setLogFile(FILE * fptr) {
|
||||
const GGWave::Parameters & GGWave::getDefaultParameters() {
|
||||
static ggwave_Parameters result {
|
||||
-1, // vaiable payload length
|
||||
kBaseSampleRate,
|
||||
kBaseSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSampleRate,
|
||||
kDefaultSamplesPerFrame,
|
||||
kDefaultSoundMarkerThreshold,
|
||||
GGWAVE_SAMPLE_FORMAT_F32,
|
||||
GGWAVE_SAMPLE_FORMAT_F32,
|
||||
GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GGWave::GGWave(const Parameters & parameters) :
|
||||
m_sampleRateInp(parameters.sampleRateInp),
|
||||
m_sampleRateOut(parameters.sampleRateOut),
|
||||
m_samplesPerFrame(parameters.samplesPerFrame),
|
||||
m_isamplesPerFrame(1.0f/m_samplesPerFrame),
|
||||
m_sampleSizeBytesInp(bytesForSampleFormat(parameters.sampleFormatInp)),
|
||||
m_sampleSizeBytesOut(bytesForSampleFormat(parameters.sampleFormatOut)),
|
||||
m_sampleFormatInp(parameters.sampleFormatInp),
|
||||
m_sampleFormatOut(parameters.sampleFormatOut),
|
||||
m_hzPerSample(kBaseSampleRate/parameters.samplesPerFrame),
|
||||
m_ihzPerSample(1.0f/m_hzPerSample),
|
||||
m_freqDelta_bin(1),
|
||||
m_freqDelta_hz(2*m_hzPerSample),
|
||||
m_nBitsInMarker(16),
|
||||
m_nMarkerFrames(parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames),
|
||||
m_encodedDataOffset(parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset),
|
||||
m_sampleRateInp (parameters.sampleRateInp),
|
||||
m_sampleRateOut (parameters.sampleRateOut),
|
||||
m_sampleRate (parameters.sampleRate),
|
||||
m_samplesPerFrame (parameters.samplesPerFrame),
|
||||
m_isamplesPerFrame (1.0f/m_samplesPerFrame),
|
||||
m_sampleSizeBytesInp (bytesForSampleFormat(parameters.sampleFormatInp)),
|
||||
m_sampleSizeBytesOut (bytesForSampleFormat(parameters.sampleFormatOut)),
|
||||
m_sampleFormatInp (parameters.sampleFormatInp),
|
||||
m_sampleFormatOut (parameters.sampleFormatOut),
|
||||
m_hzPerSample (m_sampleRate/m_samplesPerFrame),
|
||||
m_ihzPerSample (1.0f/m_hzPerSample),
|
||||
m_freqDelta_bin (1),
|
||||
m_freqDelta_hz (2*m_hzPerSample),
|
||||
m_nBitsInMarker (16),
|
||||
m_nMarkerFrames (parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames),
|
||||
m_encodedDataOffset (parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset),
|
||||
m_soundMarkerThreshold(parameters.soundMarkerThreshold),
|
||||
|
||||
// common
|
||||
m_isFixedPayloadLength(parameters.payloadLength > 0),
|
||||
m_payloadLength(parameters.payloadLength),
|
||||
m_payloadLength (parameters.payloadLength),
|
||||
m_dataEncoded (kMaxDataSize),
|
||||
|
||||
// Rx
|
||||
m_samplesNeeded(m_samplesPerFrame),
|
||||
m_fftInp(kMaxSamplesPerFrame),
|
||||
m_fftOut(2*kMaxSamplesPerFrame),
|
||||
m_hasNewSpectrum(false),
|
||||
m_hasNewAmplitude(false),
|
||||
m_sampleSpectrum(kMaxSamplesPerFrame),
|
||||
m_sampleAmplitude(kMaxSamplesPerFrame + 128), // small extra space because sometimes resampling needs a few more samples
|
||||
m_sampleAmplitudeResampled(8*kMaxSamplesPerFrame), // min input sampling rate is 0.125*kBaseSampleRate
|
||||
m_sampleAmplitudeTmp(8*kMaxSamplesPerFrame*m_sampleSizeBytesInp),
|
||||
m_hasNewRxData(false),
|
||||
m_lastRxDataLength(0),
|
||||
m_rxData(kMaxDataSize),
|
||||
m_rxProtocol(getDefaultTxProtocol()),
|
||||
m_rxProtocolId(getDefaultTxProtocolId()),
|
||||
m_rxProtocols(getTxProtocols()),
|
||||
m_historyId(0),
|
||||
m_sampleAmplitudeAverage(kMaxSamplesPerFrame),
|
||||
m_sampleAmplitudeHistory(kMaxSpectrumHistory),
|
||||
m_historyIdFixed(0),
|
||||
m_isRxEnabled(parameters.operatingMode == GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX ||
|
||||
parameters.operatingMode == GGWAVE_OPERATING_MODE_ONLY_RX),
|
||||
|
||||
m_samplesNeeded (m_samplesPerFrame),
|
||||
m_fftInp (m_samplesPerFrame),
|
||||
m_fftOut (2*m_samplesPerFrame),
|
||||
m_hasNewSpectrum (false),
|
||||
m_hasNewAmplitude (false),
|
||||
m_sampleSpectrum (m_samplesPerFrame),
|
||||
m_sampleAmplitude (m_sampleRateInp == m_sampleRate ? m_samplesPerFrame : m_samplesPerFrame + 128), // small extra space because sometimes resampling needs a few more samples
|
||||
m_sampleAmplitudeResampled(m_sampleRateInp == m_sampleRate ? m_samplesPerFrame : 8*m_samplesPerFrame), // min input sampling rate is 0.125*m_sampleRate
|
||||
m_sampleAmplitudeTmp (m_sampleRateInp == m_sampleRate ? m_samplesPerFrame*m_sampleSizeBytesInp : 8*m_samplesPerFrame*m_sampleSizeBytesInp),
|
||||
m_hasNewRxData (false),
|
||||
m_lastRxDataLength (0),
|
||||
m_rxData (kMaxDataSize),
|
||||
m_rxProtocol (getDefaultTxProtocol()),
|
||||
m_rxProtocolId (getDefaultTxProtocolId()),
|
||||
m_rxProtocols (getTxProtocols()),
|
||||
m_historyId (0),
|
||||
m_sampleAmplitudeAverage (parameters.payloadLength > 0 ? 0 : m_samplesPerFrame),
|
||||
m_sampleAmplitudeHistory (parameters.payloadLength > 0 ? 0 : kMaxSpectrumHistory),
|
||||
m_historyIdFixed (0),
|
||||
|
||||
// Tx
|
||||
m_hasNewTxData(false),
|
||||
m_sendVolume(0.1),
|
||||
m_txDataLength(0),
|
||||
m_txData(kMaxDataSize),
|
||||
m_txDataEncoded(kMaxDataSize),
|
||||
m_outputBlock(kMaxSamplesPerFrame),
|
||||
m_outputBlockResampled(2*kMaxSamplesPerFrame),
|
||||
m_outputBlockTmp(kMaxRecordedFrames*kMaxSamplesPerFrame*m_sampleSizeBytesOut),
|
||||
m_outputBlockI16(kMaxRecordedFrames*kMaxSamplesPerFrame),
|
||||
m_isTxEnabled(parameters.operatingMode == GGWAVE_OPERATING_MODE_BOTH_RX_AND_TX),
|
||||
|
||||
m_hasNewTxData (false),
|
||||
m_sendVolume (0.1),
|
||||
m_txDataLength (0),
|
||||
m_txData (m_isTxEnabled ? kMaxDataSize : 0),
|
||||
m_outputBlock (m_isTxEnabled ? m_samplesPerFrame : 0),
|
||||
m_outputBlockResampled(m_isTxEnabled ? 2*m_samplesPerFrame : 0),
|
||||
m_outputBlockTmp (m_isTxEnabled ? kMaxRecordedFrames*m_samplesPerFrame*m_sampleSizeBytesOut : 0),
|
||||
m_outputBlockI16 (m_isTxEnabled ? kMaxRecordedFrames*m_samplesPerFrame : 0),
|
||||
m_impl(new Impl()) {
|
||||
|
||||
if (m_payloadLength > 0) {
|
||||
// fixed payload length
|
||||
if (m_payloadLength > kMaxLengthFixed) {
|
||||
throw std::runtime_error("Invalid payload legnth");
|
||||
ggprintf("Invalid payload legnth: %d, max: %d\n", m_payloadLength, kMaxLengthFixed);
|
||||
return;
|
||||
}
|
||||
|
||||
m_txDataLength = m_payloadLength;
|
||||
@@ -428,29 +442,32 @@ GGWave::GGWave(const Parameters & parameters) :
|
||||
m_spectrumHistoryFixed.resize(totalTxs*maxFramesPerTx());
|
||||
} else {
|
||||
// variable payload length
|
||||
m_recordedAmplitude.resize(kMaxRecordedFrames*kMaxSamplesPerFrame);
|
||||
m_recordedAmplitude.resize(kMaxRecordedFrames*m_samplesPerFrame);
|
||||
}
|
||||
|
||||
if (m_sampleSizeBytesInp == 0) {
|
||||
throw std::runtime_error("Invalid or unsupported capture sample format");
|
||||
ggprintf("Invalid or unsupported capture sample format: %d\n", (int) parameters.sampleFormatInp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleSizeBytesOut == 0) {
|
||||
throw std::runtime_error("Invalid or unsupported playback sample format");
|
||||
ggprintf("Invalid or unsupported playback sample format: %d\n", (int) parameters.sampleFormatOut);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {
|
||||
throw std::runtime_error("Invalid samples per frame");
|
||||
ggprintf("Invalid samples per frame: %d, max: %d\n", parameters.samplesPerFrame, kMaxSamplesPerFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleRateInp < kSampleRateMin) {
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin);
|
||||
throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sampleRateInp > kSampleRateMax) {
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax);
|
||||
throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
return;
|
||||
}
|
||||
|
||||
init("", getDefaultTxProtocol(), 0);
|
||||
@@ -488,53 +505,58 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txPr
|
||||
return false;
|
||||
}
|
||||
|
||||
m_txProtocol = txProtocol;
|
||||
m_txDataLength = dataSize;
|
||||
m_sendVolume = ((double)(volume))/100.0f;
|
||||
// Tx
|
||||
if (m_isTxEnabled) {
|
||||
m_txProtocol = txProtocol;
|
||||
m_txDataLength = dataSize;
|
||||
m_sendVolume = ((double)(volume))/100.0f;
|
||||
|
||||
const uint8_t * text = reinterpret_cast<const uint8_t *>(dataBuffer);
|
||||
const uint8_t * text = reinterpret_cast<const uint8_t *>(dataBuffer);
|
||||
|
||||
m_hasNewTxData = false;
|
||||
std::fill(m_txData.begin(), m_txData.end(), 0);
|
||||
std::fill(m_txDataEncoded.begin(), m_txDataEncoded.end(), 0);
|
||||
m_hasNewTxData = false;
|
||||
std::fill(m_txData.begin(), m_txData.end(), 0);
|
||||
std::fill(m_dataEncoded.begin(), m_dataEncoded.end(), 0);
|
||||
|
||||
if (m_txDataLength > 0) {
|
||||
m_txData[0] = m_txDataLength;
|
||||
for (int i = 0; i < m_txDataLength; ++i) m_txData[i + 1] = text[i];
|
||||
if (m_txDataLength > 0) {
|
||||
m_txData[0] = m_txDataLength;
|
||||
for (int i = 0; i < m_txDataLength; ++i) m_txData[i + 1] = text[i];
|
||||
|
||||
m_hasNewTxData = true;
|
||||
}
|
||||
m_hasNewTxData = true;
|
||||
}
|
||||
|
||||
if (m_isFixedPayloadLength) {
|
||||
m_txDataLength = m_payloadLength;
|
||||
if (m_isFixedPayloadLength) {
|
||||
m_txDataLength = m_payloadLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Rx
|
||||
m_receivingData = false;
|
||||
m_analyzingData = false;
|
||||
if (m_isRxEnabled) {
|
||||
m_receivingData = false;
|
||||
m_analyzingData = false;
|
||||
|
||||
m_framesToAnalyze = 0;
|
||||
m_framesLeftToAnalyze = 0;
|
||||
m_framesToRecord = 0;
|
||||
m_framesLeftToRecord = 0;
|
||||
m_framesToAnalyze = 0;
|
||||
m_framesLeftToAnalyze = 0;
|
||||
m_framesToRecord = 0;
|
||||
m_framesLeftToRecord = 0;
|
||||
|
||||
std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0);
|
||||
std::fill(m_sampleAmplitude.begin(), m_sampleAmplitude.end(), 0);
|
||||
for (auto & s : m_sampleAmplitudeHistory) {
|
||||
s.resize(kMaxSamplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
}
|
||||
std::fill(m_sampleSpectrum.begin(), m_sampleSpectrum.end(), 0);
|
||||
std::fill(m_sampleAmplitude.begin(), m_sampleAmplitude.end(), 0);
|
||||
for (auto & s : m_sampleAmplitudeHistory) {
|
||||
s.resize(m_samplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
}
|
||||
|
||||
std::fill(m_rxData.begin(), m_rxData.end(), 0);
|
||||
std::fill(m_rxData.begin(), m_rxData.end(), 0);
|
||||
|
||||
for (int i = 0; i < m_samplesPerFrame; ++i) {
|
||||
m_fftOut[2*i + 0] = 0.0f;
|
||||
m_fftOut[2*i + 1] = 0.0f;
|
||||
}
|
||||
for (int i = 0; i < m_samplesPerFrame; ++i) {
|
||||
m_fftOut[2*i + 0] = 0.0f;
|
||||
m_fftOut[2*i + 1] = 0.0f;
|
||||
}
|
||||
|
||||
for (auto & s : m_spectrumHistoryFixed) {
|
||||
s.resize(kMaxSamplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
for (auto & s : m_spectrumHistoryFixed) {
|
||||
s.resize(m_samplesPerFrame);
|
||||
std::fill(s.begin(), s.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -551,8 +573,8 @@ uint32_t GGWave::encodeSize_samples() const {
|
||||
|
||||
float factor = 1.0f;
|
||||
int samplesPerFrameOut = m_samplesPerFrame;
|
||||
if (m_sampleRateOut != kBaseSampleRate) {
|
||||
factor = kBaseSampleRate/m_sampleRateOut;
|
||||
if (m_sampleRateOut != m_sampleRate) {
|
||||
factor = m_sampleRate/m_sampleRateOut;
|
||||
// note : +1 extra sample in order to overestimate the buffer size
|
||||
samplesPerFrameOut = m_impl->resampler.resample(factor, m_samplesPerFrame, m_outputBlock.data(), nullptr) + 1;
|
||||
}
|
||||
@@ -591,8 +613,8 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
for (int k = 0; k < (int) dataBits.size(); ++k) {
|
||||
double freq = bitFreq(m_txProtocol, k);
|
||||
|
||||
bit1Amplitude[k].resize(kMaxSamplesPerFrame);
|
||||
bit0Amplitude[k].resize(kMaxSamplesPerFrame);
|
||||
bit1Amplitude[k].resize(m_samplesPerFrame);
|
||||
bit0Amplitude[k].resize(m_samplesPerFrame);
|
||||
|
||||
double phaseOffset = phaseOffsets[k];
|
||||
double curHzPerSample = m_hzPerSample;
|
||||
@@ -614,16 +636,16 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
if (m_isFixedPayloadLength == false) {
|
||||
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1);
|
||||
rsLength.Encode(m_txData.data(), m_txDataEncoded.data());
|
||||
rsLength.Encode(m_txData.data(), m_dataEncoded.data());
|
||||
}
|
||||
|
||||
// first byte of m_txData contains the length of the payload, so we skip it:
|
||||
RS::ReedSolomon rsData = RS::ReedSolomon(m_txDataLength, nECCBytesPerTx);
|
||||
rsData.Encode(m_txData.data() + 1, m_txDataEncoded.data() + m_encodedDataOffset);
|
||||
rsData.Encode(m_txData.data() + 1, m_dataEncoded.data() + m_encodedDataOffset);
|
||||
|
||||
const float factor = m_sampleRate/m_sampleRateOut;
|
||||
|
||||
float factor = kBaseSampleRate/m_sampleRateOut;
|
||||
uint32_t offset = 0;
|
||||
|
||||
m_waveformTones.clear();
|
||||
|
||||
while (m_hasNewTxData) {
|
||||
@@ -637,7 +659,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
for (int i = 0; i < m_nBitsInMarker; ++i) {
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (i%2 == 0) {
|
||||
::addAmplitudeSmooth(bit1Amplitude[i], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, i);
|
||||
@@ -656,11 +678,11 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
for (int j = 0; j < m_txProtocol.bytesPerTx; ++j) {
|
||||
{
|
||||
uint8_t d = m_txDataEncoded[dataOffset + j] & 15;
|
||||
uint8_t d = m_dataEncoded[dataOffset + j] & 15;
|
||||
dataBits[(2*j + 0)*16 + d] = 1;
|
||||
}
|
||||
{
|
||||
uint8_t d = m_txDataEncoded[dataOffset + j] & 240;
|
||||
uint8_t d = m_dataEncoded[dataOffset + j] & 240;
|
||||
dataBits[(2*j + 1)*16 + (d >> 4)] = 1;
|
||||
}
|
||||
}
|
||||
@@ -670,7 +692,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
|
||||
++nFreq;
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (k%2) {
|
||||
::addAmplitudeSmooth(bit0Amplitude[k/2], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, cycleModMain, m_txProtocol.framesPerTx);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, k/2) + m_hzPerSample;
|
||||
@@ -685,7 +707,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
int fId = frameId - (m_nMarkerFrames + totalDataFrames);
|
||||
for (int i = 0; i < m_nBitsInMarker; ++i) {
|
||||
m_waveformTones.back().push_back({});
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/kBaseSampleRate;
|
||||
m_waveformTones.back().back().duration_ms = (1000.0*m_samplesPerFrame)/m_sampleRate;
|
||||
if (i%2 == 0) {
|
||||
addAmplitudeSmooth(bit0Amplitude[i], m_outputBlock, m_sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);
|
||||
m_waveformTones.back().back().freq_hz = bitFreq(m_txProtocol, i) + m_hzPerSample;
|
||||
@@ -706,7 +728,7 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
}
|
||||
|
||||
int samplesPerFrameOut = m_samplesPerFrame;
|
||||
if (m_sampleRateOut != kBaseSampleRate) {
|
||||
if (m_sampleRateOut != m_sampleRate) {
|
||||
samplesPerFrameOut = m_impl->resampler.resample(factor, m_samplesPerFrame, m_outputBlock.data(), m_outputBlockResampled.data());
|
||||
} else {
|
||||
m_outputBlockResampled = m_outputBlock;
|
||||
@@ -788,10 +810,10 @@ bool GGWave::encode(const CBWaveformOut & cbWaveformOut) {
|
||||
void GGWave::decode(const CBWaveformInp & cbWaveformInp) {
|
||||
while (m_hasNewTxData == false) {
|
||||
// read capture data
|
||||
float factor = m_sampleRateInp/kBaseSampleRate;
|
||||
float factor = m_sampleRateInp/m_sampleRate;
|
||||
uint32_t nBytesNeeded = m_samplesNeeded*m_sampleSizeBytesInp;
|
||||
|
||||
if (m_sampleRateInp != kBaseSampleRate) {
|
||||
if (m_sampleRateInp != m_sampleRate) {
|
||||
// note : predict 4 extra samples just to make sure we have enough data
|
||||
nBytesNeeded = (m_impl->resampler.resample(1.0f/factor, m_samplesNeeded, m_sampleAmplitudeResampled.data(), nullptr) + 4)*m_sampleSizeBytesInp;
|
||||
}
|
||||
@@ -872,14 +894,14 @@ void GGWave::decode(const CBWaveformInp & cbWaveformInp) {
|
||||
|
||||
uint32_t offset = m_samplesPerFrame - m_samplesNeeded;
|
||||
|
||||
if (m_sampleRateInp != kBaseSampleRate) {
|
||||
if (m_sampleRateInp != m_sampleRate) {
|
||||
if (nSamplesRecorded <= 2*Resampler::kWidth) {
|
||||
m_samplesNeeded = m_samplesPerFrame;
|
||||
break;
|
||||
}
|
||||
|
||||
// reset resampler state every minute
|
||||
if (!m_receivingData && m_impl->resampler.nSamplesTotal() > 60.0f*factor*kBaseSampleRate) {
|
||||
if (!m_receivingData && m_impl->resampler.nSamplesTotal() > 60.0f*factor*m_sampleRate) {
|
||||
m_impl->resampler.reset();
|
||||
}
|
||||
|
||||
@@ -1051,7 +1073,7 @@ void GGWave::decode_variable() {
|
||||
const int offsetStart = ii;
|
||||
for (int itx = 0; itx < 1024; ++itx) {
|
||||
int offsetTx = offsetStart + itx*rxProtocol.framesPerTx*stepsPerFrame;
|
||||
if (offsetTx >= m_recvDuration_frames*stepsPerFrame || (itx + 1)*rxProtocol.bytesPerTx >= (int) m_txDataEncoded.size()) {
|
||||
if (offsetTx >= m_recvDuration_frames*stepsPerFrame || (itx + 1)*rxProtocol.bytesPerTx >= (int) m_dataEncoded.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1091,7 +1113,7 @@ void GGWave::decode_variable() {
|
||||
|
||||
if (i%2) {
|
||||
curByte += (kmax << 4);
|
||||
m_txDataEncoded[itx*rxProtocol.bytesPerTx + i/2] = curByte;
|
||||
m_dataEncoded[itx*rxProtocol.bytesPerTx + i/2] = curByte;
|
||||
curByte = 0;
|
||||
} else {
|
||||
curByte = kmax;
|
||||
@@ -1100,7 +1122,7 @@ void GGWave::decode_variable() {
|
||||
|
||||
if (itx*rxProtocol.bytesPerTx > m_encodedDataOffset && knownLength == false) {
|
||||
RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1);
|
||||
if ((rsLength.Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) && (m_rxData[0] > 0 && m_rxData[0] <= 140)) {
|
||||
if ((rsLength.Decode(m_dataEncoded.data(), m_rxData.data()) == 0) && (m_rxData[0] > 0 && m_rxData[0] <= 140)) {
|
||||
knownLength = true;
|
||||
decodedLength = m_rxData[0];
|
||||
|
||||
@@ -1127,7 +1149,7 @@ void GGWave::decode_variable() {
|
||||
if (knownLength) {
|
||||
RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength));
|
||||
|
||||
if (rsData.Decode(m_txDataEncoded.data() + m_encodedDataOffset, m_rxData.data()) == 0) {
|
||||
if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rxData.data()) == 0) {
|
||||
if (m_rxData[0] != 0) {
|
||||
std::string s((char *) m_rxData.data(), decodedLength);
|
||||
|
||||
@@ -1294,7 +1316,7 @@ void GGWave::decode_fixed() {
|
||||
const int binStart = rxProtocol.freqStart;
|
||||
const int binDelta = 16;
|
||||
|
||||
if (binStart > kMaxSamplesPerFrame) {
|
||||
if (binStart > m_samplesPerFrame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1382,10 +1404,6 @@ void GGWave::decode_fixed() {
|
||||
txNeededTotal += txNeeded;
|
||||
}
|
||||
|
||||
//if (rxProtocolId == GGWAVE_TX_PROTOCOL_DT_FAST) {
|
||||
// printf("detected = %d, needed = %d\n", txDetectedTotal, txNeededTotal);
|
||||
//}
|
||||
|
||||
if (txDetectedTotal < 0.75*txNeededTotal) {
|
||||
detectedSignal = false;
|
||||
}
|
||||
@@ -1394,10 +1412,10 @@ void GGWave::decode_fixed() {
|
||||
RS::ReedSolomon rsData(m_payloadLength, getECCBytesForLength(m_payloadLength));
|
||||
|
||||
for (int j = 0; j < totalLength; ++j) {
|
||||
m_txDataEncoded[j] = (detectedBins[2*j + 1] << 4) + detectedBins[2*j + 0];
|
||||
m_dataEncoded[j] = (detectedBins[2*j + 1] << 4) + detectedBins[2*j + 0];
|
||||
}
|
||||
|
||||
if (rsData.Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) {
|
||||
if (rsData.Decode(m_dataEncoded.data(), m_rxData.data()) == 0) {
|
||||
if (m_rxData[0] != 0) {
|
||||
ggprintf("Received sound data successfully: '%s'\n", m_rxData.data());
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ int main(int argc, char ** argv) {
|
||||
}
|
||||
|
||||
// playback / capture at different sample rates
|
||||
for (int srInp = GGWave::kBaseSampleRate/6; srInp <= 2*GGWave::kBaseSampleRate; srInp += 1371) {
|
||||
for (int srInp = GGWave::kDefaultSampleRate/6; srInp <= 2*GGWave::kDefaultSampleRate; srInp += 1371) {
|
||||
printf("Testing: sample rate = %d\n", srInp);
|
||||
|
||||
auto parameters = GGWave::getDefaultParameters();
|
||||
|
||||
Reference in New Issue
Block a user