#ifndef GGWAVE_H #define GGWAVE_H #ifdef GGWAVE_SHARED # ifdef _WIN32 # ifdef GGWAVE_BUILD # define GGWAVE_API __declspec(dllexport) # else # define GGWAVE_API __declspec(dllimport) # endif # else # define GGWAVE_API __attribute__ ((visibility ("default"))) # endif #else # define GGWAVE_API #endif #ifdef __cplusplus extern "C" { #endif // // C interface // // Data format of the audio samples typedef enum { GGWAVE_SAMPLE_FORMAT_UNDEFINED, GGWAVE_SAMPLE_FORMAT_U8, GGWAVE_SAMPLE_FORMAT_I8, GGWAVE_SAMPLE_FORMAT_U16, GGWAVE_SAMPLE_FORMAT_I16, GGWAVE_SAMPLE_FORMAT_F32, } ggwave_SampleFormat; // TxProtocol ids typedef enum { GGWAVE_TX_PROTOCOL_AUDIBLE_NORMAL, GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, GGWAVE_TX_PROTOCOL_AUDIBLE_FASTEST, GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL, GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST, GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST, GGWAVE_TX_PROTOCOL_DT_NORMAL, GGWAVE_TX_PROTOCOL_DT_FAST, GGWAVE_TX_PROTOCOL_DT_FASTEST, GGWAVE_TX_PROTOCOL_CUSTOM_0, GGWAVE_TX_PROTOCOL_CUSTOM_1, GGWAVE_TX_PROTOCOL_CUSTOM_2, GGWAVE_TX_PROTOCOL_CUSTOM_3, GGWAVE_TX_PROTOCOL_CUSTOM_4, GGWAVE_TX_PROTOCOL_CUSTOM_5, GGWAVE_TX_PROTOCOL_CUSTOM_6, GGWAVE_TX_PROTOCOL_CUSTOM_7, GGWAVE_TX_PROTOCOL_CUSTOM_8, GGWAVE_TX_PROTOCOL_CUSTOM_9, } ggwave_TxProtocolId; // GGWave instance parameters // // If payloadLength <= 0, then GGWave will transmit with variable payload length // depending on the provided payload. Sound markers are used to identify the // start and end of the transmission. // // If payloadLength > 0, then the transmitted payload will be of the specified // fixed length. In this case, no sound markers are emitted and a slightly // different decoding scheme is applied. This is useful in cases where the // length of the payload is known in advance. // // The sample rates are values typically between 8000 and 96000. // Default value: GGWave::kBaseSampleRate // // 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 // typedef struct { int payloadLength; // payload length float sampleRateInp; // capture sample rate float sampleRateOut; // playback 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_Parameters; // GGWave instances are identified with an integer and are stored // in a private map container. Using void * caused some issues with // the python module and unfortunately had to do it this way typedef int ggwave_Instance; // Change file stream for internal ggwave logging. NULL - disable logging // // Intentionally passing it as void * instead of FILE * to avoid including a header // // // log to standard error // ggwave_setLogFile(stderr); // // // log to standard output // ggwave_setLogFile(stdout); // // // disable logging // ggwave_setLogFile(NULL); // // Note: not thread-safe. Do not call while any GGWave instances are running // GGWAVE_API void ggwave_setLogFile(void * fptr); // Helper method to get default instance parameters GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void); // Create a new GGWave instance with the specified parameters // // The newly created instance is added to the internal map container. // This function returns an id that can be used to identify this instance. // Make sure to deallocate the instance at the end by calling ggwave_free() // GGWAVE_API ggwave_Instance ggwave_init(const ggwave_Parameters parameters); // Free a GGWave instance GGWAVE_API void ggwave_free(ggwave_Instance instance); // Encode data into audio waveform // // instance - the GGWave instance to use // dataBuffer - the data to encode // dataSize - number of bytes in the input dataBuffer // txProtocolId - the protocol to use for encoding // volume - the volume of the generated waveform [0, 100] // usually 25 is OK and you should not go over 50 // outputBuffer - the generated audio waveform. must be big enough to fit the generated data // query - if != 0, do not perform encoding. // if == 1, return waveform size in bytes // if != 1, return waveform size in samples // // returns the number of generated bytes or samples (see query) // // returns -1 if there was an error // // This function can be used to encode some binary data (payload) into an audio waveform. // // payload -> waveform // // When calling it, make sure that the outputBuffer is big enough to store the // generated waveform. This means that its size must be at least: // // nSamples*sizeOfSample_bytes // // Where nSamples is the number of audio samples in the waveform and sizeOfSample_bytes // is the size of a single sample in bytes based on the sampleFormatOut parameter // specified during the initialization of the GGWave instance. // // If query != 0, then this function does not perform the actual encoding and just // outputs the expected size of the waveform that would be generated if you call it // with query == 0. This mechanism can be used to ask ggwave how much memory to // allocate for the outputBuffer. For example: // // // this is the data to encode // const char * payload = "test"; // // // query the number of bytes in the waveform // int n = ggwave_encode(instance, payload, 4, GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, 25, NULL, 1); // // // allocate the output buffer // char waveform[n]; // // // generate the waveform // ggwave_encode(instance, payload, 4, GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, 25, waveform, 0); // // The dataBuffer can be any binary data that you would like to transmit (i.e. the payload). // Usually, this is some text, but it can be any sequence of bytes. // // todo: // - change the type of dataBuffer to const void * // - change the type of outputBuffer to void * // - rename dataBuffer to payloadBuffer // - rename dataSize to payloadSize // - rename outputBuffer to waveformBuffer // GGWAVE_API int ggwave_encode( ggwave_Instance instance, const char * dataBuffer, int dataSize, ggwave_TxProtocolId txProtocolId, int volume, char * outputBuffer, int query); // Decode an audio waveform into data // // instance - the GGWave instance to use // dataBuffer - the audio waveform // dataSize - number of bytes in the input dataBuffer // outputBuffer - stores the decoded data on success // the maximum size of the output is GGWave::kMaxDataSize // // returns the number of decoded bytes // // Use this function to continuously provide audio samples to a GGWave instance. // On each call, GGWave will analyze the provided data and if it detects a payload, // it will return a non-zero result. // // waveform -> payload // // If the return value is -1 then there was an error during the decoding process. // Usually can occur if there is a lot of background noise in the audio. // // If the return value is greater than 0, then there are that number of bytes decoded. // // IMPORTANT: // Notice that the decoded data written to the outputBuffer is NOT null terminated. // // Example: // // char payload[256]; // // while (true) { // ... capture samplesPerFrame audio samples into waveform ... // // int ret = ggwave_decode(instance, waveform, samplesPerFrame*sizeOfSample_bytes, payload); // if (ret > 0) { // printf("Received payload: '%s'\n", payload); // } // } // // todo: // - change the type of dataBuffer to const void * // - change the type of outputBuffer to void * // - rename dataBuffer to waveformBuffer // - rename dataSize to waveformSize // - rename outputBuffer to payloadBuffer // GGWAVE_API int ggwave_decode( ggwave_Instance instance, const char * dataBuffer, int dataSize, char * outputBuffer); // Memory-safe overload of ggwave_decode // // outputSize - optionally specify the size of the output buffer // // If the return value is -2 then the provided outputBuffer was not big enough to // store the decoded data. // // See ggwave_decode for more information // GGWAVE_API int ggwave_ndecode( ggwave_Instance instance, const char * dataBuffer, int dataSize, char * outputBuffer, int outputSize); // Toggle Rx protocols on and off // // instance - the GGWave instance to use // rxProtocolId - Id of the Rx protocol to modify // state - 0 - disable, 1 - enable // // If an Rx protocol is enabled, the GGWave instance will attempt to decode received // data using this protocol. By default, all protocols are enabled. // Use this function to restrict the number of Rx protocols used in the decoding // process. This helps to reduce the number of false positives and improves the transmission // accuracy, especially when the Tx/Rx protocol is known in advance. // GGWAVE_API void ggwave_toggleRxProtocol( ggwave_Instance instance, ggwave_TxProtocolId rxProtocolId, int state); #ifdef __cplusplus } // // C++ interface // #include #include #include #include #include #include class GGWave { public: static constexpr auto kBaseSampleRate = 48000.0f; static constexpr auto kSampleRateMin = 6000.0f; static constexpr auto kSampleRateMax = 96000.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 = 2048; static constexpr auto kMaxDataBits = 256; static constexpr auto kMaxDataSize = 256; static constexpr auto kMaxLengthVarible = 140; static constexpr auto kMaxLengthFixed = 16; static constexpr auto kMaxSpectrumHistory = 4; static constexpr auto kMaxRecordedFrames = 2048; using Parameters = ggwave_Parameters; using SampleFormat = ggwave_SampleFormat; using TxProtocolId = ggwave_TxProtocolId; using RxProtocolId = ggwave_TxProtocolId; struct TxProtocol { const char * name; // string identifier of the protocol int freqStart; // FFT bin index of the lowest frequency int framesPerTx; // number of frames to transmit a single chunk of data int bytesPerTx; // number of bytes in a chunk of data int nDataBitsPerTx() const { return 8*bytesPerTx; } }; using RxProtocol = TxProtocol; using TxProtocols = std::map; using RxProtocols = std::map; 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_DT_FASTEST, { "[DT] Fastest", 24, 3, 1, } }, { GGWAVE_TX_PROTOCOL_DT_NORMAL, { "Arduino", 16, 9, 1, } }, { GGWAVE_TX_PROTOCOL_DT_FAST, { "Arduino", 16, 6, 1, } }, { GGWAVE_TX_PROTOCOL_DT_FASTEST, { "Arduino", 16, 3, 1, } }, }; return kTxProtocols; } struct ToneData { double freq_hz; double duration_ms; }; using Tones = std::vector; using WaveformTones = std::vector; using AmplitudeData = std::vector; using AmplitudeDataI16 = std::vector; using SpectrumData = std::vector; using RecordedData = std::vector; using TxRxData = std::vector; using CBWaveformOut = std::function; using CBWaveformInp = std::function; GGWave(const Parameters & parameters); ~GGWave(); // set file stream for the internal ggwave logging // // By default, ggwave prints internal log messages to stderr. // To disable logging all together, call this method with nullptr. // // Note: not thread-safe. Do not call while any GGWave instances are running // static void setLogFile(FILE * fptr); static const Parameters & getDefaultParameters(); // set Tx data to encode // // This prepares the GGWave instance for transmission. // To perform the actual encoding, the encode() method must be called // // returns false upon invalid parameters or failure to initialize // bool init(const std::string & text, const int volume = kDefaultVolume); bool init(const std::string & text, const TxProtocol & txProtocol, const int volume = kDefaultVolume); bool init(int dataSize, const char * dataBuffer, const int volume = kDefaultVolume); bool init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume = kDefaultVolume); // 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 // 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 // the actual number of samples that would be produced // uint32_t encodeSize_samples() const; // encode Tx data into an audio waveform // // The generated waveform is returned by calling the cbWaveformOut callback. // // returns false if the encoding fails // bool encode(const CBWaveformOut & cbWaveformOut); // decode an audio waveform // // This methods calls cbWaveformInp multiple times (at least once) until it returns 0. // Use the Rx methods to check if any data was decoded successfully. // void decode(const CBWaveformInp & cbWaveformInp); // instance state const bool & hasTxData() const { return m_hasNewTxData; } const bool & isReceiving() const { return m_receivingData; } const bool & isAnalyzing() const { return m_analyzingData; } const int & getFramesToRecord() const { return m_framesToRecord; } const int & getFramesLeftToRecord() const { return m_framesLeftToRecord; } const int & getFramesToAnalyze() const { return m_framesToAnalyze; } const int & getFramesLeftToAnalyze() const { return m_framesLeftToAnalyze; } const int & getSamplesPerFrame() const { return m_samplesPerFrame; } const int & getSampleSizeBytesInp() const { return m_sampleSizeBytesInp; } const int & getSampleSizeBytesOut() const { return m_sampleSizeBytesOut; } const float & getSampleRateInp() const { return m_sampleRateInp; } const float & getSampleRateOut() const { return m_sampleRateOut; } const SampleFormat & getSampleFormatInp() const { return m_sampleFormatInp; } const SampleFormat & getSampleFormatOut() const { return m_sampleFormatOut; } // Tx static TxProtocolId getDefaultTxProtocolId() { return GGWAVE_TX_PROTOCOL_DT_NORMAL; } 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); } // get a list of the tones generated for the last waveform // // Call this method after calling encode() to get a list of the tones participating in the generated waveform // const WaveformTones & getWaveformTones() { return m_waveformTones; } bool takeTxAmplitudeI16(AmplitudeDataI16 & dst); // Rx bool stopReceiving(); void setRxProtocols(const RxProtocols & rxProtocols) { m_rxProtocols = rxProtocols; } const RxProtocols & getRxProtocols() const { return m_rxProtocols; } int lastRxDataLength() const { return m_lastRxDataLength; } const TxRxData & getRxData() const { return m_rxData; } const RxProtocol & getRxProtocol() const { return m_rxProtocol; } const RxProtocolId & getRxProtocolId() const { return m_rxProtocolId; } int takeRxData(TxRxData & dst); bool takeRxSpectrum(SpectrumData & dst); bool takeRxAmplitude(AmplitudeData & dst); // compute FFT of real values // // src - input real-valued data, size is N // dst - output complex-valued data, size is 2*N // // d is scaling factor // N must be <= kMaxSamplesPerFrame // static bool computeFFTR(const float * src, float * dst, int N, float d); private: void decode_fixed(); void decode_variable(); int maxFramesPerTx() const; int minBytesPerTx() const; double bitFreq(const TxProtocol & p, int bit) const { return m_hzPerSample*p.freqStart + m_freqDelta_hz*bit; } const float m_sampleRateInp; const float m_sampleRateOut; const int m_samplesPerFrame; const float m_isamplesPerFrame; const int m_sampleSizeBytesInp; const int m_sampleSizeBytesOut; const SampleFormat m_sampleFormatInp; const SampleFormat m_sampleFormatOut; const float m_hzPerSample; const float m_ihzPerSample; const int m_freqDelta_bin; const float m_freqDelta_hz; const int m_nBitsInMarker; const int m_nMarkerFrames; const int m_encodedDataOffset; const float m_soundMarkerThreshold; // common bool m_isFixedPayloadLength; int m_payloadLength; // Rx bool m_receivingData; bool m_analyzingData; int m_nMarkersSuccess; int m_markerFreqStart; int m_recvDuration_frames; int m_framesLeftToAnalyze; int m_framesLeftToRecord; int m_framesToAnalyze; int m_framesToRecord; int m_samplesNeeded; std::vector m_fftInp; // real std::vector m_fftOut; // complex bool m_hasNewSpectrum; bool m_hasNewAmplitude; SpectrumData m_sampleSpectrum; AmplitudeData m_sampleAmplitude; AmplitudeData m_sampleAmplitudeResampled; TxRxData m_sampleAmplitudeTmp; bool m_hasNewRxData; int m_lastRxDataLength; TxRxData m_rxData; TxProtocol m_rxProtocol; TxProtocolId m_rxProtocolId; TxProtocols m_rxProtocols; int m_historyId; AmplitudeData m_sampleAmplitudeAverage; std::vector m_sampleAmplitudeHistory; RecordedData m_recordedAmplitude; int m_historyIdFixed; std::vector m_spectrumHistoryFixed; // Tx bool m_hasNewTxData; float m_sendVolume; int m_txDataLength; TxRxData m_txData; TxRxData m_txDataEncoded; TxProtocol m_txProtocol; AmplitudeData m_outputBlock; AmplitudeData m_outputBlockResampled; TxRxData m_outputBlockTmp; AmplitudeDataI16 m_outputBlockI16; AmplitudeDataI16 m_txAmplitudeDataI16; WaveformTones m_waveformTones; // Impl // todo : move all members inside Impl struct Impl; std::unique_ptr m_impl; }; #endif #endif