From 8fa457fce029fe31a6fd003de7f5d9e68abf0fda Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Sat, 28 May 2022 22:14:16 +0300 Subject: [PATCH] wip --- examples/CMakeLists.txt | 2 + examples/arduino-rx-web/CMakeLists.txt | 6 +- .../{arduino-rx.cpp => arduino-rx-web.cpp} | 0 examples/arduino-rx/CMakeLists.txt | 10 + examples/arduino-rx/arduino-rx.ino | 7 +- examples/arduino-rx/ggwave.cpp | 260 ++++++++++-------- examples/arduino-rx/ggwave/ggwave.h | 56 ++-- examples/ggwave-common-sdl2.cpp | 10 +- examples/ggwave-to-file/main.cpp | 16 +- examples/spectrogram/main.cpp | 8 +- examples/waver/common.cpp | 12 +- include/ggwave/ggwave.h | 32 ++- src/ggwave.cpp | 252 +++++++++-------- tests/test-ggwave.cpp | 2 +- 14 files changed, 387 insertions(+), 286 deletions(-) rename examples/arduino-rx-web/{arduino-rx.cpp => arduino-rx-web.cpp} (100%) create mode 100644 examples/arduino-rx/CMakeLists.txt diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 71dcc91..113df7f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -89,6 +89,8 @@ if (EMSCRIPTEN) add_subdirectory(buttons) else() add_subdirectory(ggwave-to-file) + + add_subdirectory(arduino-rx) endif() if (GGWAVE_SUPPORT_SDL2) diff --git a/examples/arduino-rx-web/CMakeLists.txt b/examples/arduino-rx-web/CMakeLists.txt index ffbdfe5..3aa06b7 100644 --- a/examples/arduino-rx-web/CMakeLists.txt +++ b/examples/arduino-rx-web/CMakeLists.txt @@ -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 diff --git a/examples/arduino-rx-web/arduino-rx.cpp b/examples/arduino-rx-web/arduino-rx-web.cpp similarity index 100% rename from examples/arduino-rx-web/arduino-rx.cpp rename to examples/arduino-rx-web/arduino-rx-web.cpp diff --git a/examples/arduino-rx/CMakeLists.txt b/examples/arduino-rx/CMakeLists.txt new file mode 100644 index 0000000..76bd66f --- /dev/null +++ b/examples/arduino-rx/CMakeLists.txt @@ -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) diff --git a/examples/arduino-rx/arduino-rx.ino b/examples/arduino-rx/arduino-rx.ino index 9cad340..8a74493 100644 --- a/examples/arduino-rx/arduino-rx.ino +++ b/examples/arduino-rx/arduino-rx.ino @@ -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) { diff --git a/examples/arduino-rx/ggwave.cpp b/examples/arduino-rx/ggwave.cpp index 90d983e..000bd22 100644 --- a/examples/arduino-rx/ggwave.cpp +++ b/examples/arduino-rx/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,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(dataBuffer); + const uint8_t * text = reinterpret_cast(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()); diff --git a/examples/arduino-rx/ggwave/ggwave.h b/examples/arduino-rx/ggwave/ggwave.h index b620958..80e7d25 100644 --- a/examples/arduino-rx/ggwave/ggwave.h +++ b/examples/arduino-rx/ggwave/ggwave.h @@ -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 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; diff --git a/examples/ggwave-common-sdl2.cpp b/examples/ggwave-common-sdl2.cpp index c6bcc03..b5f4ed7 100644 --- a/examples/ggwave-common-sdl2.cpp +++ b/examples/ggwave-common-sdl2.cpp @@ -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; diff --git a/examples/ggwave-to-file/main.cpp b/examples/ggwave-to-file/main.cpp index 66d52d9..922a7cd 100644 --- a/examples/ggwave-to-file/main.cpp +++ b/examples/ggwave-to-file/main.cpp @@ -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 bufferPCM; diff --git a/examples/spectrogram/main.cpp b/examples/spectrogram/main.cpp index 43556ce..9595ff1 100644 --- a/examples/spectrogram/main.cpp +++ b/examples/spectrogram/main.cpp @@ -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; diff --git a/examples/waver/common.cpp b/examples/waver/common.cpp index 4e45bdd..f66784e 100644 --- a/examples/waver/common.cpp +++ b/examples/waver/common.cpp @@ -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)); } diff --git a/include/ggwave/ggwave.h b/include/ggwave/ggwave.h index 1af5e04..80e7d25 100644 --- a/include/ggwave/ggwave.h +++ b/include/ggwave/ggwave.h @@ -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 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; diff --git a/src/ggwave.cpp b/src/ggwave.cpp index aed64e2..000bd22 100644 --- a/src/ggwave.cpp +++ b/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(dataBuffer); + const uint8_t * text = reinterpret_cast(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()); diff --git a/tests/test-ggwave.cpp b/tests/test-ggwave.cpp index bbdea63..e5e352c 100644 --- a/tests/test-ggwave.cpp +++ b/tests/test-ggwave.cpp @@ -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();