r2t2 - Transmit data through the PC speaker (#32)

* inital implementation

* remove file

* ggwave-cli : txProtocol -> txProtocolId

* ggwave : add custom protocol enum values

* r2t2 : use cutom protocols

* r2t2 : build only on Unix systems

* r2t2 : remove thread

* r2t2-rx : wip

* r2t2 : wasm build ready + various fixes

* r2t2 : error message

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* r2t2 : length 16

* r2t2 : use slow protocol by default

* r2t2 : add timestamp

* r2t2 : update html

* r2t2 : update github link

* r2t2 : more robust tx

* r2t2 : add option to use beep command

* emscripten : cannot use requestAnimationFrame when capturing audio

This causes the queued audio buffer to grow indefinitely when the page
is not focused, causing the process to run out of memory.

* r2t2 : disable beep option

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* r2t2 : add example to README
This commit is contained in:
Georgi Gerganov
2021-04-18 13:20:45 +03:00
committed by GitHub
parent 464aa2283b
commit 00b23ff54f
28 changed files with 4153 additions and 29 deletions

View File

@@ -101,6 +101,7 @@ The [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder
| [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API |
| [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL |
| [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio |
| [r2t2](https://gitlab.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker |
Other projects using **ggwave** or one of its prototypes:

View File

@@ -101,6 +101,7 @@ The [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder
| [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API |
| [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL |
| [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio |
| [r2t2](https://gitlab.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker |
Other projects using **ggwave** or one of its prototypes:

File diff suppressed because one or more lines are too long

View File

@@ -84,6 +84,10 @@ else()
add_subdirectory(ggwave-to-file)
endif()
if (UNIX AND NOT APPLE)
add_subdirectory(r2t2)
endif()
if (GGWAVE_SUPPORT_SDL2)
if (EMSCRIPTEN)
# emscripten sdl2 examples

View File

@@ -24,7 +24,7 @@ int main(int argc, char** argv) {
auto argm = parseCmdArguments(argc, argv);
int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]);
int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]);
int txProtocol = argm["t"].empty() ? 1 : std::stoi(argm["t"]);
int txProtocolId = argm["t"].empty() ? 1 : std::stoi(argm["t"]);
int payloadLength = argm["l"].empty() ? -1 : std::stoi(argm["l"]);
bool printTones = argm.find("v") == argm.end() ? false : true;
@@ -41,12 +41,12 @@ int main(int argc, char** argv) {
printf(" -t%d : %s\n", protocol.first, protocol.second.name);
}
if (txProtocol < 0 || txProtocol > (int) ggWave->getTxProtocols().size()) {
fprintf(stderr, "Unknown Tx protocol %d\n", txProtocol);
if (txProtocolId < 0 || txProtocolId > (int) ggWave->getTxProtocols().size()) {
fprintf(stderr, "Unknown Tx protocol %d\n", txProtocolId);
return -3;
}
printf("Selecting Tx protocol %d\n", txProtocol);
printf("Selecting Tx protocol %d\n", txProtocolId);
std::mutex mutex;
std::thread inputThread([&]() {
@@ -76,7 +76,7 @@ int main(int argc, char** argv) {
}
{
std::lock_guard<std::mutex> lock(mutex);
ggWave->init(input.size(), input.data(), ggWave->getTxProtocol(txProtocol), 10);
ggWave->init(input.size(), input.data(), ggWave->getTxProtocol(txProtocolId), 10);
}
inputOld = input;
}

View File

@@ -169,7 +169,7 @@ bool GGWave_init(
SDL_zero(g_obtainedSpecInp);
if (captureId >= 0) {
printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_FALSE));
printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE));
g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);
} else {
printf("Attempt to open default capture device ...\n");
@@ -254,6 +254,7 @@ bool GGWave_mainLoop() {
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
g_ggWave->decode(cbWaveformInp);
if ((int) SDL_GetQueuedAudioSize(g_devIdInp) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesInp()) {
fprintf(stderr, "Warning: slow processing, clearing queued audio buffer of %d bytes ...", SDL_GetQueuedAudioSize(g_devIdInp));
SDL_ClearQueuedAudio(g_devIdInp);
}
} else {

View File

@@ -11,5 +11,4 @@ target_link_libraries(${TARGET} PRIVATE
ggwave
ggwave-common
ggwave-common-sdl2
${CMAKE_THREAD_LIBS_INIT}
)

View File

@@ -158,7 +158,7 @@
var isiOS = /iPad|iPhone|iPod|CriOS/.test(navigator.userAgent) && !window.MSStream;
var isInitialized = false;
var isAudioContextUnlocked = !isiOS;
var isAudioContextUnlocked = true;
var htmlGreenLED = "<div class=\"led-green\"></div>";
var htmlRedLED = "<div class=\"led-red\"></div>";
@@ -193,7 +193,7 @@
}
}
var isBrowserSupported = !isiOS;
var isBrowserSupported = true;
{
var el = document.getElementById('is-browser-supported');
if (isBrowserSupported) {
@@ -229,7 +229,7 @@
postRun: [ (function() { document.getElementById("butInit").disabled = false; }) ],
print: (function() {
var element = document.getElementById('output');
if (element) element.alue = ''; // clear browser cache
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
@@ -293,7 +293,7 @@
Module.setStatus('Initializing...');
window.onerror = function(event) {
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus('Exception thrown: ' + JSON.stringify(event));
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
@@ -301,22 +301,22 @@
};
window.addEventListener('touchstart', function() {
if (isAudioContextUnlocked == false && SDL2.audioContext) {
var buffer = SDL2.audioContext.createBuffer(1, 1, 22050);
var source = SDL2.audioContext.createBufferSource();
source.buffer = buffer;
source.connect(SDL2.audioContext.destination);
source.start();
//if (isAudioContextUnlocked == false && SDL2.audioContext) {
// var buffer = SDL2.audioContext.createBuffer(1, 1, 22050);
// var source = SDL2.audioContext.createBufferSource();
// source.buffer = buffer;
// source.connect(SDL2.audioContext.destination);
// source.start();
setTimeout(function() {
if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {
isAudioContextUnlocked = true;
Module.setStatus('Wab Audio API unlocked successfully!');
} else {
Module.setStatus('Failed to unlock Web Audio APIi. This browser seems to not be supported');
}
}, 0);
}
// setTimeout(function() {
// if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {
// isAudioContextUnlocked = true;
// Module.setStatus('Wab Audio API unlocked successfully!');
// } else {
// Module.setStatus('Failed to unlock Web Audio APIi. This browser seems to not be supported');
// }
// }, 0);
//}
}, false);
function playSound(filename){

View File

@@ -0,0 +1,71 @@
#
# r2t2
set(TARGET r2t2)
if (NOT EMSCRIPTEN)
add_executable(${TARGET}
main.cpp
ggwave-mod/src/ggwave.cpp
ggwave-mod/src/resampler.cpp
)
target_include_directories(${TARGET} PRIVATE
..
ggwave-mod/include
ggwave-mod/src
)
target_link_libraries(${TARGET} PRIVATE
ggwave-common
)
endif()
#
# r2t2-rx
set(TARGET r2t2-rx)
if (NOT EMSCRIPTEN)
add_executable(${TARGET}
r2t2-rx.cpp
ggwave-mod/src/ggwave.cpp
ggwave-mod/src/resampler.cpp
)
target_include_directories(${TARGET} PRIVATE
..
ggwave-mod/include
ggwave-mod/src
${SDL2_INCLUDE_DIRS}
)
target_link_libraries(${TARGET} PRIVATE
ggwave-common
${SDL2_LIBRARIES}
)
else()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)
add_executable(${TARGET}
r2t2-rx.cpp
ggwave-mod/src/ggwave.cpp
ggwave-mod/src/resampler.cpp
)
target_include_directories(${TARGET} PRIVATE
..
ggwave-mod/include
ggwave-mod/src
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/
)
target_link_libraries(${TARGET} PRIVATE
ggwave-common
)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/main.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/main.js COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plucky.mp3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/plucky.mp3 COPYONLY)
endif()

58
examples/r2t2/README.md Normal file
View File

@@ -0,0 +1,58 @@
# r2t2
Transmit data with the PC speaker
<a href="https://user-images.githubusercontent.com/1991296/115141782-cba9f480-a046-11eb-9462-791477b856f5.mp4"><img width="100%" src="https://user-images.githubusercontent.com/1991296/115141739-a1583700-a046-11eb-94e7-a411d52ecf30.png"></img></a>
This is a command-line program that encodes short messages/data into audio and plays it via the motherboard's PC speaker. To use this tool, you need to attach a [piezo speaker/buzzer](https://en.wikipedia.org/wiki/Piezoelectric_speaker) to your motherboard. Some computers have such speaker already attached.
You can then run the following command:
```bash
echo test | sudo r2t2
```
This will transmit the message `test` via sound through the buzzer.
To receive the transmitted message, open the following page on your phone and place it near the speaker:
https://r2t2.ggerganov.com
## Applications
This tool can be useful when you need to transmit data from air-gapped machines. The hardware requirements are very low-cost - you only need a PC speaker. Automated scripts can be configured to periodically emit some data about the machine, which can be received by someone nearby running the `r2t2` receiver application.
## Requirements
- [PC speaker / buzzer](https://www.amazon.com/SoundOriginal-Motherboard-Internal-Speaker-Buzzer/dp/B01DM56TFY/ref=sr_1_1_sspa?dchild=1&keywords=Motherboard+Speaker&qid=1614504288&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzTkpFVlk4SzRXS1lWJmVuY3J5cHRlZElkPUEwOTU3NzI3MkpCQUZJRFIxSzZGNSZlbmNyeXB0ZWRBZElkPUEwODk0ODQ4MlVBQzFSR1RHMTYyMiZ3aWRnZXROYW1lPXNwX2F0ZiZhY3Rpb249Y2xpY2tSZWRpcmVjdCZkb05vdExvZ0NsaWNrPXRydWU=) attached to the motherboard.
Here are the ones that I use:
<p align="center">
<table border=0>
<tr>
<td>
<img width="100%" alt="Talking buttons" src="https://user-images.githubusercontent.com/1991296/115141260-ee86d980-a043-11eb-9699-587e0af53af9.jpg"></img>
</td>
<td>
<img width="100%" alt="Talking buttons" src="https://user-images.githubusercontent.com/1991296/115141261-f0509d00-a043-11eb-82cf-a89040b51f13.jpg"></img>
</td>
</tr>
</table>
</p>
<p align="center">
<i>Img. Left: PC speaker plugged into a motherboard. Right: two PC speakers with a coin for size comparison</i>
</p>
- Unix operating system
- The program requires to run as `sudo` in order to access the PC speaker
## Build
```bash
git clone https://github.com/ggerganov/ggwave --recursive
cd ggwave
mkdir build && cd build
make
./bin/r2t2
```

View File

@@ -0,0 +1 @@
static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)";

View File

@@ -0,0 +1,511 @@
#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;
// 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 will be that number of bytes
// decoded in the outputBuffer
//
// 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);
#ifdef __cplusplus
}
//
// C++ interface
//
#include <cstdint>
#include <functional>
#include <vector>
#include <map>
#include <string>
#include <memory>
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<TxProtocolId, TxProtocol>;
using RxProtocols = std::map<RxProtocolId, RxProtocol>;
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, } },
};
return kTxProtocols;
}
struct ToneData {
double freq_hz;
double duration_ms;
};
using Tones = std::vector<ToneData>;
using WaveformTones = std::vector<Tones>;
using AmplitudeData = std::vector<float>;
using AmplitudeDataI16 = std::vector<int16_t>;
using SpectrumData = std::vector<float>;
using RecordedData = std::vector<float>;
using TxRxData = std::vector<std::uint8_t>;
using CBWaveformOut = std::function<void(const void * data, uint32_t nBytes)>;
using CBWaveformInp = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
GGWave(const Parameters & parameters);
~GGWave();
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_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); }
// 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; }
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<float> m_fftInp; // real
std::vector<float> 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<AmplitudeData> m_sampleAmplitudeHistory;
RecordedData m_recordedAmplitude;
int m_historyIdFixed;
std::vector<SpectrumData> 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<Impl> m_impl;
};
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
Copyright © 2015 Mike Lubinets, github.com/mersinvald
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the “Software”), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,235 @@
/* Author: Mike Lubinets (aka mersinvald)
* Date: 29.12.15
*
* See LICENSE */
#ifndef GF_H
#define GF_H
#include "poly.hpp"
#include <stdint.h>
#include <string.h>
#include <assert.h>
namespace RS {
namespace gf {
/* GF tables pre-calculated for 0x11d primitive polynomial */
const uint8_t exp[512] = {
0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,
0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d,
0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,
0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,
0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,
0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,
0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,
0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,
0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,
0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,
0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,
0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,
0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,
0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12,
0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c,
0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2,
0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98,
0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27,
0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,
0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe,
0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7,
0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf,
0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f,
0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,
0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d,
0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1,
0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb,
0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19,
0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,
0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24,
0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58,
0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2
};
const uint8_t log[256] = {
0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4,
0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5,
0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d,
0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6,
0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36,
0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e,
0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca,
0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7,
0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3,
0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37,
0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2,
0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f,
0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c,
0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb,
0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f,
0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
};
/* ################################
* # OPERATIONS OVER GALUA FIELDS #
* ################################ */
/* @brief Addition in Galua Fields
* @param x - left operand
* @param y - right operand
* @return x + y */
inline uint8_t add(uint8_t x, uint8_t y) {
return x^y;
}
/* ##### GF substraction ###### */
/* @brief Substraction in Galua Fields
* @param x - left operand
* @param y - right operand
* @return x - y */
inline uint8_t sub(uint8_t x, uint8_t y) {
return x^y;
}
/* @brief Multiplication in Galua Fields
* @param x - left operand
* @param y - rifht operand
* @return x * y */
inline uint8_t mul(uint16_t x, uint16_t y){
if (x == 0 || y == 0)
return 0;
return exp[log[x] + log[y]];
}
/* @brief Division in Galua Fields
* @param x - dividend
* @param y - divisor
* @return x / y */
inline uint8_t div(uint8_t x, uint8_t y){
assert(y != 0);
if(x == 0) return 0;
return exp[(log[x] + 255 - log[y]) % 255];
}
/* @brief X in power Y w
* @param x - operand
* @param power - power
* @return x^power */
inline uint8_t pow(uint8_t x, intmax_t power){
intmax_t i = log[x];
i *= power;
i %= 255;
if(i < 0) i = i + 255;
return exp[i];
}
/* @brief Inversion in Galua Fields
* @param x - number
* @return inversion of x */
inline uint8_t inverse(uint8_t x){
return exp[255 - log[x]]; /* == div(1, x); */
}
/* ##########################
* # POLYNOMIALS OPERATIONS #
* ########################## */
/* @brief Multiplication polynomial by scalar
* @param &p - source polynomial
* @param &newp - destination polynomial
* @param x - scalar */
inline void
poly_scale(const Poly *p, Poly *newp, uint16_t x) {
newp->length = p->length;
for(uint16_t i = 0; i < p->length; i++){
newp->at(i) = mul(p->at(i), x);
}
}
/* @brief Addition of two polynomials
* @param &p - right operand polynomial
* @param &q - left operand polynomial
* @param &newp - destination polynomial */
inline void
poly_add(const Poly *p, const Poly *q, Poly *newp) {
newp->length = poly_max(p->length, q->length);
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
for(uint8_t i = 0; i < p->length; i++){
newp->at(i + newp->length - p->length) = p->at(i);
}
for(uint8_t i = 0; i < q->length; i++){
newp->at(i + newp->length - q->length) ^= q->at(i);
}
}
/* @brief Multiplication of two polynomials
* @param &p - right operand polynomial
* @param &q - left operand polynomial
* @param &newp - destination polynomial */
inline void
poly_mul(const Poly *p, const Poly *q, Poly *newp) {
newp->length = p->length + q->length - 1;
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
/* Compute the polynomial multiplication (just like the outer product of two vectors,
* we multiply each coefficients of p with all coefficients of q) */
for(uint8_t j = 0; j < q->length; j++){
for(uint8_t i = 0; i < p->length; i++){
newp->at(i+j) ^= mul(p->at(i), q->at(j)); /* == r[i + j] = gf_add(r[i+j], gf_mul(p[i], q[j])) */
}
}
}
/* @brief Division of two polynomials
* @param &p - right operand polynomial
* @param &q - left operand polynomial
* @param &newp - destination polynomial */
inline void
poly_div(const Poly *p, const Poly *q, Poly *newp) {
if(p->ptr() != newp->ptr()) {
memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t));
}
newp->length = p->length;
uint8_t coef;
for(int i = 0; i < (p->length-(q->length-1)); i++){
coef = newp->at(i);
if(coef != 0){
for(uint8_t j = 1; j < q->length; j++){
if(q->at(j) != 0)
newp->at(i+j) ^= mul(q->at(j), coef);
}
}
}
size_t sep = p->length-(q->length-1);
memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t));
newp->length = newp->length-sep;
}
/* @brief Evaluation of polynomial in x
* @param &p - polynomial to evaluate
* @param x - evaluation point */
inline int8_t
poly_eval(const Poly *p, uint16_t x) {
uint8_t y = p->at(0);
for(uint8_t i = 1; i < p->length; i++){
y = mul(y, x) ^ p->at(i);
}
return y;
}
} /* end of gf namespace */
}
#endif // GF_H

