mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 00:36:13 +08:00
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:
@@ -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:
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -11,5 +11,4 @@ target_link_libraries(${TARGET} PRIVATE
|
||||
ggwave
|
||||
ggwave-common
|
||||
ggwave-common-sdl2
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
@@ -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){
|
||||
|
||||
71
examples/r2t2/CMakeLists.txt
Normal file
71
examples/r2t2/CMakeLists.txt
Normal 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
58
examples/r2t2/README.md
Normal 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
|
||||
```
|
||||
1
examples/r2t2/build_timestamp-tmpl.h
Normal file
1
examples/r2t2/build_timestamp-tmpl.h
Normal file
@@ -0,0 +1 @@
|
||||
static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)";
|
||||
511
examples/r2t2/ggwave-mod/include/ggwave/ggwave.h
Normal file
511
examples/r2t2/ggwave-mod/include/ggwave/ggwave.h
Normal 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
|
||||
1359
examples/r2t2/ggwave-mod/src/ggwave.cpp
Normal file
1359
examples/r2t2/ggwave-mod/src/ggwave.cpp
Normal file
File diff suppressed because it is too large
Load Diff
21
examples/r2t2/ggwave-mod/src/reed-solomon/LICENSE
Normal file
21
examples/r2t2/ggwave-mod/src/reed-solomon/LICENSE
Normal 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.
|
||||
235
examples/r2t2/ggwave-mod/src/reed-solomon/gf.hpp
Normal file
235
examples/r2t2/ggwave-mod/src/reed-solomon/gf.hpp
Normal 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
|
||||
|
||||
94
examples/r2t2/ggwave-mod/src/reed-solomon/poly.hpp
Normal file
94
examples/r2t2/ggwave-mod/src/reed-solomon/poly.hpp
Normal 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
|
||||
538
examples/r2t2/ggwave-mod/src/reed-solomon/rs.hpp
Normal file
538
examples/r2t2/ggwave-mod/src/reed-solomon/rs.hpp
Normal 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
|
||||
|
||||
157
examples/r2t2/ggwave-mod/src/resampler.cpp
Normal file
157
examples/r2t2/ggwave-mod/src/resampler.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
49
examples/r2t2/ggwave-mod/src/resampler.h
Normal file
49
examples/r2t2/ggwave-mod/src/resampler.h
Normal 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;
|
||||
};
|
||||
174
examples/r2t2/index-tmpl.html
Normal file
174
examples/r2t2/index-tmpl.html
Normal 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
136
examples/r2t2/main.cpp
Normal 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
55
examples/r2t2/main.js
Normal 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
BIN
examples/r2t2/plucky.mp3
Normal file
Binary file not shown.
369
examples/r2t2/r2t2-rx.cpp
Normal file
369
examples/r2t2/r2t2-rx.cpp
Normal 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
279
examples/r2t2/style.css
Normal 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;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user