This commit is contained in:
Winter Hille 2025-08-29 18:04:07 -07:00
commit cde73248ec
11 changed files with 1011 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

407
Cargo.lock generated Normal file
View file

@ -0,0 +1,407 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "errno"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "ggwave"
version = "0.1.0"
dependencies = [
"bitflags",
"ggwave-sys",
"libc",
]
[[package]]
name = "ggwave-sys"
version = "0.0.1"
dependencies = [
"bindgen",
"libc",
"shell-words",
"tempfile",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.14.3+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"

23
Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[workspace]
resolver = "2"
members = ["sys"]
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
[package]
name = "ggwave"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
bitflags = "2.9"
ggwave-sys.path = "sys"
libc = "0.2"
[features]
default = []
less_protocls = []

18
LICENSE Normal file
View file

@ -0,0 +1,18 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

0
README.md Normal file
View file

66
flake.lock generated Normal file
View file

@ -0,0 +1,66 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1755585599,
"narHash": "sha256-tl/0cnsqB/Yt7DbaGMel2RLa7QG5elA8lkaOXli6VdY=",
"owner": "nix-community",
"repo": "fenix",
"rev": "6ed03ef4c8ec36d193c18e06b9ecddde78fb7e42",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1756386758,
"narHash": "sha256-1wxxznpW2CKvI9VdniaUnTT2Os6rdRJcRUf65ZK9OtE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "dfb2f12e899db4876308eba6d93455ab7da304cd",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1755504847,
"narHash": "sha256-VX0B9hwhJypCGqncVVLC+SmeMVd/GAYbJZ0MiiUn2Pk=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "a905e3b21b144d77e1b304e49f3264f6f8d4db75",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

72
flake.nix Normal file
View file

@ -0,0 +1,72 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
fenix,
}:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux // {
ggwave = pkgs.stdenv.mkDerivation rec {
pname = "ggwave";
version = "0.4.1";
src = pkgs.fetchFromGitHub {
owner = "ggerganov";
repo = "ggwave";
rev = "${pname}-v${version}";
hash = "sha256-tymG9JC+Anb3D26EwMF8laz8xvGnrEA+tqMmGBAnY7o=";
fetchSubmodules = true;
};
nativeBuildInputs = [ pkgs.cmake ];
cmakeFlags = [
"-DGGWAVE_BUILD_EXAMPLES=OFF"
"-DCMAKE_BUILD_TYPE=Release"
];
doCheck = true;
installPhase = ''
# for some reason nix won't copy the include even though cmake tells it to
mkdir -p $out/include
cp -r $src/include/* $out/include
cmake --install . --prefix=$out
'';
};
};
rustPkgs = fenix.packages.x86_64-linux.default;
in
with pkgs;
{
devShells.x86_64-linux.default = mkShell {
packages = [
pkg-config
cmake
clang
pkgs.ggwave
]
++ (with rustPkgs; [
cargo
clippy
rustc
rustfmt
]);
GGWAVE_PATH = "${pkgs.ggwave}";
LD_LIBRARY_PATH = "${pkgs.ggwave}/lib";
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
};
};
}

375
src/lib.rs Normal file
View file

@ -0,0 +1,375 @@
use std::{ffi::CString, path::Path};
use bitflags::bitflags;
pub use ggwave_sys as ffi;
type Str = &'static str;
#[derive(Debug)]
pub enum Error {
InitError,
EncodeError(Str),
DecodeError(Str),
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::InitError => write!(f, "Failed to initalize GGWave"),
Error::EncodeError(message) => write!(f, "Encode error: {}", message),
Error::DecodeError(message) => write!(f, "Decode error: {}", message),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SampleFormat {
Undefined,
U8,
I8,
U16,
I16,
F32,
}
impl From<u32> for SampleFormat {
fn from(value: u32) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<SampleFormat> for u32 {
fn from(value: SampleFormat) -> Self {
unsafe { std::mem::transmute(value) }
}
}
#[cfg(feature = "less_protocls")]
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ProtocolId {
DtNormal,
DtFast,
DtFastest,
MtNormal,
MtFast,
MtFastest,
}
#[cfg(not(feature = "less_protocls"))]
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ProtocolId {
AudibleNormal,
AudibleFast,
AudibleFastest,
UltrasoundNormal,
UltrasoundFast,
UltrasoundFastest,
DtNormal,
DtFast,
DtFastest,
MtNormal,
MtFast,
MtFastest,
}
impl Default for ProtocolId {
#[cfg(feature = "less_protocls")]
fn default() -> Self {
ProtocolId::DtFast
}
#[cfg(not(feature = "less_protocls"))]
fn default() -> Self {
ProtocolId::AudibleFast
}
}
impl From<u32> for ProtocolId {
fn from(value: u32) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<ProtocolId> for u32 {
fn from(value: ProtocolId) -> Self {
unsafe { std::mem::transmute(value) }
}
}
bitflags! {
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct OperatingMode: u32 {
const RX = ffi::GGWAVE_OPERATING_MODE_RX;
const TX = ffi::GGWAVE_OPERATING_MODE_TX;
const RX_AND_TX = ffi::GGWAVE_OPERATING_MODE_RX_AND_TX;
const TX_ONLY_TONES = ffi::GGWAVE_OPERATING_MODE_TX_ONLY_TONES;
const USE_DSS = ffi::GGWAVE_OPERATING_MODE_USE_DSS;
}
}
#[derive(Debug, Copy, Clone)]
pub struct Parameters(ffi::ggwave_Parameters);
impl Parameters {
pub fn payload_length(&self) -> i32 {
self.0.payloadLength
}
pub fn set_payload_length(&mut self, length: i32) {
self.0.payloadLength = length;
}
pub fn sample_rate_in(&self) -> f32 {
self.0.sampleRateInp
}
pub fn set_sample_rate_in(&mut self, rate: f32) {
self.0.sampleRateInp = rate;
}
pub fn sample_rate_out(&self) -> f32 {
self.0.sampleRateOut
}
pub fn set_sample_rate_out(&mut self, rate: f32) {
self.0.sampleRateOut = rate;
}
pub fn sample_rate(&self) -> f32 {
self.0.sampleRate
}
pub fn set_sample_rate(&mut self, rate: f32) {
self.0.sampleRate = rate;
}
pub fn samples_per_frame(&self) -> i32 {
self.0.samplesPerFrame
}
pub fn set_samples_per_frame(&mut self, samples: i32) {
self.0.samplesPerFrame = samples
}
pub fn sound_marker_threshold(&self) -> f32 {
self.0.soundMarkerThreshold
}
pub fn set_sound_marker_threshold(&mut self, threshold: f32) {
self.0.soundMarkerThreshold = threshold;
}
pub fn sample_format_in(&self) -> SampleFormat {
SampleFormat::from(self.0.sampleFormatInp)
}
pub fn set_sample_format_in(&mut self, format: SampleFormat) {
self.0.sampleFormatInp = format as u32;
}
pub fn sample_format_out(&self) -> SampleFormat {
SampleFormat::from(self.0.sampleFormatOut)
}
pub fn set_sample_format_out(&mut self, format: SampleFormat) {
self.0.sampleFormatOut = format as u32;
}
pub fn operating_mode(&self) -> OperatingMode {
OperatingMode::from_bits(self.0.operatingMode as u32).unwrap()
}
pub fn set_operating_mode(&mut self, operating_mode: OperatingMode) {
self.0.operatingMode = operating_mode.bits() as i32;
}
}
impl Default for Parameters {
fn default() -> Self {
unsafe { Parameters(ffi::ggwave_getDefaultParameters()) }
}
}
pub fn set_log_file(file: Option<&Path>) {
unsafe {
let file_ptr = if let Some(file) = file {
let file = CString::new(file.to_str().unwrap()).unwrap();
libc::fopen(file.as_ptr(), c"w".as_ptr())
} else {
std::ptr::null_mut()
};
ffi::GGWave_setLogFile(file_ptr as *mut _);
}
}
#[derive(Debug)]
pub struct GGWave(ffi::GGWave);
impl GGWave {
pub fn new(parameters: Parameters) -> Self {
unsafe { GGWave(ffi::GGWave::new(&parameters.0 as *const _)) }
}
pub fn builder() -> GGWaveBuilder {
GGWaveBuilder::new()
}
pub fn encode(&mut self, payload: &[u8], protocol_id: ProtocolId) -> Result<Vec<u8>> {
unsafe {
if !ffi::GGWave_init1(
&mut self.0,
payload.len() as i32,
payload.as_ptr() as *const _,
protocol_id as u32,
100,
) {
return Err(Error::InitError);
}
let mut waveform = Vec::new();
waveform.resize(ffi::GGWave_encodeSize_bytes(&self.0) as usize, 0);
let n_bytes = ffi::GGWave_encode(&mut self.0);
if n_bytes == 0 {
return Err(Error::EncodeError("Failed to encode"));
}
std::ptr::copy_nonoverlapping(
ffi::GGWave_txWaveform(&self.0),
waveform.as_mut_ptr() as *mut _,
n_bytes as usize,
);
Ok(waveform)
}
}
pub fn decode(&mut self, waveform: &[u8]) -> Result<Vec<u8>> {
unsafe {
if !ffi::GGWave_decode(
&mut self.0,
waveform.as_ptr() as *const _,
waveform.len() as u32,
) {
return Err(Error::DecodeError("Failed to decode"));
}
let mut payload = ffi::ggvector::default();
if ffi::GGWave_rxTakeData(&mut self.0, &mut payload) <= 0 {
return Err(Error::DecodeError("Failed to decode"));
}
Ok(std::slice::from_raw_parts(payload.m_data, payload.m_size as usize).to_vec())
}
}
}
impl Default for GGWave {
fn default() -> Self {
GGWave::new(Parameters::default())
}
}
unsafe impl Send for GGWave {}
unsafe impl Sync for GGWave {}
pub struct GGWaveBuilder {
params: Parameters,
}
impl GGWaveBuilder {
pub fn new() -> Self {
GGWaveBuilder {
params: Parameters::default(),
}
}
pub fn payload_length(mut self, length: i32) -> Self {
self.params.set_payload_length(length);
self
}
pub fn sample_rate_in(mut self, rate: f32) -> Self {
self.params.set_sample_rate_in(rate);
self
}
pub fn sample_rate_out(mut self, rate: f32) -> Self {
self.params.set_sample_rate_out(rate);
self
}
pub fn sample_rate(mut self, rate: f32) -> Self {
self.params.set_sample_rate(rate);
self
}
pub fn samples_per_frame(mut self, samples: i32) -> Self {
self.params.set_samples_per_frame(samples);
self
}
pub fn sound_marker_threshold(mut self, threshold: f32) -> Self {
self.params.set_sound_marker_threshold(threshold);
self
}
pub fn sample_format_in(mut self, format: SampleFormat) -> Self {
self.params.set_sample_format_in(format);
self
}
pub fn sample_format_out(mut self, format: SampleFormat) -> Self {
self.params.set_sample_format_out(format);
self
}
pub fn operating_mode(mut self, mode: OperatingMode) -> Self {
self.params.set_operating_mode(mode);
self
}
pub fn build(self) -> GGWave {
GGWave::new(self.params)
}
}
#[cfg(test)]
mod tests {
use crate as ggwave;
use ggwave::GGWave;
#[test]
fn test_encode() {
ggwave::set_log_file(None);
let mut ggwave = GGWave::default();
let waveform = ggwave
.encode(b"test", ggwave::ProtocolId::default())
.expect("Failed to encode");
assert!(!waveform.is_empty());
}
#[test]
fn test_decode() {
ggwave::set_log_file(None);
let mut ggwave = GGWave::default();
let waveform = ggwave
.encode(b"test", ggwave::ProtocolId::default())
.expect("Failed to encode");
let payload = ggwave.decode(&waveform).expect("Failed to decode");
assert_eq!(b"test", payload.as_slice());
}
}

18
sys/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "ggwave-sys"
version = "0.0.1"
edition.workspace = true
build = "build.rs"
[dependencies]
libc = "0.2"
[build-dependencies]
bindgen = "0.72"
[dev-dependencies]
shell-words = "1.0.0"
tempfile = "3"
[features]
default = []

27
sys/build.rs Normal file
View file

@ -0,0 +1,27 @@
use std::{env, path::Path};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
// TODO: add better error handling for when GGWAVE_PATH is not set
let ggwave_path = env::var("GGWAVE_PATH").unwrap();
let ggwave_path = Path::new(&ggwave_path);
let ggwave_lib_dir = ggwave_path.join("lib");
let ggwave_include_dir = ggwave_path.join("include");
println!("cargo:rustc-link-search=native={ggwave_lib_dir:?}");
println!("cargo:rustc-link-lib=ggwave");
let bindings = bindgen::Builder::default()
.header(ggwave_include_dir.join("ggwave/ggwave.h").to_string_lossy())
.clang_args(["-x", "c++"])
.derive_debug(true)
.derive_default(true)
.generate()
.unwrap();
let bindings_path = out_dir.join("bindings.rs");
bindings.write_to_file(&bindings_path).unwrap();
}

4
sys/src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
#![allow(clippy::missing_safety_doc)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));