View File

@@ -0,0 +1,94 @@
/* Author: Mike Lubinets (aka mersinvald)
* Date: 29.12.15
*
* See LICENSE */
#ifndef POLY_H
#define POLY_H
#include <stdint.h>
#include <string.h>
#include <assert.h>
namespace RS {
struct Poly {
Poly()
: length(0), _memory(NULL) {}
Poly(uint8_t id, uint16_t offset, uint8_t size) \
: length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {}
/* @brief Append number at the end of polynomial
* @param num - number to append
* @return false if polynomial can't be stretched */
inline bool Append(uint8_t num) {
assert(length < _size);
ptr()[length++] = num;
return true;
}
/* @brief Polynomial initialization */
inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {
this->_id = id;
this->_offset = offset;
this->_size = size;
this->length = 0;
this->_memory = memory_ptr;
}
/* @brief Polynomial memory zeroing */
inline void Reset() {
memset((void*)ptr(), 0, this->_size);
}
/* @brief Copy polynomial to memory
* @param src - source byte-sequence
* @param size - size of polynomial
* @param offset - write offset */
inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {
assert(src && len <= this->_size-offset);
memcpy(ptr()+offset, src, len * sizeof(uint8_t));
length = len + offset;
}
#define poly_max(a, b) ((a > b) ? (a) : (b))
inline void Copy(const Poly* src) {
length = poly_max(length, src->length);
Set(src->ptr(), length);
}
inline uint8_t& at(uint8_t i) const {
assert(i < _size);
return ptr()[i];
}
inline uint8_t id() const {
return _id;
}
inline uint8_t size() const {
return _size;
}
// Returns pointer to memory of this polynomial
inline uint8_t* ptr() const {
assert(_memory && *_memory);
return (*_memory) + _offset;
}
uint8_t length;
protected:
uint8_t _id;
uint8_t _size; // Size of reserved memory for this polynomial
uint16_t _offset; // Offset in memory
uint8_t** _memory; // Pointer to pointer to memory
};
}
#endif // POLY_H

