mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 16:47:59 +08:00
Add arduino-rx + update ggwave-mod
This commit is contained in:
@@ -79,6 +79,9 @@ if (GGWAVE_SUPPORT_SDL2)
|
||||
)
|
||||
endif()
|
||||
|
||||
# modified ggwave used by some of the examples
|
||||
add_subdirectory(ggwave-mod/src)
|
||||
|
||||
# examples
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
@@ -93,6 +96,7 @@ if (GGWAVE_SUPPORT_SDL2)
|
||||
add_subdirectory(r2t2)
|
||||
endif()
|
||||
|
||||
add_subdirectory(arduino-rx)
|
||||
if (EMSCRIPTEN)
|
||||
# emscripten sdl2 examples
|
||||
|
||||
|
||||
24
examples/arduino-rx/CMakeLists.txt
Normal file
24
examples/arduino-rx/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# arduino-rx
|
||||
|
||||
set(TARGET arduino-rx)
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
add_executable(${TARGET}
|
||||
arduino-rx.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET} PRIVATE
|
||||
..
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
ggwave-common
|
||||
ggwave-mod
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
else()
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/examples/ggwave-mod/src/ggwave-mod.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave-mod.js COPYONLY)
|
||||
endif()
|
||||
402
examples/arduino-rx/arduino-rx.cpp
Normal file
402
examples/arduino-rx/arduino-rx.cpp
Normal file
@@ -0,0 +1,402 @@
|
||||
#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 <array>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
// Direct-sequence spread magic numbers
|
||||
// Used to xor the actual payload
|
||||
const std::array<uint8_t, 64> kDSSMagic = {
|
||||
0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f,
|
||||
0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d,
|
||||
0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f,
|
||||
0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e,
|
||||
};
|
||||
|
||||
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,
|
||||
512,
|
||||
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);
|
||||
|
||||
GGWave::TxRxData rxData;
|
||||
int n = g_ggWave->takeRxData(rxData);
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
rxData[i] ^= kDSSMagic[i%kDSSMagic.size()];
|
||||
}
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
std::string tstr = std::asctime(std::localtime(×tamp));
|
||||
tstr.back() = 0;
|
||||
printf("[%s] Received: '%s'\n", tstr.c_str(), rxData.data());
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
#else
|
||||
printf("Usage: %s [-cN] [-lN]\n", argv[0]);
|
||||
printf(" -cN - select capture device N\n");
|
||||
printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed);
|
||||
printf("\n");
|
||||
#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, } },
|
||||
{ GGWAVE_TX_PROTOCOL_CUSTOM_3, { "[R2T2] Low Normal", 16, 9, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_CUSTOM_4, { "[R2T2] Low Fast", 16, 6, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_CUSTOM_5, { "[R2T2] Low Fastest", 16, 3, 1, } },
|
||||
};
|
||||
|
||||
const auto argm = parseCmdArguments(argc, argv);
|
||||
const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c"));
|
||||
const int payloadLength = argm.count("l") == 0 ? 16 : std::stoi(argm.at("l"));
|
||||
|
||||
bool isInitialized = false;
|
||||
|
||||
g_doInit = [&]() {
|
||||
if (GGWave_init(0, captureId, payloadLength, 0) == false) {
|
||||
fprintf(stderr, "Failed to initialize GGWave\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_ggWave->setRxProtocols(protocols);
|
||||
|
||||
isInitialized = true;
|
||||
printf("Listening for payload with length = %d bytes ..\n", payloadLength);
|
||||
|
||||
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;
|
||||
}
|
||||
235
examples/arduino-rx/index-tmpl.html
Normal file
235
examples/arduino-rx/index-tmpl.html
Normal file
@@ -0,0 +1,235 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title>ggwave : arduino-rx</title>
|
||||
|
||||
<style>
|
||||
.block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: #FFFFFF00;
|
||||
color: #000000;
|
||||
padding: 14px 28px;
|
||||
padding-top: 40vh;
|
||||
font-size: 100px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-container">
|
||||
<div id="controls">
|
||||
<div id="controls0">
|
||||
<button id="captureStart" class="block">Start</button>
|
||||
</div>
|
||||
<div id="controls1" hidden>
|
||||
<button id="rxData" class="block">[no data]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="debug" hidden>
|
||||
<br><br>
|
||||
|
||||
<button onclick="onSend('RRR');">Red</button>
|
||||
<button onclick="onSend('GGG');">Green</button>
|
||||
<button onclick="onSend('BBB');">Blue</button>
|
||||
|
||||
<br><br>
|
||||
|
||||
<textarea name="textarea" id="rxData2" style="width:300px;height:100px;" disabled></textarea><br>
|
||||
|
||||
<button id="captureStart2">Start capturing</button>
|
||||
<button id="captureStop" hidden>Stop capturing</button>
|
||||
|
||||
<br><br>
|
||||
|
||||
<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> |
|
||||
<a class="nav-link" href="https://github.com/ggerganov/ggwave/tree/master/examples/ggwave-js">Source Code</a> |
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="ggwave-mod.js"></script>
|
||||
<script type='text/javascript'>
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;
|
||||
|
||||
var context = null;
|
||||
var recorder = null;
|
||||
|
||||
// the ggwave module instance
|
||||
var ggwave = null;
|
||||
var parameters = null;
|
||||
var instance = null;
|
||||
|
||||
const kPayloadLength = 16;
|
||||
|
||||
var kDSSMagic = [
|
||||
0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f,
|
||||
0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d,
|
||||
0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f,
|
||||
0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e,
|
||||
];
|
||||
|
||||
// instantiate the ggwave instance
|
||||
// ggwave_factory comes from the ggwave.js module
|
||||
ggwave_factory().then(function(obj) {
|
||||
ggwave = obj;
|
||||
});
|
||||
|
||||
var txData = document.getElementById("txData");
|
||||
var rxData = document.getElementById("rxData");
|
||||
var captureStart = document.getElementById("captureStart");
|
||||
var captureStop = document.getElementById("captureStop");
|
||||
|
||||
// helper function
|
||||
function convertTypedArray(src, type) {
|
||||
var buffer = new ArrayBuffer(src.byteLength);
|
||||
var baseView = new src.constructor(buffer).set(src);
|
||||
return new type(buffer);
|
||||
}
|
||||
|
||||
// initialize audio context and ggwave
|
||||
function init() {
|
||||
if (!context) {
|
||||
context = new AudioContext({sampleRate: 48000});
|
||||
|
||||
parameters = ggwave.getDefaultParameters();
|
||||
parameters.payloadLength = kPayloadLength;
|
||||
parameters.samplesPerFrame = 512;
|
||||
parameters.sampleRateInp = context.sampleRate;
|
||||
parameters.sampleRateOut = context.sampleRate;
|
||||
instance = ggwave.init(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Tx
|
||||
//
|
||||
|
||||
function onSend(text) {
|
||||
init();
|
||||
|
||||
// DSS : xor the text with the magic numbers
|
||||
var payload = new Uint8Array(kPayloadLength);
|
||||
for (var i = 0; i < kPayloadLength; i++) {
|
||||
payload[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
payload[i] = text.charCodeAt(i);
|
||||
}
|
||||
for (var i = 0; i < kPayloadLength; i++) {
|
||||
payload[i] = payload[i] ^ kDSSMagic[i];
|
||||
}
|
||||
|
||||
// generate audio waveform
|
||||
var waveform = ggwave.encode(instance, payload, ggwave.TxProtocolId.GGWAVE_TX_PROTOCOL_DT_FASTEST, 25)
|
||||
|
||||
// play audio
|
||||
var buf = convertTypedArray(waveform, Float32Array);
|
||||
var buffer = context.createBuffer(1, buf.length, context.sampleRate);
|
||||
buffer.getChannelData(0).set(buf);
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.connect(context.destination);
|
||||
source.start(0);
|
||||
}
|
||||
|
||||
//
|
||||
// Rx
|
||||
//
|
||||
|
||||
captureStart.addEventListener("click", function () {
|
||||
init();
|
||||
|
||||
let constraints = {
|
||||
audio: {
|
||||
// not sure if these are necessary to have
|
||||
echoCancellation: false,
|
||||
autoGainControl: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
};
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(function (e) {
|
||||
mediaStream = context.createMediaStreamSource(e);
|
||||
|
||||
var bufferSize = 1024;
|
||||
var numberOfInputChannels = 1;
|
||||
var numberOfOutputChannels = 1;
|
||||
|
||||
if (context.createScriptProcessor) {
|
||||
recorder = context.createScriptProcessor(
|
||||
bufferSize,
|
||||
numberOfInputChannels,
|
||||
numberOfOutputChannels);
|
||||
} else {
|
||||
recorder = context.createJavaScriptNode(
|
||||
bufferSize,
|
||||
numberOfInputChannels,
|
||||
numberOfOutputChannels);
|
||||
}
|
||||
|
||||
recorder.onaudioprocess = function (e) {
|
||||
var source = e.inputBuffer;
|
||||
var res = ggwave.decode(instance, convertTypedArray(new Float32Array(source.getChannelData(0)), Int8Array));
|
||||
if (res && res.length == kPayloadLength) {
|
||||
// DSS
|
||||
var payload = "";
|
||||
res8 = convertTypedArray(res, Uint8Array);
|
||||
for (var i = 0; i < kPayloadLength; i++) {
|
||||
res8[i] = res8[i] ^ kDSSMagic[i];
|
||||
payload += String.fromCharCode(res8[i]);
|
||||
if (res8[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log(res8);
|
||||
document.getElementById("rxData").value = payload;
|
||||
document.getElementById("rxData").innerHTML = payload;
|
||||
|
||||
document.body.style.backgroundColor = '#aaffaa';
|
||||
setTimeout(function () {
|
||||
document.body.style.backgroundColor = '#FFFFFF';
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
mediaStream.connect(recorder);
|
||||
recorder.connect(context.destination);
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
rxData.value = 'Listening ...';
|
||||
document.getElementById("controls0").hidden = true;
|
||||
document.getElementById("controls1").hidden = false;
|
||||
captureStart.hidden = true;
|
||||
captureStop.hidden = false;
|
||||
});
|
||||
|
||||
captureStop.addEventListener("click", function () {
|
||||
if (recorder) {
|
||||
recorder.disconnect(context.destination);
|
||||
mediaStream.disconnect(recorder);
|
||||
recorder = null;
|
||||
}
|
||||
|
||||
rxData.value = 'Audio capture is paused! Press the "Start capturing" button to analyze audio from the microphone';
|
||||
captureStart.hidden = false;
|
||||
captureStop.hidden = true;
|
||||
});
|
||||
|
||||
captureStop.click();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -25,5 +25,23 @@ void loop() {
|
||||
GGWave::send(kPinSpeaker, "This is GGWave!!", GGWave::TX_ARDUINO_512_FASTEST);
|
||||
digitalWrite(kPinLed0, LOW);
|
||||
|
||||
delay(250);
|
||||
|
||||
digitalWrite(kPinLed0, HIGH);
|
||||
GGWave::send(kPinSpeaker, "This is NORMAL", GGWave::TX_ARDUINO_512_NORMAL);
|
||||
digitalWrite(kPinLed0, LOW);
|
||||
|
||||
delay(250);
|
||||
|
||||
digitalWrite(kPinLed0, HIGH);
|
||||
GGWave::send(kPinSpeaker, "This is FAST", GGWave::TX_ARDUINO_512_FAST);
|
||||
digitalWrite(kPinLed0, LOW);
|
||||
|
||||
delay(250);
|
||||
|
||||
digitalWrite(kPinLed0, HIGH);
|
||||
GGWave::send(kPinSpeaker, "This is FASTEST", GGWave::TX_ARDUINO_512_FASTEST);
|
||||
digitalWrite(kPinLed0, LOW);
|
||||
|
||||
delay(5000);
|
||||
}
|
||||
@@ -90,6 +90,23 @@ extern "C" {
|
||||
// the python module and unfortunately had to do it this way
|
||||
typedef int ggwave_Instance;
|
||||
|
||||
// Change file stream for internal ggwave logging. NULL - disable logging
|
||||
//
|
||||
// Intentionally passing it as void * instead of FILE * to avoid including a header
|
||||
//
|
||||
// // log to standard error
|
||||
// ggwave_setLogFile(stderr);
|
||||
//
|
||||
// // log to standard output
|
||||
// ggwave_setLogFile(stdout);
|
||||
//
|
||||
// // disable logging
|
||||
// ggwave_setLogFile(NULL);
|
||||
//
|
||||
// Note: not thread-safe. Do not call while any GGWave instances are running
|
||||
//
|
||||
GGWAVE_API void ggwave_setLogFile(void * fptr);
|
||||
|
||||
// Helper method to get default instance parameters
|
||||
GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void);
|
||||
|
||||
@@ -189,8 +206,10 @@ extern "C" {
|
||||
// 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
|
||||
// If the return value is greater than 0, then there are that number of bytes decoded.
|
||||
//
|
||||
// IMPORTANT:
|
||||
// Notice that the decoded data written to the outputBuffer is NOT null terminated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
@@ -218,6 +237,39 @@ extern "C" {
|
||||
int dataSize,
|
||||
char * outputBuffer);
|
||||
|
||||
// Memory-safe overload of ggwave_decode
|
||||
//
|
||||
// outputSize - optionally specify the size of the output buffer
|
||||
//
|
||||
// If the return value is -2 then the provided outputBuffer was not big enough to
|
||||
// store the decoded data.
|
||||
//
|
||||
// See ggwave_decode for more information
|
||||
//
|
||||
GGWAVE_API int ggwave_ndecode(
|
||||
ggwave_Instance instance,
|
||||
const char * dataBuffer,
|
||||
int dataSize,
|
||||
char * outputBuffer,
|
||||
int outputSize);
|
||||
|
||||
// Toggle Rx protocols on and off
|
||||
//
|
||||
// instance - the GGWave instance to use
|
||||
// rxProtocolId - Id of the Rx protocol to modify
|
||||
// state - 0 - disable, 1 - enable
|
||||
//
|
||||
// If an Rx protocol is enabled, the GGWave instance will attempt to decode received
|
||||
// data using this protocol. By default, all protocols are enabled.
|
||||
// Use this function to restrict the number of Rx protocols used in the decoding
|
||||
// process. This helps to reduce the number of false positives and improves the transmission
|
||||
// accuracy, especially when the Tx/Rx protocol is known in advance.
|
||||
//
|
||||
GGWAVE_API void ggwave_toggleRxProtocol(
|
||||
ggwave_Instance instance,
|
||||
ggwave_TxProtocolId rxProtocolId,
|
||||
int state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -272,15 +324,19 @@ public:
|
||||
|
||||
static const TxProtocols & getTxProtocols() {
|
||||
static const TxProtocols kTxProtocols {
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_NORMAL, { "Normal", 40, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, { "Fast", 40, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_AUDIBLE_FASTEST, { "Fastest", 40, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL, { "[U] Normal", 320, 9, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST, { "[U] Fast", 320, 6, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST, { "[U] Fastest", 320, 3, 3, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_NORMAL, { "[DT] Normal", 24, 9, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FAST, { "[DT] Fast", 24, 6, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FASTEST, { "[DT] Fastest", 24, 3, 1, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_AUDIBLE_NORMAL, { "Normal", 40, 9, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_AUDIBLE_FAST, { "Fast", 40, 6, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_AUDIBLE_FASTEST, { "Fastest", 40, 3, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_ULTRASOUND_NORMAL, { "[U] Normal", 320, 9, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FAST, { "[U] Fast", 320, 6, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_ULTRASOUND_FASTEST, { "[U] Fastest", 320, 3, 3, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_DT_NORMAL, { "[DT] Normal", 24, 9, 1, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_DT_FAST, { "[DT] Fast", 24, 6, 1, } },
|
||||
//{ GGWAVE_TX_PROTOCOL_DT_FASTEST, { "[DT] Fastest", 24, 3, 1, } },
|
||||
|
||||
{ GGWAVE_TX_PROTOCOL_DT_NORMAL, { "Arduino", 16, 9, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FAST, { "Arduino", 16, 6, 1, } },
|
||||
{ GGWAVE_TX_PROTOCOL_DT_FASTEST, { "Arduino", 16, 3, 1, } },
|
||||
};
|
||||
|
||||
return kTxProtocols;
|
||||
@@ -306,6 +362,15 @@ public:
|
||||
GGWave(const Parameters & parameters);
|
||||
~GGWave();
|
||||
|
||||
// set file stream for the internal ggwave logging
|
||||
//
|
||||
// By default, ggwave prints internal log messages to stderr.
|
||||
// To disable logging all together, call this method with nullptr.
|
||||
//
|
||||
// Note: not thread-safe. Do not call while any GGWave instances are running
|
||||
//
|
||||
static void setLogFile(FILE * fptr);
|
||||
|
||||
static const Parameters & getDefaultParameters();
|
||||
|
||||
// set Tx data to encode
|
||||
@@ -369,7 +434,7 @@ public:
|
||||
|
||||
// Tx
|
||||
|
||||
static TxProtocolId getDefaultTxProtocolId() { return GGWAVE_TX_PROTOCOL_AUDIBLE_FAST; }
|
||||
static TxProtocolId getDefaultTxProtocolId() { return GGWAVE_TX_PROTOCOL_DT_NORMAL; }
|
||||
static const TxProtocol & getDefaultTxProtocol() { return getTxProtocols().at(getDefaultTxProtocolId()); }
|
||||
static const TxProtocol & getTxProtocol(int id) { return getTxProtocols().at(TxProtocolId(id)); }
|
||||
static const TxProtocol & getTxProtocol(TxProtocolId id) { return getTxProtocols().at(id); }
|
||||
@@ -388,6 +453,8 @@ public:
|
||||
void setRxProtocols(const RxProtocols & rxProtocols) { m_rxProtocols = rxProtocols; }
|
||||
const RxProtocols & getRxProtocols() const { return m_rxProtocols; }
|
||||
|
||||
int lastRxDataLength() const { return m_lastRxDataLength; }
|
||||
|
||||
const TxRxData & getRxData() const { return m_rxData; }
|
||||
const RxProtocol & getRxProtocol() const { return m_rxProtocol; }
|
||||
const RxProtocolId & getRxProtocolId() const { return m_rxProtocolId; }
|
||||
60
examples/ggwave-mod/src/CMakeLists.txt
Normal file
60
examples/ggwave-mod/src/CMakeLists.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
set(TARGET ggwave-mod)
|
||||
|
||||
add_library(${TARGET}
|
||||
ggwave.cpp
|
||||
resampler.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET} PUBLIC
|
||||
.
|
||||
../include
|
||||
)
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
target_link_libraries(${TARGET} PUBLIC
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
target_compile_definitions(${TARGET} PUBLIC
|
||||
GGWAVE_SHARED
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MINGW)
|
||||
target_link_libraries(${TARGET} PUBLIC
|
||||
stdc++
|
||||
)
|
||||
endif()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(TARGET libggwave-mod)
|
||||
|
||||
add_executable(${TARGET}
|
||||
${PROJECT_SOURCE_DIR}/bindings/javascript/emscripten.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
ggwave-mod
|
||||
)
|
||||
|
||||
unset(EXTRA_FLAGS)
|
||||
if (GGWAVE_WASM_SINGLE_FILE)
|
||||
set(EXTRA_FLAGS "-s SINGLE_FILE=1")
|
||||
message(STATUS "Embedding WASM inside ggwave-mod.js")
|
||||
|
||||
add_custom_command(
|
||||
TARGET libggwave-mod POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_BINARY_DIR}/bin/libggwave-mod.js
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ggwave-mod.js
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
|
||||
--bind \
|
||||
-s MODULARIZE=1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s EXPORT_NAME=\"'ggwave_factory'\" \
|
||||
${EXTRA_FLAGS} \
|
||||
")
|
||||
endif()
|
||||
21
examples/ggwave-mod/src/ggwave-mod.js
Normal file
21
examples/ggwave-mod/src/ggwave-mod.js
Normal file
File diff suppressed because one or more lines are too long
@@ -11,12 +11,26 @@
|
||||
#include <map>
|
||||
//#include <random>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define ggprintf(...) \
|
||||
g_fptr && fprintf(g_fptr, __VA_ARGS__)
|
||||
|
||||
//
|
||||
// C interface
|
||||
//
|
||||
|
||||
namespace {
|
||||
FILE * g_fptr = stderr;
|
||||
std::map<ggwave_Instance, GGWave *> g_instances;
|
||||
std::map<ggwave_Instance, GGWave::RxProtocols> g_rxProtocols;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
void ggwave_setLogFile(void * fptr) {
|
||||
GGWave::setLogFile((FILE *) fptr);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@@ -58,12 +72,12 @@ int ggwave_encode(
|
||||
GGWave * ggWave = (GGWave *) g_instances[instance];
|
||||
|
||||
if (ggWave == nullptr) {
|
||||
fprintf(stderr, "Invalid GGWave instance %d\n", instance);
|
||||
ggprintf("Invalid GGWave instance %d\n", instance);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ggWave->init(dataSize, dataBuffer, ggWave->getTxProtocol(txProtocolId), volume) == false) {
|
||||
fprintf(stderr, "Failed to initialize GGWave instance %d\n", instance);
|
||||
ggprintf("Failed to initialize GGWave instance %d\n", instance);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -85,7 +99,7 @@ int ggwave_encode(
|
||||
};
|
||||
|
||||
if (ggWave->encode(cbWaveformOut) == false) {
|
||||
fprintf(stderr, "Failed to encode data - GGWave instance %d\n", instance);
|
||||
ggprintf("Failed to encode data - GGWave instance %d\n", instance);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -112,7 +126,7 @@ int ggwave_decode(
|
||||
|
||||
ggWave->decode(cbWaveformInp);
|
||||
|
||||
// todo : avoid allocation
|
||||
// TODO : avoid allocation
|
||||
GGWave::TxRxData rxData;
|
||||
|
||||
auto rxDataLength = ggWave->takeRxData(rxData);
|
||||
@@ -120,12 +134,72 @@ int ggwave_decode(
|
||||
// failed to decode message
|
||||
return -1;
|
||||
} else if (rxDataLength > 0) {
|
||||
std::copy(rxData.begin(), rxData.end(), outputBuffer);
|
||||
memcpy(outputBuffer, rxData.data(), rxDataLength);
|
||||
}
|
||||
|
||||
return rxDataLength;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
int ggwave_ndecode(
|
||||
ggwave_Instance instance,
|
||||
const char * dataBuffer,
|
||||
int dataSize,
|
||||
char * outputBuffer,
|
||||
int outputSize) {
|
||||
// TODO : avoid duplicated code
|
||||
GGWave * ggWave = (GGWave *) g_instances[instance];
|
||||
|
||||
GGWave::CBWaveformInp cbWaveformInp = [&](void * data, uint32_t nMaxBytes) -> uint32_t {
|
||||
uint32_t nCopied = std::min((uint32_t) dataSize, nMaxBytes);
|
||||
std::copy(dataBuffer, dataBuffer + nCopied, (char *) data);
|
||||
|
||||
dataSize -= nCopied;
|
||||
dataBuffer += nCopied;
|
||||
|
||||
return nCopied;
|
||||
};
|
||||
|
||||
ggWave->decode(cbWaveformInp);
|
||||
|
||||
// TODO : avoid allocation
|
||||
GGWave::TxRxData rxData;
|
||||
|
||||
auto rxDataLength = ggWave->takeRxData(rxData);
|
||||
if (rxDataLength == -1) {
|
||||
// failed to decode message
|
||||
return -1;
|
||||
} else if (rxDataLength > outputSize) {
|
||||
// the outputBuffer is not big enough to store the data
|
||||
return -2;
|
||||
} else if (rxDataLength > 0) {
|
||||
memcpy(outputBuffer, rxData.data(), rxDataLength);
|
||||
}
|
||||
|
||||
return rxDataLength;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
void ggwave_toggleRxProtocol(
|
||||
ggwave_Instance instance,
|
||||
ggwave_TxProtocolId rxProtocolId,
|
||||
int state) {
|
||||
// if never called - initialize with all available protocols
|
||||
if (g_rxProtocols.find(instance) == g_rxProtocols.end()) {
|
||||
g_rxProtocols[instance] = GGWave::getTxProtocols();
|
||||
}
|
||||
|
||||
if (state == 0) {
|
||||
// disable Rx protocol
|
||||
g_rxProtocols[instance].erase(rxProtocolId);
|
||||
} else if (state == 1) {
|
||||
// enable Rx protocol
|
||||
g_rxProtocols[instance][rxProtocolId] = GGWave::getTxProtocols().at(rxProtocolId);
|
||||
}
|
||||
|
||||
g_instances[instance]->setRxProtocols(g_rxProtocols[instance]);
|
||||
}
|
||||
|
||||
//
|
||||
// C++ implementation
|
||||
//
|
||||
@@ -259,7 +333,7 @@ int bytesForSampleFormat(GGWave::SampleFormat sampleFormat) {
|
||||
case GGWAVE_SAMPLE_FORMAT_F32: return sizeof(float); break;
|
||||
};
|
||||
|
||||
fprintf(stderr, "Invalid sample format: %d\n", (int) sampleFormat);
|
||||
ggprintf("Invalid sample format: %d\n", (int) sampleFormat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -270,6 +344,10 @@ struct GGWave::Impl {
|
||||
Resampler resampler;
|
||||
};
|
||||
|
||||
void GGWave::setLogFile(FILE * fptr) {
|
||||
g_fptr = fptr;
|
||||
}
|
||||
|
||||
const GGWave::Parameters & GGWave::getDefaultParameters() {
|
||||
static ggwave_Parameters result {
|
||||
-1, // vaiable payload length
|
||||
@@ -366,12 +444,12 @@ GGWave::GGWave(const Parameters & parameters) :
|
||||
}
|
||||
|
||||
if (m_sampleRateInp < kSampleRateMin) {
|
||||
fprintf(stderr, "Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin);
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin);
|
||||
throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
}
|
||||
|
||||
if (m_sampleRateInp > kSampleRateMax) {
|
||||
fprintf(stderr, "Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax);
|
||||
ggprintf("Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax);
|
||||
throw std::runtime_error("Invalid capture/playback sample rate");
|
||||
}
|
||||
|
||||
@@ -382,11 +460,11 @@ GGWave::~GGWave() {
|
||||
}
|
||||
|
||||
bool GGWave::init(const std::string & text, const int volume) {
|
||||
return init(text.size(), text.data(), getDefaultTxProtocol(), volume);
|
||||
return init((int) text.size(), text.data(), getDefaultTxProtocol(), volume);
|
||||
}
|
||||
|
||||
bool GGWave::init(const std::string & text, const TxProtocol & txProtocol, const int volume) {
|
||||
return init(text.size(), text.data(), txProtocol, volume);
|
||||
return init((int) text.size(), text.data(), txProtocol, volume);
|
||||
}
|
||||
|
||||
bool GGWave::init(int dataSize, const char * dataBuffer, const int volume) {
|
||||
@@ -395,18 +473,18 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const int volume) {
|
||||
|
||||
bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume) {
|
||||
if (dataSize < 0) {
|
||||
fprintf(stderr, "Negative data size: %d\n", dataSize);
|
||||
ggprintf("Negative data size: %d\n", dataSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVarible;
|
||||
if (dataSize > maxLength) {
|
||||
fprintf(stderr, "Truncating data from %d to %d bytes\n", dataSize, maxLength);
|
||||
ggprintf("Truncating data from %d to %d bytes\n", dataSize, maxLength);
|
||||
dataSize = maxLength;
|
||||
}
|
||||
|
||||
if (volume < 0 || volume > 100) {
|
||||
fprintf(stderr, "Invalid volume: %d\n", volume);
|
||||
ggprintf("Invalid volume: %d\n", volume);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -481,7 +559,7 @@ uint32_t GGWave::encodeSize_samples() const {
|
||||
int nECCBytesPerTx = getECCBytesForLength(m_txDataLength);
|
||||
int sendDataLength = m_txDataLength + m_encodedDataOffset;
|
||||
int totalBytes = sendDataLength + nECCBytesPerTx;
|
||||
int totalDataFrames = ((totalBytes + m_txProtocol.bytesPerTx - 1)/m_txProtocol.bytesPerTx)*m_txProtocol.framesPerTx;
|
||||
int totalDataFrames = 2*((totalBytes + m_txProtocol.bytesPerTx - 1)/m_txProtocol.bytesPerTx)*m_txProtocol.framesPerTx;
|
||||
|
||||
return (
|
||||
m_nMarkerFrames + totalDataFrames + m_nMarkerFrames
|
||||
@@ -737,14 +815,14 @@ void GGWave::decode(const CBWaveformInp & cbWaveformInp) {
|
||||
}
|
||||
|
||||
if (nBytesRecorded % m_sampleSizeBytesInp != 0) {
|
||||
fprintf(stderr, "Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n",
|
||||
ggprintf("Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n",
|
||||
nBytesRecorded, m_sampleSizeBytesInp);
|
||||
m_samplesNeeded = m_samplesPerFrame;
|
||||
break;
|
||||
}
|
||||
|
||||
if (nBytesRecorded > nBytesNeeded) {
|
||||
fprintf(stderr, "Failure during capture - more samples were provided (%d) than requested (%d)\n",
|
||||
ggprintf("Failure during capture - more samples were provided (%d) than requested (%d)\n",
|
||||
nBytesRecorded/m_sampleSizeBytesInp, nBytesNeeded/m_sampleSizeBytesInp);
|
||||
m_samplesNeeded = m_samplesPerFrame;
|
||||
break;
|
||||
@@ -888,7 +966,7 @@ bool GGWave::takeRxAmplitude(AmplitudeData & dst) {
|
||||
|
||||
bool GGWave::computeFFTR(const float * src, float * dst, int N, float d) {
|
||||
if (N > kMaxSamplesPerFrame) {
|
||||
fprintf(stderr, "computeFFTR: N (%d) must be <= %d\n", N, GGWave::kMaxSamplesPerFrame);
|
||||
ggprintf("computeFFTR: N (%d) must be <= %d\n", N, GGWave::kMaxSamplesPerFrame);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -945,7 +1023,7 @@ void GGWave::decode_variable() {
|
||||
}
|
||||
|
||||
if (m_analyzingData) {
|
||||
fprintf(stderr, "Analyzing captured data ..\n");
|
||||
ggprintf("Analyzing captured data ..\n");
|
||||
auto tStart = std::chrono::high_resolution_clock::now();
|
||||
|
||||
const int stepsPerFrame = 16;
|
||||
@@ -1054,8 +1132,8 @@ void GGWave::decode_variable() {
|
||||
if (m_rxData[0] != 0) {
|
||||
std::string s((char *) m_rxData.data(), decodedLength);
|
||||
|
||||
fprintf(stderr, "Decoded length = %d, protocol = '%s' (%d)\n", decodedLength, rxProtocol.name, rxProtocolId);
|
||||
fprintf(stderr, "Received sound data successfully: '%s'\n", s.c_str());
|
||||
ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", decodedLength, rxProtocol.name, rxProtocolId);
|
||||
ggprintf("Received sound data successfully: '%s'\n", s.c_str());
|
||||
|
||||
isValid = true;
|
||||
m_hasNewRxData = true;
|
||||
@@ -1078,7 +1156,7 @@ void GGWave::decode_variable() {
|
||||
m_framesToRecord = 0;
|
||||
|
||||
if (isValid == false) {
|
||||
fprintf(stderr, "Failed to capture sound data. Please try again (length = %d)\n", m_rxData[0]);
|
||||
ggprintf("Failed to capture sound data. Please try again (length = %d)\n", m_rxData[0]);
|
||||
m_lastRxDataLength = -1;
|
||||
m_framesToRecord = -1;
|
||||
}
|
||||
@@ -1092,7 +1170,7 @@ void GGWave::decode_variable() {
|
||||
m_framesLeftToAnalyze = 0;
|
||||
|
||||
auto tEnd = std::chrono::high_resolution_clock::now();
|
||||
fprintf(stderr, "Time to analyze: %g ms\n", getTime_ms(tStart, tEnd));
|
||||
ggprintf("Time to analyze: %g ms\n", getTime_ms(tStart, tEnd));
|
||||
}
|
||||
|
||||
// check if receiving data
|
||||
@@ -1131,7 +1209,7 @@ void GGWave::decode_variable() {
|
||||
|
||||
if (isReceiving) {
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
fprintf(stderr, "%sReceiving sound data ...\n", std::asctime(std::localtime(×tamp)));
|
||||
ggprintf("%sReceiving sound data ...\n", std::asctime(std::localtime(×tamp)));
|
||||
|
||||
m_receivingData = true;
|
||||
std::fill(m_rxData.begin(), m_rxData.end(), 0);
|
||||
@@ -1180,7 +1258,7 @@ void GGWave::decode_variable() {
|
||||
if (isEnded && m_framesToRecord > 1) {
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
m_recvDuration_frames -= m_framesLeftToRecord - 1;
|
||||
fprintf(stderr, "%sReceived end marker. Frames left = %d, recorded = %d\n", std::asctime(std::localtime(×tamp)), m_framesLeftToRecord, m_recvDuration_frames);
|
||||
ggprintf("%sReceived end marker. Frames left = %d, recorded = %d\n", std::asctime(std::localtime(×tamp)), m_framesLeftToRecord, m_recvDuration_frames);
|
||||
m_nMarkersSuccess = 0;
|
||||
m_framesLeftToRecord = 1;
|
||||
}
|
||||
@@ -1321,10 +1399,7 @@ void GGWave::decode_fixed() {
|
||||
|
||||
if (rsData.Decode(m_txDataEncoded.data(), m_rxData.data()) == 0) {
|
||||
if (m_rxData[0] != 0) {
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
std::string tstr = std::asctime(std::localtime(×tamp));
|
||||
tstr.back() = 0;
|
||||
fprintf(stderr, "[%s] Received: '%s'\n", tstr.c_str(), m_rxData.data());
|
||||
ggprintf("Received sound data successfully: '%s'\n", m_rxData.data());
|
||||
|
||||
isValid = true;
|
||||
m_hasNewRxData = true;
|
||||
@@ -6,18 +6,15 @@ 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
|
||||
ggwave-mod
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -29,19 +26,16 @@ 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
|
||||
ggwave-mod
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
else()
|
||||
@@ -49,19 +43,16 @@ else()
|
||||
|
||||
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
|
||||
ggwave-mod
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
|
||||
|
||||
Reference in New Issue
Block a user