View File

@@ -0,0 +1,538 @@
/* Author: Mike Lubinets (aka mersinvald)
* Date: 29.12.15
*
* See LICENSE */
#ifndef RS_HPP
#define RS_HPP
#include "poly.hpp"
#include "gf.hpp"
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <vector>
namespace RS {
#define MSG_CNT 3 // message-length polynomials count
#define POLY_CNT 14 // (ecc_length*2)-length polynomialc count
class ReedSolomon {
public:
const uint8_t msg_length;
const uint8_t ecc_length;
uint8_t * generator_cache = nullptr;
bool generator_cached = false;
ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p) :
msg_length(msg_length_p), ecc_length(ecc_length_p) {
generator_cache = new uint8_t[ecc_length + 1];
const uint8_t enc_len = msg_length + ecc_length;
const uint8_t poly_len = ecc_length * 2;
uint8_t** memptr = &memory;
uint16_t offset = 0;
/* Initialize first six polys manually cause their amount depends on template parameters */
polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);
offset += enc_len;
polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);
offset += enc_len;
for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {
polynoms[i].Init(i, offset, poly_len, memptr);
offset += poly_len;
}
polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);
offset += enc_len;
for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {
polynoms[i].Init(i, offset, poly_len, memptr);
offset += poly_len;
}
}
~ReedSolomon() {
delete [] generator_cache;
// Dummy destructor, gcc-generated one crashes programm
memory = NULL;
}
/* @brief Message block encoding
* @param *src - input message buffer (msg_lenth size)
* @param *dst - output buffer for ecc (ecc_length size at least) */
void EncodeBlock(const void* src, void* dst) {
assert(msg_length + ecc_length < 256);
///* Allocating memory on stack for polynomials storage */
//uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
//this->memory = stack_memory;
// gg : allocation is now on the heap
std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);
this->memory = stack_memory.data();
const uint8_t* src_ptr = (const uint8_t*) src;
uint8_t* dst_ptr = (uint8_t*) dst;
Poly *msg_in = &polynoms[ID_MSG_IN];
Poly *msg_out = &polynoms[ID_MSG_OUT];
Poly *gen = &polynoms[ID_GENERATOR];
// Weird shit, but without reseting msg_in it simply doesn't work
msg_in->Reset();
msg_out->Reset();
// Using cached generator or generating new one
if(generator_cached) {
gen->Set(generator_cache, ecc_length + 1);
} else {
GeneratorPoly();
memcpy(generator_cache, gen->ptr(), gen->length);
generator_cached = true;
}
// Copying input message to internal polynomial
msg_in->Set(src_ptr, msg_length);
msg_out->Set(src_ptr, msg_length);
msg_out->length = msg_in->length + ecc_length;
// Here all the magic happens
uint8_t coef = 0; // cache
for(uint8_t i = 0; i < msg_length; i++){
coef = msg_out->at(i);
if(coef != 0){
for(uint32_t j = 1; j < gen->length; j++){
msg_out->at(i+j) ^= gf::mul(gen->at(j), coef);
}
}
}
// Copying ECC to the output buffer
memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t));
}
/* @brief Message encoding
* @param *src - input message buffer (msg_lenth size)
* @param *dst - output buffer (msg_length + ecc_length size at least) */
void Encode(const void* src, void* dst) {
uint8_t* dst_ptr = (uint8_t*) dst;
// Copying message to the output buffer
memcpy(dst_ptr, src, msg_length * sizeof(uint8_t));
// Calling EncodeBlock to write ecc to out[ut buffer
EncodeBlock(src, dst_ptr+msg_length);
}
/* @brief Message block decoding
* @param *src - encoded message buffer (msg_length size)
* @param *ecc - ecc buffer (ecc_length size)
* @param *msg_out - output buffer (msg_length size at least)
* @param *erase_pos - known errors positions
* @param erase_count - count of known errors
* @return RESULT_SUCCESS if successfull, error code otherwise */
int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
assert(msg_length + ecc_length < 256);
const uint8_t *src_ptr = (const uint8_t*) src;
const uint8_t *ecc_ptr = (const uint8_t*) ecc;
uint8_t *dst_ptr = (uint8_t*) dst;
const uint8_t src_len = msg_length + ecc_length;
const uint8_t dst_len = msg_length;
bool ok;
///* Allocation memory on stack */
//uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
//this->memory = stack_memory;
// gg : allocation is now on the heap
std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);
this->memory = stack_memory.data();
Poly *msg_in = &polynoms[ID_MSG_IN];
Poly *msg_out = &polynoms[ID_MSG_OUT];
Poly *epos = &polynoms[ID_ERASURES];
// Copying message to polynomials memory
msg_in->Set(src_ptr, msg_length);
msg_in->Set(ecc_ptr, ecc_length, msg_length);
msg_out->Copy(msg_in);
// Copying known errors to polynomial
if(erase_pos == NULL) {
epos->length = 0;
} else {
epos->Set(erase_pos, erase_count);
for(uint8_t i = 0; i < epos->length; i++){
msg_in->at(epos->at(i)) = 0;
}
}
// Too many errors
if(epos->length > ecc_length) return 1;
Poly *synd = &polynoms[ID_SYNDROMES];
Poly *eloc = &polynoms[ID_ERRORS_LOC];
Poly *reloc = &polynoms[ID_TPOLY1];
Poly *err = &polynoms[ID_ERRORS];
Poly *forney = &polynoms[ID_FORNEY];
// Calculating syndrome
CalcSyndromes(msg_in);
// Checking for errors
bool has_errors = false;
for(uint8_t i = 0; i < synd->length; i++) {
if(synd->at(i) != 0) {
has_errors = true;
break;
}
}
// Going to exit if no errors
if(!has_errors) goto return_corrected_msg;
CalcForneySyndromes(synd, epos, src_len);
FindErrorLocator(forney, NULL, epos->length);
// Reversing syndrome
// TODO optimize through special Poly flag
reloc->length = eloc->length;
for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){
reloc->at(j) = eloc->at(i);
}
// Fing errors
ok = FindErrors(reloc, src_len);
if(!ok) return 1;
// Error happened while finding errors (so helpfull :D)
if(err->length == 0) return 1;
/* Adding found errors with known */
for(uint8_t i = 0; i < err->length; i++) {
epos->Append(err->at(i));
}
// Correcting errors
CorrectErrata(synd, epos, msg_in);
return_corrected_msg:
// Wrighting corrected message to output buffer
msg_out->length = dst_len;
memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t));
return 0;
}
/* @brief Message block decoding
* @param *src - encoded message buffer (msg_length + ecc_length size)
* @param *msg_out - output buffer (msg_length size at least)
* @param *erase_pos - known errors positions
* @param erase_count - count of known errors
* @return RESULT_SUCCESS if successfull, error code otherwise */
int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
const uint8_t *src_ptr = (const uint8_t*) src;
const uint8_t *ecc_ptr = src_ptr + msg_length;
return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count);
}
#ifndef DEBUG
private:
#endif
enum POLY_ID {
ID_MSG_IN = 0,
ID_MSG_OUT,
ID_GENERATOR, // 3
ID_TPOLY1, // T for Temporary
ID_TPOLY2,
ID_MSG_E, // 5
ID_TPOLY3, // 6
ID_TPOLY4,
ID_SYNDROMES,
ID_FORNEY,
ID_ERASURES_LOC,
ID_ERRORS_LOC,
ID_ERASURES,
ID_ERRORS,
ID_COEF_POS,
ID_ERR_EVAL
};
// Pointer for polynomials memory on stack
uint8_t* memory;
Poly polynoms[MSG_CNT + POLY_CNT];
void GeneratorPoly() {
Poly *gen = polynoms + ID_GENERATOR;
gen->at(0) = 1;
gen->length = 1;
Poly *mulp = polynoms + ID_TPOLY1;
Poly *temp = polynoms + ID_TPOLY2;
mulp->length = 2;
for(int8_t i = 0; i < ecc_length; i++){
mulp->at(0) = 1;
mulp->at(1) = gf::pow(2, i);
gf::poly_mul(gen, mulp, temp);
gen->Copy(temp);
}
}
void CalcSyndromes(const Poly *msg) {
Poly *synd = &polynoms[ID_SYNDROMES];
synd->length = ecc_length+1;
synd->at(0) = 0;
for(uint8_t i = 1; i < ecc_length+1; i++){
synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1));
}
}
void FindErrataLocator(const Poly *epos) {
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
Poly *mulp = &polynoms[ID_TPOLY1];
Poly *addp = &polynoms[ID_TPOLY2];
Poly *apol = &polynoms[ID_TPOLY3];
Poly *temp = &polynoms[ID_TPOLY4];
errata_loc->length = 1;
errata_loc->at(0) = 1;
mulp->length = 1;
addp->length = 2;
for(uint8_t i = 0; i < epos->length; i++){
mulp->at(0) = 1;
addp->at(0) = gf::pow(2, epos->at(i));
addp->at(1) = 0;
gf::poly_add(mulp, addp, apol);
gf::poly_mul(errata_loc, apol, temp);
errata_loc->Copy(temp);
}
}
void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) {
Poly *mulp = &polynoms[ID_TPOLY1];
gf::poly_mul(synd, errata_loc, mulp);
Poly *divisor = &polynoms[ID_TPOLY2];
divisor->length = ecclen+2;
divisor->Reset();
divisor->at(0) = 1;
gf::poly_div(mulp, divisor, dst);
}
void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) {
Poly *c_pos = &polynoms[ID_COEF_POS];
Poly *corrected = &polynoms[ID_MSG_OUT];
c_pos->length = err_pos->length;
for(uint8_t i = 0; i < err_pos->length; i++)
c_pos->at(i) = msg_in->length - 1 - err_pos->at(i);
/* uses t_poly 1, 2, 3, 4 */
FindErrataLocator(c_pos);
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
/* reversing syndromes */
Poly *rsynd = &polynoms[ID_TPOLY3];
rsynd->length = synd->length;
for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) {
rsynd->at(j) = synd->at(i);
}
/* getting reversed error evaluator polynomial */
Poly *re_eval = &polynoms[ID_TPOLY4];
/* uses T_POLY 1, 2 */
FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1);
/* reversing it back */
Poly *e_eval = &polynoms[ID_ERR_EVAL];
e_eval->length = re_eval->length;
for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) {
e_eval->at(j) = re_eval->at(i);
}
Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */
X->length = 0;
int16_t l;
for(uint8_t i = 0; i < c_pos->length; i++){
l = 255 - c_pos->at(i);
X->Append(gf::pow(2, -l));
}
/* Magnitude polynomial
Shit just got real */
Poly *E = &polynoms[ID_MSG_E];
E->Reset();
E->length = msg_in->length;
uint8_t Xi_inv;
Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2];
uint8_t err_loc_prime;
uint8_t y;
for(uint8_t i = 0; i < X->length; i++){
Xi_inv = gf::inverse(X->at(i));
err_loc_prime_temp->length = 0;
for(uint8_t j = 0; j < X->length; j++){
if(j != i){
err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j))));
}
}
err_loc_prime = 1;
for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){
err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j));
}
y = gf::poly_eval(re_eval, Xi_inv);
y = gf::mul(gf::pow(X->at(i), 1), y);
E->at(err_pos->at(i)) = gf::div(y, err_loc_prime);
}
gf::poly_add(msg_in, E, corrected);
}
bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) {
Poly *error_loc = &polynoms[ID_ERRORS_LOC];
Poly *err_loc = &polynoms[ID_TPOLY1];
Poly *old_loc = &polynoms[ID_TPOLY2];
Poly *temp = &polynoms[ID_TPOLY3];
Poly *temp2 = &polynoms[ID_TPOLY4];
if(erase_loc != NULL) {
err_loc->Copy(erase_loc);
old_loc->Copy(erase_loc);
} else {
err_loc->length = 1;
old_loc->length = 1;
err_loc->at(0) = 1;
old_loc->at(0) = 1;
}
uint8_t synd_shift = 0;
if(synd->length > ecc_length) {
synd_shift = synd->length - ecc_length;
}
uint8_t K = 0;
uint8_t delta = 0;
uint8_t index;
for(uint8_t i = 0; i < ecc_length - erase_count; i++){
if(erase_loc != NULL)
K = erase_count + i + synd_shift;
else
K = i + synd_shift;
delta = synd->at(K);
for(uint8_t j = 1; j < err_loc->length; j++) {
index = err_loc->length - j - 1;
delta ^= gf::mul(err_loc->at(index), synd->at(K-j));
}
old_loc->Append(0);
if(delta != 0) {
if(old_loc->length > err_loc->length) {
gf::poly_scale(old_loc, temp, delta);
gf::poly_scale(err_loc, old_loc, gf::inverse(delta));
err_loc->Copy(temp);
}
gf::poly_scale(old_loc, temp, delta);
gf::poly_add(err_loc, temp, temp2);
err_loc->Copy(temp2);
}
}
uint32_t shift = 0;
while(err_loc->length && err_loc->at(shift) == 0) shift++;
uint32_t errs = err_loc->length - shift - 1;
if(((errs - erase_count) * 2 + erase_count) > ecc_length){
return false; /* Error count is greater then we can fix! */
}
memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t));
error_loc->length = (err_loc->length - shift);
return true;
}
bool FindErrors(const Poly *error_loc, size_t msg_in_size) {
Poly *err = &polynoms[ID_ERRORS];
uint8_t errs = error_loc->length - 1;
err->length = 0;
for(uint8_t i = 0; i < msg_in_size; i++) {
if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) {
err->Append(msg_in_size - 1 - i);
}
}
/* Sanity check:
* the number of err/errata positions found
* should be exactly the same as the length of the errata locator polynomial */
if(err->length != errs)
/* couldn't find error locations */
return false;
return true;
}
void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) {
Poly *erase_pos_reversed = &polynoms[ID_TPOLY1];
Poly *forney_synd = &polynoms[ID_FORNEY];
erase_pos_reversed->length = 0;
for(uint8_t i = 0; i < erasures_pos->length; i++){
erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i));
}
forney_synd->Reset();
forney_synd->Set(synd->ptr()+1, synd->length-1);
uint8_t x;
for(uint8_t i = 0; i < erasures_pos->length; i++) {
x = gf::pow(2, erase_pos_reversed->at(i));
for(int8_t j = 0; j < forney_synd->length - 1; j++){
forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1);
}
}
}
};
}
#endif // RS_HPP

View File

@@ -0,0 +1,157 @@
#include "resampler.h"
#include <cassert>
#include <cmath>
#include <cstdio>
namespace {
double linear_interp(double first_number, double second_number, double fraction) {
return (first_number + ((second_number - first_number)*fraction));
}
}
Resampler::Resampler() :
m_sincTable(kWidth*kSamplesPerZeroCrossing),
m_delayBuffer(3*kWidth),
m_edgeSamples(kWidth),
m_samplesInp(2048) {
make_sinc();
reset();
}
void Resampler::reset() {
m_state = {};
std::fill(m_edgeSamples.begin(), m_edgeSamples.end(), 0.0f);
std::fill(m_delayBuffer.begin(), m_delayBuffer.end(), 0.0f);
std::fill(m_samplesInp.begin(), m_samplesInp.end(), 0.0f);
}
int Resampler::resample(
float factor,
int nSamples,
const float * samplesInp,
float * samplesOut) {
int idxInp = -1;
int idxOut = 0;
int notDone = 1;
float data_in = 0.0f;
float data_out = 0.0f;
double one_over_factor = 1.0;
auto stateSave = m_state;
m_state.nSamplesTotal += nSamples;
if (samplesOut) {
assert(nSamples > kWidth);
if ((int) m_samplesInp.size() < nSamples + kWidth) {
m_samplesInp.resize(nSamples + kWidth);
}
for (int i = 0; i < kWidth; ++i) {
m_samplesInp[i] = m_edgeSamples[i];
m_edgeSamples[i] = samplesInp[nSamples - kWidth + i];
}
for (int i = 0; i < nSamples; ++i) {
m_samplesInp[i + kWidth] = samplesInp[i];
}
samplesInp = m_samplesInp.data();
}
while (notDone) {
while (m_state.timeLast < m_state.timeInt) {
if (++idxInp >= nSamples) {
notDone = 0;
break;
} else {
data_in = samplesInp[idxInp];
}
//printf("xxxx idxInp = %d\n", idxInp);
if (samplesOut) new_data(data_in);
m_state.timeLast += 1;
}
if (notDone == false) break;
double temp1 = 0.0;
int left_limit = m_state.timeNow - kWidth + 1; /* leftmost neighboring sample used for interp.*/
int right_limit = m_state.timeNow + kWidth; /* rightmost leftmost neighboring sample used for interp.*/
if (left_limit < 0) left_limit = 0;
if (right_limit > m_state.nSamplesTotal + kWidth) right_limit = m_state.nSamplesTotal + kWidth;
if (factor < 1.0) {
for (int j = left_limit; j < right_limit; j++) {
temp1 += gimme_data(j - m_state.timeInt)*sinc(m_state.timeNow - (double) j);
}
data_out = temp1;
}
else {
one_over_factor = 1.0 / factor;
for (int j = left_limit; j < right_limit; j++) {
temp1 += gimme_data(j - m_state.timeInt)*one_over_factor*sinc(one_over_factor*(m_state.timeNow - (double) j));
}
data_out = temp1;
}
if (samplesOut) {
//printf("inp = %d, l = %d, r = %d, n = %d, a = %d, b = %d\n", idxInp, left_limit, right_limit, m_state.nSamplesTotal, left_limit - m_state.timeInt, right_limit - m_state.timeInt - 1);
samplesOut[idxOut] = data_out;
}
++idxOut;
m_state.timeNow += factor;
m_state.timeLast = m_state.timeInt;
m_state.timeInt = m_state.timeNow;
while (m_state.timeLast < m_state.timeInt) {
if (++idxInp >= nSamples) {
notDone = 0;
break;
} else {
data_in = samplesInp[idxInp];
}
if (samplesOut) new_data(data_in);
m_state.timeLast += 1;
}
//printf("last idxInp = %d, nSamples = %d\n", idxInp, nSamples);
}
if (samplesOut == nullptr) {
m_state = stateSave;
}
return idxOut;
}
float Resampler::gimme_data(int j) const {
return m_delayBuffer[(int) j + kWidth];
}
void Resampler::new_data(float data) {
for (int i = 0; i < kDelaySize - 5; i++) {
m_delayBuffer[i] = m_delayBuffer[i + 1];
}
m_delayBuffer[kDelaySize - 5] = data;
}
void Resampler::make_sinc() {
double temp, win_freq, win;
win_freq = M_PI/kWidth/kSamplesPerZeroCrossing;
m_sincTable[0] = 1.0;
for (int i = 1; i < kWidth*kSamplesPerZeroCrossing; i++) {
temp = (double) i*M_PI/kSamplesPerZeroCrossing;
m_sincTable[i] = sin(temp)/temp;
win = 0.5 + 0.5*cos(win_freq*i);
m_sincTable[i] *= win;
}
}
double Resampler::sinc(double x) const {
int low;
double temp, delta;
if (fabs(x) >= kWidth - 1) {
return 0.0;
} else {
temp = fabs(x)*(double) kSamplesPerZeroCrossing;
low = temp; /* these are interpolation steps */
delta = temp - low; /* and can be ommited if desired */
return linear_interp(m_sincTable[low], m_sincTable[low + 1], delta);
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <vector>
#include <cstdint>
class Resampler {
public:
// this controls the number of neighboring samples
// which are used to interpolate the new samples. The
// processing time is linearly related to this width
static const int kWidth = 64;
Resampler();
void reset();
int nSamplesTotal() const { return m_state.nSamplesTotal; }
int resample(
float factor,
int nSamples,
const float * samplesInp,
float * samplesOut);
private:
float gimme_data(int j) const;
void new_data(float data);
void make_sinc();
double sinc(double x) const;
static const int kDelaySize = 140;
// this defines how finely the sinc function is sampled for storage in the table
static const int kSamplesPerZeroCrossing = 32;
std::vector<float> m_sincTable;
std::vector<float> m_delayBuffer;
std::vector<float> m_edgeSamples;
std::vector<float> m_samplesInp;
struct State {
int nSamplesTotal = 0;
int timeInt = 0;
int timeLast = 0;
double timeNow = 0.0;
};
State m_state;
};

View File

@@ -0,0 +1,174 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>r2t2</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="main-container">
<h1>r2t2</h1>
Press the Init button and place the microphone near the PC speaker to receive messages
<br><br>
<button onClick="doInit()" id="butInit" disabled>Init</button>
<div id="sound"></div>
<br><hr>
<p>Standard output:</p>
<textarea id="output" rows="8"></textarea>
<div class="spinner" id='spinnerEm'></div>
<div class="emscripten" id="statusEm">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progressEm" hidden=1></progress>
</div>
</div>
<div class="cell-version">
<span>
|
Build time: <span class="nav-link">@GIT_DATE@</span> |
Commit hash: <a class="nav-link" href="https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@">@GIT_SHA1@</a> |
Commit subject: <span class="nav-link">@GIT_COMMIT_SUBJECT@</span> |
</span>
</div>
<div class="cell-about">
<a class="nav-link" href="https://github.com/ggerganov/ggwave/tree/master/examples/r2t2"><span class="d-none d-sm-inline">View on GitHub </span>
<svg version="1.1" width="16" height="16" viewBox="0 0 16 16" class="octicon octicon-mark-github" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
</a>
</div>
<script type='text/javascript'>
var isInitialized = false;
var isAudioContextUnlocked = false;
var statusElement = document.getElementById('statusEm');
var progressElement = document.getElementById('progressEm');
var spinnerElement = document.getElementById('spinnerEm');
var Module = {
doNotCaptureKeyboard: true,
pre: [],
preRun: [(function() {
let constraints = {
audio: {
echoCancellation: false,
autoGainControl: false,
noiseSuppression: false
}
};
let mediaInput = navigator.mediaDevices.getUserMedia( constraints );
}) ],
postRun: [ (function() { document.getElementById("butInit").disabled = false; }) ],
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
printErr: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.style.display = 'none';
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
function doInit() {
if (isInitialized == false) {
Module._doInit();
isInitialized = true;
}
playSound("plucky");
}
Module.setStatus('Initializing...');
window.onerror = function(event) {
Module.setStatus('Exception thrown: ' + JSON.stringify(event));
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
window.addEventListener('touchstart', function() {
//if (isAudioContextUnlocked == false && SDL2.audioContext) {
// var buffer = SDL2.audioContext.createBuffer(1, 1, 22050);
// var source = SDL2.audioContext.createBufferSource();
// source.buffer = buffer;
// source.connect(SDL2.audioContext.destination);
// source.start();
// setTimeout(function() {
// if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {
// isAudioContextUnlocked = true;
// Module.setStatus('Wab Audio API unlocked successfully!');
// } else {
// Module.setStatus('Failed to unlock Web Audio APIi. This browser seems to not be supported');
// }
// }, 0);
//}
}, false);
function playSound(filename){
document.getElementById("sound").innerHTML='<audio id="soundInner"><source src="' + filename + '.mp3" type="audio/mpeg" /><embed hidden="true" autostart="true" loop="false" src="' + filename +'.mp3" /></audio>';
document.getElementById("soundInner").volume = 0.1;
document.getElementById("soundInner").play();
}
</script>
<script async type="text/javascript" src="@TARGET@.js"></script>
<script type="text/javascript" src="main.js"></script>
</body>
</html>

136
examples/r2t2/main.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include "ggwave/ggwave.h"
#include "ggwave-common.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/kd.h>
#define CONSOLE "/dev/tty0"
#include <cmath>
#include <cstdio>
#include <string>
#include <iostream>
void processTone(int fd, double freq_hz, long duration_ms, bool useBeep, bool printTones) {
if (printTones) {
printf("TONE %8.2f Hz %5ld ms\n", freq_hz, duration_ms);
return;
}
if (useBeep) {
static char cmd[128];
snprintf(cmd, 128, "beep -f %g -l %ld", freq_hz, duration_ms);
system(cmd);
return;
}
long pitch = std::round(1193180.0/freq_hz);
long ms = std::round(duration_ms);
ioctl(fd, KDMKTONE, (ms<<16)|pitch);
usleep(ms*1000);
}
int main(int argc, char** argv) {
printf("Usage: %s [-tN] [-lN]\n", argv[0]);
printf(" -p - print tones, no playback\n");
//printf(" -b - use 'beep' command\n");
printf(" -tN - transmission protocol\n");
printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed);
printf("\n");
const GGWave::TxProtocols protocols = {
{ GGWAVE_TX_PROTOCOL_CUSTOM_0, { "[R2T2] Normal", 64, 9, 1, } },
{ GGWAVE_TX_PROTOCOL_CUSTOM_1, { "[R2T2] Fast", 64, 6, 1, } },
{ GGWAVE_TX_PROTOCOL_CUSTOM_2, { "[R2T2] Fastest", 64, 3, 1, } },
};
auto argm = parseCmdArguments(argc, argv);
bool printTones = argm.find("p") != argm.end();
bool useBeep = false; //argm.find("b") != argm.end();
int txProtocolId = argm["t"].empty() ? GGWAVE_TX_PROTOCOL_CUSTOM_0 : std::stoi(argm["t"]);
int payloadLength = argm["l"].empty() ? 16 : std::stoi(argm["l"]);
GGWave ggWave({
payloadLength,
GGWave::kBaseSampleRate,
GGWave::kBaseSampleRate,
GGWave::kDefaultSamplesPerFrame,
GGWave::kDefaultSoundMarkerThreshold,
GGWAVE_SAMPLE_FORMAT_F32,
GGWAVE_SAMPLE_FORMAT_F32});
printf("Available Tx protocols:\n");
for (const auto & protocol : protocols) {
printf(" -t%d : %s\n", protocol.first, protocol.second.name);
}
if (protocols.find(GGWave::TxProtocolId(txProtocolId)) == protocols.end()) {
fprintf(stderr, "Unknown Tx protocol %d\n", txProtocolId);
return -3;
}
printf("Selecting Tx protocol %d\n", txProtocolId);
int fd = 1;
if (useBeep == false && printTones == false) {
if (ioctl(fd, KDMKTONE, 0)) {
fd = open(CONSOLE, O_RDONLY);
}
if (fd < 0) {
perror(CONSOLE);
fprintf(stderr, "This program must be run as root\n");
return 1;
}
}
fprintf(stderr, "Enter a text message:\n");
std::string message;
std::getline(std::cin, message);
printf("\n");
if (message.size() == 0) {
fprintf(stderr, "Invalid message: size = 0\n");
return -2;
}
if ((int) message.size() > payloadLength) {
fprintf(stderr, "Invalid message: size > %d\n", payloadLength);
return -3;
}
ggWave.init(message.size(), message.data(), protocols.at(GGWave::TxProtocolId(txProtocolId)), 10);
GGWave::CBWaveformOut tmp = [](const void * , uint32_t ){};
ggWave.encode(tmp);
int nFrames = 0;
double lastF = -1.0f;
auto tones = ggWave.getWaveformTones();
for (auto & tonesCur : tones) {
if (tonesCur.size() == 0) continue;
const auto & tone = tonesCur.front();
if (tone.freq_hz != lastF) {
if (nFrames > 0) {
processTone(fd, lastF, nFrames*tone.duration_ms, useBeep, printTones);
}
nFrames = 0;
lastF = tone.freq_hz;
}
++nFrames;
}
if (nFrames > 0) {
const auto & tone = tones.front().front();
processTone(fd, lastF, nFrames*tone.duration_ms, useBeep, printTones);
}
return 0;
}

55
examples/r2t2/main.js Normal file
View File

@@ -0,0 +1,55 @@
function transmitText(sText) {
var r = new Uint8Array(256);
for (var i = 0; i < sText.length; ++i) {
r[i] = sText.charCodeAt(i);
}
var buffer = Module._malloc(256);
Module.writeArrayToMemory(r, buffer, 256);
Module._sendData(sText.length, buffer, protocolId, volume);
Module._free(buffer);
}
var firstTimeFail = false;
var peerInfo = document.querySelector('a#peer-info');
function updatePeerInfo() {
if (typeof Module === 'undefined') return;
var framesLeftToRecord = Module._getFramesLeftToRecord();
var framesToRecord = Module._getFramesToRecord();
var framesLeftToAnalyze = Module._getFramesLeftToAnalyze();
var framesToAnalyze = Module._getFramesToAnalyze();
if (framesToAnalyze > 0) {
peerInfo.innerHTML=
"Analyzing Rx data: <progress value=" + (framesToAnalyze - framesLeftToAnalyze) +
" max=" + (framesToRecord) + "></progress>";
peerReceive.innerHTML= "";
} else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) {
firstTimeFail = true;
peerInfo.innerHTML=
"Transmission in progress: <progress value=" + (framesToRecord - framesLeftToRecord) +
" max=" + (framesToRecord) + "></progress>";
} else if (framesToRecord > 0) {
peerInfo.innerHTML= "Analyzing Rx data ...";
} else if (framesToRecord == 0) {
peerInfo.innerHTML= "<p>Listening for waves ...</p>";
} else if (framesToRecord == -1) {
if (firstTimeFail) {
playSound("/media/case-closed");
firstTimeFail = false;
}
peerInfo.innerHTML= "<p style=\"color:red\">Failed to decode Rx data</p>";
}
}
function updateRx() {
if (typeof Module === 'undefined') return;
Module._getText(bufferRx);
var result = "";
for (var i = 0; i < 140; ++i){
result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i]));
brx[i] = (Module.HEAPU8)[bufferRx + i];
}
document.getElementById('rxData').innerHTML = result;
}

BIN
examples/r2t2/plucky.mp3 Normal file

Binary file not shown.

369
examples/r2t2/r2t2-rx.cpp Normal file
View File

@@ -0,0 +1,369 @@
#include "ggwave-common.h"
#include "ggwave/ggwave.h"
#ifdef __EMSCRIPTEN__
#include "build_timestamp.h"
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif
#include <SDL.h>
#include <SDL_opengl.h>
#include <chrono>
#include <string>
#include <thread>
namespace {
std::string g_defaultCaptureDeviceName = "";
SDL_AudioDeviceID g_devIdInp = 0;
SDL_AudioDeviceID g_devIdOut = 0;
SDL_AudioSpec g_obtainedSpecInp;
SDL_AudioSpec g_obtainedSpecOut;
GGWave *g_ggWave = nullptr;
}
static std::function<bool()> g_doInit;
static std::function<void(int, int)> g_setWindowSize;
static std::function<bool()> g_mainUpdate;
void mainUpdate(void *) {
g_mainUpdate();
}
// JS interface
extern "C" {
EMSCRIPTEN_KEEPALIVE
int sendData(int textLength, const char * text, int protocolId, int volume) {
g_ggWave->init(textLength, text, g_ggWave->getTxProtocol(protocolId), volume);
return 0;
}
EMSCRIPTEN_KEEPALIVE
int getText(char * text) {
std::copy(g_ggWave->getRxData().begin(), g_ggWave->getRxData().end(), text);
return 0;
}
EMSCRIPTEN_KEEPALIVE
float getSampleRate() { return g_ggWave->getSampleRateInp(); }
EMSCRIPTEN_KEEPALIVE
int getFramesToRecord() { return g_ggWave->getFramesToRecord(); }
EMSCRIPTEN_KEEPALIVE
int getFramesLeftToRecord() { return g_ggWave->getFramesLeftToRecord(); }
EMSCRIPTEN_KEEPALIVE
int getFramesToAnalyze() { return g_ggWave->getFramesToAnalyze(); }
EMSCRIPTEN_KEEPALIVE
int getFramesLeftToAnalyze() { return g_ggWave->getFramesLeftToAnalyze(); }
EMSCRIPTEN_KEEPALIVE
int hasDeviceOutput() { return g_devIdOut; }
EMSCRIPTEN_KEEPALIVE
int hasDeviceCapture() { return g_devIdInp; }
EMSCRIPTEN_KEEPALIVE
int doInit() {
return g_doInit();
}
}
void GGWave_setDefaultCaptureDeviceName(std::string name) {
g_defaultCaptureDeviceName = std::move(name);
}
bool GGWave_init(
const int playbackId,
const int captureId,
const int payloadLength,
const float sampleRateOffset) {
if (g_devIdInp && g_devIdOut) {
return false;
}
if (g_devIdInp == 0 && g_devIdOut == 0) {
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
return (1);
}
SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, "medium", SDL_HINT_OVERRIDE);
{
int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);
printf("Found %d playback devices:\n", nDevices);
for (int i = 0; i < nDevices; i++) {
printf(" - Playback device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_FALSE));
}
}
{
int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);
printf("Found %d capture devices:\n", nDevices);
for (int i = 0; i < nDevices; i++) {
printf(" - Capture device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_TRUE));
}
}
}
bool reinit = false;
if (g_devIdOut == 0) {
printf("Initializing playback ...\n");
SDL_AudioSpec playbackSpec;
SDL_zero(playbackSpec);
playbackSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
playbackSpec.format = AUDIO_S16SYS;
playbackSpec.channels = 1;
playbackSpec.samples = 16*1024;
playbackSpec.callback = NULL;
SDL_zero(g_obtainedSpecOut);
if (playbackId >= 0) {
printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));
g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);
} else {
printf("Attempt to open default playback device ...\n");
g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);
}
if (!g_devIdOut) {
printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError());
g_devIdOut = 0;
} else {
printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut);
printf(" - Sample rate: %d (required: %d)\n", g_obtainedSpecOut.freq, playbackSpec.freq);
printf(" - Format: %d (required: %d)\n", g_obtainedSpecOut.format, playbackSpec.format);
printf(" - Channels: %d (required: %d)\n", g_obtainedSpecOut.channels, playbackSpec.channels);
printf(" - Samples per frame: %d (required: %d)\n", g_obtainedSpecOut.samples, playbackSpec.samples);
if (g_obtainedSpecOut.format != playbackSpec.format ||
g_obtainedSpecOut.channels != playbackSpec.channels ||
g_obtainedSpecOut.samples != playbackSpec.samples) {
g_devIdOut = 0;
SDL_CloseAudio();
fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!");
return false;
}
reinit = true;
}
}
if (g_devIdInp == 0) {
SDL_AudioSpec captureSpec;
captureSpec = g_obtainedSpecOut;
captureSpec.freq = GGWave::kBaseSampleRate + sampleRateOffset;
captureSpec.format = AUDIO_F32SYS;
captureSpec.samples = 1024;
SDL_zero(g_obtainedSpecInp);
if (captureId >= 0) {
printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE));
g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);
} else {
printf("Attempt to open default capture device ...\n");
g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(),
SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);
}
if (!g_devIdInp) {
printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError());
g_devIdInp = 0;
} else {
printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdInp);
printf(" - Sample rate: %d\n", g_obtainedSpecInp.freq);
printf(" - Format: %d (required: %d)\n", g_obtainedSpecInp.format, captureSpec.format);
printf(" - Channels: %d (required: %d)\n", g_obtainedSpecInp.channels, captureSpec.channels);
printf(" - Samples per frame: %d\n", g_obtainedSpecInp.samples);
reinit = true;
}
}
GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
switch (g_obtainedSpecInp.format) {
case AUDIO_U8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8; break;
case AUDIO_S8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8; break;
case AUDIO_U16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break;
case AUDIO_S16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break;
case AUDIO_S32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;
case AUDIO_F32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;
}
switch (g_obtainedSpecOut.format) {
case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break;
case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break;
case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;
case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;
case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
break;
}
if (reinit) {
if (g_ggWave) delete g_ggWave;
g_ggWave = new GGWave({
payloadLength,
(float) g_obtainedSpecInp.freq,
(float) g_obtainedSpecOut.freq,
GGWave::kDefaultSamplesPerFrame,
GGWave::kDefaultSoundMarkerThreshold,
sampleFormatInp,
sampleFormatOut});
}
return true;
}
GGWave *& GGWave_instance() { return g_ggWave; }
bool GGWave_mainLoop() {
if (g_devIdInp == 0 && g_devIdOut == 0) {
return false;
}
static GGWave::CBWaveformOut cbWaveformOut = [&](const void * data, uint32_t nBytes) {
SDL_QueueAudio(g_devIdOut, data, nBytes);
};
static GGWave::CBWaveformInp cbWaveformInp = [&](void * data, uint32_t nMaxBytes) {
return SDL_DequeueAudio(g_devIdInp, data, nMaxBytes);
};
if (g_ggWave->hasTxData() == false) {
SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);
static auto tLastNoData = std::chrono::high_resolution_clock::now();
auto tNow = std::chrono::high_resolution_clock::now();
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE);
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
g_ggWave->decode(cbWaveformInp);
if ((int) SDL_GetQueuedAudioSize(g_devIdInp) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesInp()) {
fprintf(stderr, "Warning: slow processing, clearing queued audio buffer of %d bytes ...", SDL_GetQueuedAudioSize(g_devIdInp));
SDL_ClearQueuedAudio(g_devIdInp);
}
} else {
SDL_ClearQueuedAudio(g_devIdInp);
}
} else {
tLastNoData = tNow;
}
} else {
SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);
SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE);
g_ggWave->encode(cbWaveformOut);
}
return true;
}
bool GGWave_deinit() {
if (g_devIdInp == 0 && g_devIdOut == 0) {
return false;
}
delete g_ggWave;
g_ggWave = nullptr;
SDL_PauseAudioDevice(g_devIdInp, 1);
SDL_CloseAudioDevice(g_devIdInp);
SDL_PauseAudioDevice(g_devIdOut, 1);
SDL_CloseAudioDevice(g_devIdOut);
g_devIdInp = 0;
g_devIdOut = 0;
return true;
}
int main(int argc, char** argv) {
#ifdef __EMSCRIPTEN__
printf("Build time: %s\n", BUILD_TIMESTAMP);
printf("Press the Init button to start\n");
if (argv[1]) {
GGWave_setDefaultCaptureDeviceName(argv[1]);
}
#endif
const GGWave::TxProtocols protocols = {
{ GGWAVE_TX_PROTOCOL_CUSTOM_0, { "[R2T2] Normal", 64, 9, 1, } },
{ GGWAVE_TX_PROTOCOL_CUSTOM_1, { "[R2T2] Fast", 64, 6, 1, } },
{ GGWAVE_TX_PROTOCOL_CUSTOM_2, { "[R2T2] Fastest", 64, 3, 1, } },
};
auto argm = parseCmdArguments(argc, argv);
int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]);
bool isInitialized = false;
g_doInit = [&]() {
if (GGWave_init(0, captureId, 16, 0) == false) {
fprintf(stderr, "Failed to initialize GGWave\n");
return false;
}
g_ggWave->setRxProtocols(protocols);
isInitialized = true;
return true;
};
g_mainUpdate = [&]() {
if (isInitialized == false) {
return true;
}
GGWave_mainLoop();
return true;
};
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);
#else
if (g_doInit() == false) {
printf("Error: failed to initialize audio\n");
return -2;
}
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (g_mainUpdate() == false) break;
}
GGWave_deinit();
// Cleanup
SDL_CloseAudio();
SDL_Quit();
#endif
return 0;
}

279
examples/r2t2/style.css Normal file
View File

@@ -0,0 +1,279 @@
body {
margin: 0; background-color: white;
-webkit-font-smoothing: subpixel-antialiased;
font-smoothing: subpixel-antialiased;
}
#screen {
margin: 0;
padding: 0;
font-size: 13px;
height: 100%;
font: sans-serif;
}
.no-sel {
-moz-user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
-ms-user-select:none;
user-select:none;
-o-user-select:none;
}
.cell {
pointer-events: none;
}
.cell-version {
padding-left: 4px;
padding-top: 0.5em;
text-align: left;
display: inline-block;
float: left;
color: rgba(0, 0, 0, 0.75);
}
.cell-about {
padding-right: 24px;
padding-top: 0.5em;
text-align: right;
display: inline-block;
float: right;
}
.nav-link {
text-decoration: none;
color: rgba(0, 0, 0, 1.0);
}
#main-container {
font-size:12px;
font-family: monospace;
}
textarea {
font-size:12px;
font-family: monospace;
}
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 30px;
width: 30px;
margin: 0;
margin-top: 20px;
margin-left: 20px;
display: inline-block;
vertical-align: top;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 5px solid rgb(235, 235, 235);
border-right: 5px solid rgb(235, 235, 235);
border-bottom: 5px solid rgb(235, 235, 235);
border-top: 5px solid rgb(120, 120, 120);
border-radius: 100%;
background-color: rgb(189, 215, 46);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
#status {
display: inline-block;
vertical-align: top;
margin-top: 30px;
margin-left: 20px;
font-weight: bold;
color: rgb(120, 120, 120);
}
#progress {
height: 20px;
width: 30px;
}
#output {
width: 100%;
height: 200px;
margin: 0 auto;
margin-top: 10px;
border-left: 0px;
border-right: 0px;
padding-left: 0px;
padding-right: 0px;
background-color: black;
color: white;
font-size:10px;
font-family: 'Lucida Console', Monaco, monospace;
outline: none;
}
.led-box {
height: 30px;
width: 25%;
margin: 10px 0;
float: left;
}
.led-box p {
font-size: 12px;
text-align: center;
margin: 1em;
}
.led-red {
margin: 0 auto;
width: 12px;
height: 12px;
background-color: #F00;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px;
-webkit-animation: blinkRed 0.5s infinite;
-moz-animation: blinkRed 0.5s infinite;
-ms-animation: blinkRed 0.5s infinite;
-o-animation: blinkRed 0.5s infinite;
animation: blinkRed 0.5s infinite;
}
@-webkit-keyframes blinkRed {
from { background-color: #F00; }
50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;}
to { background-color: #F00; }
}
@-moz-keyframes blinkRed {
from { background-color: #F00; }
50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;}
to { background-color: #F00; }
}
@-ms-keyframes blinkRed {
from { background-color: #F00; }
50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;}
to { background-color: #F00; }
}
@-o-keyframes blinkRed {
from { background-color: #F00; }
50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;}
to { background-color: #F00; }
}
@keyframes blinkRed {
from { background-color: #F00; }
50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;}
to { background-color: #F00; }
}
.led-yellow {
margin: 0 auto;
width: 12px;
height: 12px;
background-color: #FF0;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px;
-webkit-animation: blinkYellow 1s infinite;
-moz-animation: blinkYellow 1s infinite;
-ms-animation: blinkYellow 1s infinite;
-o-animation: blinkYellow 1s infinite;
animation: blinkYellow 1s infinite;
}
@-webkit-keyframes blinkYellow {
from { background-color: #FF0; }
50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }
to { background-color: #FF0; }
}
@-moz-keyframes blinkYellow {
from { background-color: #FF0; }
50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }
to { background-color: #FF0; }
}
@-ms-keyframes blinkYellow {
from { background-color: #FF0; }
50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }
to { background-color: #FF0; }
}
@-o-keyframes blinkYellow {
from { background-color: #FF0; }
50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }
to { background-color: #FF0; }
}
@keyframes blinkYellow {
from { background-color: #FF0; }
50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }
to { background-color: #FF0; }
}
.led-green {
margin: 0 auto;
width: 12px;
height: 12px;
background-color: #ABFF00;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px;
}
.led-blue {
margin: 0 auto;
width: 18px;
height: 18px;
background-color: #24E0FF;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px;
}
table td {
border: 1px solid #e8e8e8;
}
table th, table td {
padding: 10px 10px;
}
table td {
border: 1px solid #e8e8e8;
}
table th, table td {
padding: 10px 10px;
}
td[Attributes Style] {
text-align: -webkit-center;
}
td {
display: table-cell;
vertical-align: inherit;
}
table {
margin-bottom: 30px;
width: 800px;
text-align: left;
color: #3f3f3f;
border-collapse: collapse;
border: 1px solid #e8e8e8;
}
table {
margin-bottom: 30px;
width: 800px;
text-align: left;
color: #3f3f3f;
border-collapse: collapse;
border: 1px solid #e8e8e8;
}
table {
border-collapse: separate;
border-spacing: 2px;
}

View File

@@ -556,7 +556,7 @@ int main(int argc, char** argv) {
};
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(mainUpdate, NULL, 0, true);
emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);
#else
if (g_doInit() == false) {
printf("Error: failed to initialize audio\n");

View File

@@ -303,7 +303,7 @@ int main(int argc, char** argv) {
};
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(mainUpdate, NULL, 0, true);
emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);
#else
if (g_doInit() == false) {
printf("Error: failed to initialize audio\n");

View File

@@ -44,6 +44,17 @@ extern "C" {
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