diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 138b211..43e8b79 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -36,8 +36,10 @@ jobs: "docs/troubleshooting.md:docs/troubleshooting_RU.md" "docs/roadmap.md:docs/roadmap_RU.md" "docs/research/acoustic-1-phy-sources.md:docs/research/acoustic-1-phy-sources_RU.md" + "docs/research/acoustic-2-measurement-sources.md:docs/research/acoustic-2-measurement-sources_RU.md" "spec/protocol-v1.md:spec/protocol-v1_RU.md" "spec/acoustic-1.md:spec/acoustic-1_RU.md" + "spec/acoustic-2.md:spec/acoustic-2_RU.md" ) for pair in "${pairs[@]}"; do source_path="${pair%%:*}" diff --git a/crates/audio-modem-core/src/acoustic2.rs b/crates/audio-modem-core/src/acoustic2.rs new file mode 100644 index 0000000..edaabdc --- /dev/null +++ b/crates/audio-modem-core/src/acoustic2.rs @@ -0,0 +1,290 @@ +//! Experimental Acoustic-2 controlled PCM measurement harness. +//! +//! This module applies declared deterministic sample-domain transforms around +//! Acoustic-1 WAV decoding. It does not represent a live audio route, a device, +//! SNR/BER measurement, timing-recovery loop, or acoustic interoperability. + +use std::fmt; + +use adlp_protocol::TransferProfile; + +use crate::{acoustic1, make_canonical_wav, parse_canonical_wav, CodecError, SAMPLE_RATE_HZ}; + +pub const MAX_LEADING_SILENCE_SAMPLES: usize = acoustic1::MAX_SYNC_OFFSET_SAMPLES; +pub const MAX_NOISE_PEAK: i16 = 1_000; + +/// A reproducible integer-domain transform applied to an Acoustic-1 PCM/WAV. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PcmImpairment { + pub leading_silence_samples: usize, + pub gain_per_mille: u16, + pub noise_peak: i16, + pub noise_seed: u32, + pub clip_abs: Option, + pub drop_every_nth_sample: Option, +} + +impl Default for PcmImpairment { + fn default() -> Self { + Self { + leading_silence_samples: 0, + gain_per_mille: 1_000, + noise_peak: 0, + noise_seed: 0, + clip_abs: None, + drop_every_nth_sample: None, + } + } +} + +/// Codec-observable output from a named controlled PCM measurement. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Acoustic2Measurement { + pub sample_rate_hz: u32, + pub input_samples: usize, + pub output_samples: usize, + pub dropped_samples: usize, + pub leading_silence_samples: usize, + pub acquisition_offset_samples: usize, + pub samples_consumed: usize, + pub profile: TransferProfile, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Acoustic2Error { + Codec(CodecError), + Acoustic1(acoustic1::Acoustic1Error), + LeadingSilenceOutOfRange, + GainOutOfRange, + NoiseOutOfRange, + ClipOutOfRange, + DropIntervalOutOfRange, +} + +impl fmt::Display for Acoustic2Error { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Codec(error) => write!(formatter, "WAV error: {error}"), + Self::Acoustic1(error) => write!(formatter, "Acoustic-1 error: {error}"), + Self::LeadingSilenceOutOfRange => write!( + formatter, + "leading silence exceeds Acoustic-1 bounded acquisition window" + ), + Self::GainOutOfRange => write!(formatter, "gain must be between 1 and 1,000 per mille"), + Self::NoiseOutOfRange => write!(formatter, "noise peak must be between 0 and 1,000"), + Self::ClipOutOfRange => { + write!(formatter, "clip threshold must be between 1 and 32,767") + } + Self::DropIntervalOutOfRange => { + write!(formatter, "sample drop interval must be at least two") + } + } + } +} + +impl std::error::Error for Acoustic2Error {} + +impl From for Acoustic2Error { + fn from(error: CodecError) -> Self { + Self::Codec(error) + } +} + +impl From for Acoustic2Error { + fn from(error: acoustic1::Acoustic1Error) -> Self { + Self::Acoustic1(error) + } +} + +/// Applies a declared controlled PCM impairment and measures Acoustic-1 decode. +pub fn measure_acoustic1_wav( + wav: &[u8], + impairment: &PcmImpairment, +) -> Result { + validate_impairment(impairment)?; + let (sample_rate_hz, samples) = parse_canonical_wav(wav)?; + if sample_rate_hz != SAMPLE_RATE_HZ { + return Err(Acoustic2Error::Codec(CodecError::InvalidWav( + "sample rate must be 48 kHz", + ))); + } + let (transformed, dropped_samples) = apply_impairment(&samples, impairment); + let transformed_wav = make_canonical_wav(&transformed)?; + let decoded = acoustic1::decode_wav(&transformed_wav)?; + Ok(Acoustic2Measurement { + sample_rate_hz, + input_samples: samples.len(), + output_samples: transformed.len(), + dropped_samples, + leading_silence_samples: impairment.leading_silence_samples, + acquisition_offset_samples: decoded.frame_start_candidate_samples, + samples_consumed: decoded.samples_consumed, + profile: decoded.object.manifest.profile, + }) +} + +fn validate_impairment(impairment: &PcmImpairment) -> Result<(), Acoustic2Error> { + if impairment.leading_silence_samples > MAX_LEADING_SILENCE_SAMPLES { + return Err(Acoustic2Error::LeadingSilenceOutOfRange); + } + if impairment.gain_per_mille == 0 || impairment.gain_per_mille > 1_000 { + return Err(Acoustic2Error::GainOutOfRange); + } + if !(0..=MAX_NOISE_PEAK).contains(&impairment.noise_peak) { + return Err(Acoustic2Error::NoiseOutOfRange); + } + if impairment.clip_abs.is_some_and(|clip| clip <= 0) { + return Err(Acoustic2Error::ClipOutOfRange); + } + if impairment + .drop_every_nth_sample + .is_some_and(|interval| interval < 2) + { + return Err(Acoustic2Error::DropIntervalOutOfRange); + } + Ok(()) +} + +fn apply_impairment(samples: &[i16], impairment: &PcmImpairment) -> (Vec, usize) { + let mut state = impairment.noise_seed; + let mut transformed = Vec::with_capacity(samples.len() + impairment.leading_silence_samples); + let mut dropped_samples = 0; + for (index, sample) in samples.iter().enumerate() { + let mut value = (i32::from(*sample) * i32::from(impairment.gain_per_mille)) / 1_000; + state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); + if impairment.noise_peak != 0 { + let span = u32::try_from(i32::from(impairment.noise_peak) * 2 + 1) + .expect("validated noise span fits u32"); + value += i32::try_from(state % span).expect("noise sample fits i32") + - i32::from(impairment.noise_peak); + } + if let Some(clip) = impairment.clip_abs { + value = value.clamp(-i32::from(clip), i32::from(clip)); + } + if impairment + .drop_every_nth_sample + .is_some_and(|interval| (index + 1) % interval == 0) + { + dropped_samples += 1; + continue; + } + transformed.push(value.clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16); + } + if impairment.leading_silence_samples != 0 { + let mut with_silence = vec![0; impairment.leading_silence_samples]; + with_silence.extend(transformed); + return (with_silence, dropped_samples); + } + (transformed, dropped_samples) +} + +#[cfg(test)] +mod tests { + use super::*; + use adlp_protocol::WireObject; + + const ACOUSTIC1_GOLDEN_WAV: &[u8] = + include_bytes!("../tests/fixtures/acoustic-1-v1-text-balanced.wav"); + + fn test_wav() -> Vec { + let object = WireObject::text( + 91, + "AC2", + "Acoustic-2 measurement", + TransferProfile::Balanced, + ) + .unwrap(); + acoustic1::encode_wav(&object).unwrap() + } + + #[test] + fn baseline_reports_acoustic1_observables() { + let measurement = measure_acoustic1_wav(&test_wav(), &PcmImpairment::default()).unwrap(); + assert_eq!(measurement.sample_rate_hz, SAMPLE_RATE_HZ); + assert_eq!(measurement.acquisition_offset_samples, 0); + assert_eq!(measurement.dropped_samples, 0); + assert_eq!(measurement.profile, TransferProfile::Balanced); + assert!(measurement.samples_consumed > 0); + } + + #[test] + fn bounded_silence_attenuation_and_seeded_noise_are_measured() { + let impairment = PcmImpairment { + leading_silence_samples: 137, + gain_per_mille: 500, + noise_peak: 200, + noise_seed: 0xAC02_0001, + clip_abs: Some(8_000), + drop_every_nth_sample: None, + }; + let measurement = measure_acoustic1_wav(&test_wav(), &impairment).unwrap(); + assert_eq!(measurement.leading_silence_samples, 137); + assert!(measurement.acquisition_offset_samples <= 137); + assert_eq!(measurement.profile, TransferProfile::Balanced); + } + + #[test] + fn periodic_sample_deletion_is_rejected_in_the_declared_vector() { + let impairment = PcmImpairment { + drop_every_nth_sample: Some(113), + ..PcmImpairment::default() + }; + assert!(measure_acoustic1_wav(&test_wav(), &impairment).is_err()); + } + + #[test] + fn bounds_are_rejected_before_wav_transforms() { + let leading = PcmImpairment { + leading_silence_samples: MAX_LEADING_SILENCE_SAMPLES + 1, + ..PcmImpairment::default() + }; + assert_eq!( + measure_acoustic1_wav(&test_wav(), &leading).unwrap_err(), + Acoustic2Error::LeadingSilenceOutOfRange + ); + let invalid_drop = PcmImpairment { + drop_every_nth_sample: Some(1), + ..PcmImpairment::default() + }; + assert_eq!( + measure_acoustic1_wav(&test_wav(), &invalid_drop).unwrap_err(), + Acoustic2Error::DropIntervalOutOfRange + ); + } + + #[test] + fn seeded_transform_is_byte_deterministic() { + let (_, source_samples) = parse_canonical_wav(&test_wav()).unwrap(); + let impairment = PcmImpairment { + noise_peak: 200, + noise_seed: 42, + clip_abs: Some(9_000), + ..PcmImpairment::default() + }; + assert_eq!( + apply_impairment(&source_samples, &impairment), + apply_impairment(&source_samples, &impairment) + ); + } + + #[test] + fn golden_measurement_matches_acoustic1_fixture_contract() { + let impairment = PcmImpairment { + leading_silence_samples: 137, + gain_per_mille: 500, + noise_peak: 200, + noise_seed: 2_885_812_225, + clip_abs: Some(8_000), + drop_every_nth_sample: None, + }; + let measurement = measure_acoustic1_wav(ACOUSTIC1_GOLDEN_WAV, &impairment).unwrap(); + assert_eq!(measurement.sample_rate_hz, SAMPLE_RATE_HZ); + assert_eq!(measurement.input_samples, 358_560); + assert_eq!(measurement.output_samples, 358_697); + assert_eq!(measurement.dropped_samples, 0); + assert_eq!(measurement.leading_silence_samples, 137); + assert_eq!(measurement.acquisition_offset_samples, 21); + assert_eq!(measurement.samples_consumed, 358_581); + assert_eq!(measurement.profile, TransferProfile::Balanced); + } +} diff --git a/crates/audio-modem-core/src/lib.rs b/crates/audio-modem-core/src/lib.rs index 1c10e7e..3ca45cb 100644 --- a/crates/audio-modem-core/src/lib.rs +++ b/crates/audio-modem-core/src/lib.rs @@ -5,6 +5,7 @@ //! profile can replace this simple symbol mapper without changing ADLP bytes. pub mod acoustic1; +pub mod acoustic2; use std::fmt; @@ -177,7 +178,8 @@ pub(crate) fn parse_canonical_wav(wav: &[u8]) -> Result<(u32, Vec), CodecEr return Err(CodecError::TruncatedSignal); } let samples = wav[44..end] - .chunks_exact(2) + // `length & 1` was checked above, so every two-byte chunk is a PCM sample. + .chunks(2) .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]])) .collect(); Ok((le_u32(&wav[24..28])?, samples)) diff --git a/crates/audio-modem-core/tests/fixtures/README.md b/crates/audio-modem-core/tests/fixtures/README.md index 70c8dbf..609d9a4 100644 --- a/crates/audio-modem-core/tests/fixtures/README.md +++ b/crates/audio-modem-core/tests/fixtures/README.md @@ -31,3 +31,22 @@ The Rust regression test both decodes this immutable fixture and regenerates it | SHA-256 | `bbceae6d334284ede97ebe8113293fee53ea3e5e55822715802aa2a7819bc29b` | The Acoustic-1 test decodes the fixture, confirms the fixed ADLP object and regenerates the complete WAV byte-for-byte. A fixture change is therefore a carrier compatibility change. It requires an RFC update, an updated hash, a documented measurement rationale and a release note; it does not by itself establish live acoustic interoperability. + +## Acoustic-2 controlled measurement contract + +Acoustic-2 does not add another waveform fixture. Its first golden measurement is a fixed transform of `acoustic-1-v1-text-balanced.wav`, keeping the carrier fixture immutable and making the exact sample-domain assumptions reviewable. + +| Field | Canonical value | +| --- | --- | +| Input fixture | `acoustic-1-v1-text-balanced.wav` | +| Leading silence | `137` samples | +| Gain | `500` per mille | +| Additive noise | peak `200`, seed `2885812225` | +| Hard clip | `8000` | +| Periodic sample deletion | none | +| Expected output samples | `358,697` | +| Expected acquisition offset | `21` samples | +| Expected consumed samples | `358,581` | +| Expected ADLP profile | `balanced` | + +The Rust test applies this transform and compares every listed result. These values are a deterministic codec/harness contract, not an acoustic-channel quality metric or device result. diff --git a/docs/README.md b/docs/README.md index e6b9a58..4e54d87 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,8 +15,10 @@ This directory holds the human-readable technical documentation for AudioModem. | Architecture | [Flutter ↔ Rust WAV bridge](architecture/flutter-rust-bridge.md) | [Flutter ↔ Rust WAV bridge на русском](architecture/flutter-rust-bridge_RU.md) | Native facade and in-memory WAV verification boundary | | Research | [Flutter Rust Bridge integration](research/flutter-rust-bridge-integration.md) | [Flutter Rust Bridge integration на русском](research/flutter-rust-bridge-integration_RU.md) | Generated-code layout and regeneration command | | Research | [Acoustic-1 PHY sources](research/acoustic-1-phy-sources.md) | [Источники Acoustic-1 PHY](research/acoustic-1-phy-sources_RU.md) | Design sources and explicit receiver/FEC constraints | +| Research | [Acoustic-2 measurement sources](research/acoustic-2-measurement-sources.md) | [Источники измерений Acoustic-2](research/acoustic-2-measurement-sources_RU.md) | Controlled PCM transform and timing-acquisition constraints | | Specification | [ADLP v1](../spec/protocol-v1.md) | [ADLP v1 на русском](../spec/protocol-v1_RU.md) | Normative wire object and WAV bootstrap carrier | | Specification | [Acoustic-1](../spec/acoustic-1.md) | [Acoustic-1 на русском](../spec/acoustic-1_RU.md) | Experimental B-FSK carrier and compatibility boundary | +| Specification | [Acoustic-2](../spec/acoustic-2.md) | [Acoustic-2 на русском](../spec/acoustic-2_RU.md) | Experimental controlled PCM measurement contract | ## Translation convention diff --git a/docs/README_RU.md b/docs/README_RU.md index d5314f2..e83ac90 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -17,8 +17,10 @@ | Architecture | [Flutter ↔ Rust WAV bridge](architecture/flutter-rust-bridge.md) | [Flutter ↔ Rust WAV bridge на русском](architecture/flutter-rust-bridge_RU.md) | Граница native facade и проверки WAV в памяти | | Research | [Flutter Rust Bridge integration](research/flutter-rust-bridge-integration.md) | [Flutter Rust Bridge integration на русском](research/flutter-rust-bridge-integration_RU.md) | Структура generated code и команда повторной генерации | | Research | [Acoustic-1 PHY sources](research/acoustic-1-phy-sources.md) | [Источники Acoustic-1 PHY](research/acoustic-1-phy-sources_RU.md) | Источники дизайна и явные receiver/FEC constraints | +| Research | [Acoustic-2 measurement sources](research/acoustic-2-measurement-sources.md) | [Источники измерений Acoustic-2](research/acoustic-2-measurement-sources_RU.md) | Ограничения controlled PCM transforms и timing acquisition | | Specification | [ADLP v1](../spec/protocol-v1.md) | [ADLP v1 на русском](../spec/protocol-v1_RU.md) | Нормативный wire object и WAV bootstrap carrier | | Specification | [Acoustic-1](../spec/acoustic-1.md) | [Acoustic-1 на русском](../spec/acoustic-1_RU.md) | Экспериментальный B-FSK carrier и граница compatibility | +| Specification | [Acoustic-2](../spec/acoustic-2.md) | [Acoustic-2 на русском](../spec/acoustic-2_RU.md) | Экспериментальный controlled PCM measurement contract | ## Правило поддержки перевода diff --git a/docs/guides/audio-routes.md b/docs/guides/audio-routes.md index 32eacf4..d12ffe4 100644 --- a/docs/guides/audio-routes.md +++ b/docs/guides/audio-routes.md @@ -30,9 +30,15 @@ The current Flutter workbench can save a verified in-memory WAV transfer through The adapter relies on `file_picker`, whose documented API supports custom extension filters, byte reads and save-file dialogs across Android, iOS, Linux, macOS, Windows and web.[1] The adapter makes no claim that a file can be played over a speaker, captured from a microphone, routed through Bluetooth, or received from an audio cable. +## Acoustic-2 controlled measurements + +Acoustic-2 is not an extra row in the route matrix. It is a repository-only measurement layer that parses an Acoustic-1 WAV, applies a declared integer PCM transform, re-encodes canonical WAV and calls the same Acoustic-1 decoder. Its result contains only input/output sample counts, dropped samples, bounded acquisition offset, consumed samples and decoded ADLP profile after a successful decode. The full order, parameter bounds and non-goals are fixed by the [Acoustic-2 contract](../../spec/acoustic-2.md). + +Leading silence, attenuation, seeded additive noise, hard clipping and fixed periodic sample deletion are reproducible transform inputs, not measurements of a real channel. A result does not state SNR, BER, range, room-noise tolerance, clock drift, device behavior or live-route readiness. + ## Golden compatibility fixture -`crates/audio-modem-core/tests/fixtures/adlp-v1-text-balanced.wav` is a fixed canonical fixture for ADLP v1 WAV bootstrap. `crates/audio-modem-core/tests/fixtures/acoustic-1-v1-text-balanced.wav` performs the same role for the experimental Acoustic-1 carrier. Their Rust regression tests decode the fixtures and compare whole byte sequences with fresh deterministic encodings of documented input objects. Any byte change is therefore a compatibility-affecting codec change and must be reviewed with an updated fixture, hash and protocol rationale. +`crates/audio-modem-core/tests/fixtures/adlp-v1-text-balanced.wav` is a fixed canonical fixture for ADLP v1 WAV bootstrap. `crates/audio-modem-core/tests/fixtures/acoustic-1-v1-text-balanced.wav` performs the same role for the experimental Acoustic-1 carrier. Their Rust regression tests decode the fixtures and compare whole byte sequences with fresh deterministic encodings of documented input objects. The same Acoustic-1 fixture also supplies the Acoustic-2 golden measurement vector, which locks declared transform parameters and codec-observable output values. Any byte or transform-result change is therefore a compatibility-affecting codec/harness change and must be reviewed with an updated fixture, hash or measurement rationale. ## Callsigns and privacy diff --git a/docs/guides/audio-routes_RU.md b/docs/guides/audio-routes_RU.md index 00e59a6..53d6873 100644 --- a/docs/guides/audio-routes_RU.md +++ b/docs/guides/audio-routes_RU.md @@ -32,9 +32,15 @@ route → PCM → selected PHY profile → ADLP frame → verified object Adapter использует `file_picker`: его документированный API поддерживает custom extension filters, чтение bytes и save-file dialogs на Android, iOS, Linux, macOS, Windows и web.[1] Adapter не утверждает, что файл можно воспроизвести через динамик, захватить с микрофона, направить по Bluetooth или получить через аудиокабель. +## Controlled measurements Acoustic-2 + +Acoustic-2 не является дополнительной строкой route matrix. Это repository-only measurement layer, который разбирает Acoustic-1 WAV, применяет declared integer PCM transform, повторно кодирует canonical WAV и вызывает тот же Acoustic-1 decoder. Его result содержит только input/output sample counts, dropped samples, bounded acquisition offset, consumed samples и decoded ADLP profile после successful decode. Полный порядок, parameter bounds и non-goals фиксирует [Acoustic-2 contract](../../spec/acoustic-2_RU.md). + +Leading silence, attenuation, seeded additive noise, hard clipping и fixed periodic sample deletion — воспроизводимые transform inputs, а не measurements real channel. Result не сообщает SNR, BER, range, room-noise tolerance, clock drift, device behavior или readiness live route. + ## Golden compatibility fixture -`crates/audio-modem-core/tests/fixtures/adlp-v1-text-balanced.wav` — фиксированный canonical fixture для ADLP v1 WAV bootstrap. `crates/audio-modem-core/tests/fixtures/acoustic-1-v1-text-balanced.wav` выполняет ту же роль для экспериментального Acoustic-1 carrier. Их Rust regression tests декодируют fixtures и сравнивают целые byte sequences со свежими deterministic encodings документированных входных objects. Поэтому любое различие байтов является изменением codec, влияющим на compatibility, и должно быть проверено вместе с обновлёнными fixture, hash и protocol rationale. +`crates/audio-modem-core/tests/fixtures/adlp-v1-text-balanced.wav` — фиксированный canonical fixture для ADLP v1 WAV bootstrap. `crates/audio-modem-core/tests/fixtures/acoustic-1-v1-text-balanced.wav` выполняет ту же роль для экспериментального Acoustic-1 carrier. Их Rust regression tests декодируют fixtures и сравнивают целые byte sequences со свежими deterministic encodings документированных входных objects. Тот же Acoustic-1 fixture также служит Acoustic-2 golden measurement vector, который фиксирует declared transform parameters и codec-observable output values. Поэтому любое изменение byte или transform-result является изменением codec/harness, влияющим на compatibility, и должно быть проверено вместе с updated fixture, hash или measurement rationale. ## Позывные и приватность diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index e7fb1f3..e10f347 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -45,14 +45,25 @@ cargo run -p adlp-cli -- decode-acoustic1 acoustic1.wav An Acoustic-1 WAV round trip proves controlled PCM/WAV codec behavior only. It does not prove speaker-to-microphone synchronisation, room-noise tolerance, gain handling, radio compatibility or any live route. +## Acoustic-2 controlled measurement + +Acoustic-2 applies declared integer-domain transforms to an existing Acoustic-1 WAV, then reports only what the existing codec can observe. The optional values below are leading silence, gain per mille, noise peak, noise seed and hard-clip threshold. + +```bash +cargo run -p adlp-cli -- measure-acoustic1 acoustic1.wav 137 500 200 2885812225 8000 +``` + +The command prints an accepted/rejected codec result, sample counts, bounded acquisition offset and ADLP profile. The numbers are reproducible for that exact WAV and parameter vector; they are not BER, SNR, range, room-noise, clock-drift or device compatibility metrics. See the [Acoustic-2 contract](../../spec/acoustic-2.md) for the full transform order and bounds. + ## What this proves—and what it does not | This test proves | This test does not prove | | --- | --- | | The implemented ADLP v1 object can survive one deterministic WAV encode/decode cycle. | That speech-band audio, a speaker, microphone, cable, Bluetooth adapter or radio can carry the waveform. | | The final integrity check detects the intentional corrupted-frame cases covered by the Rust tests; Acoustic-1 also has bounded noise, framing, FEC and golden-vector regression cases. | That a callsign belongs to a person, device or key. | +| An Acoustic-2 transform vector has deterministic PCM output and codec-observable acquisition values. | That synthetic gain/noise/clip/deletion values predict a physical audio channel. | | The test is portable between supported Rust development environments. | That a transmission is encrypted, confidential or authenticated. | ## Next steps -Read [Audio routes](audio-routes.md) to understand why WAV is the reference transport and what is required before a live route becomes supported. Read the [ADLP v1 specification](../../spec/protocol-v1.md) for the object layout and the [Acoustic-1 RFC](../../spec/acoustic-1.md) for experimental carrier compatibility. +Read [Audio routes](audio-routes.md) to understand why WAV is the reference transport and what is required before a live route becomes supported. Read the [ADLP v1 specification](../../spec/protocol-v1.md) for the object layout, the [Acoustic-1 RFC](../../spec/acoustic-1.md) for experimental carrier compatibility and the [Acoustic-2 contract](../../spec/acoustic-2.md) for controlled measurements. diff --git a/docs/guides/getting-started_RU.md b/docs/guides/getting-started_RU.md index fa9d263..25d055e 100644 --- a/docs/guides/getting-started_RU.md +++ b/docs/guides/getting-started_RU.md @@ -47,14 +47,25 @@ cargo run -p adlp-cli -- decode-acoustic1 acoustic1.wav Acoustic-1 WAV round trip доказывает только controlled PCM/WAV codec behavior. Он не доказывает speaker-to-microphone synchronisation, tolerance room noise, gain handling, radio compatibility или любой live route. +## Controlled measurement Acoustic-2 + +Acoustic-2 применяет declared integer-domain transforms к существующему Acoustic-1 WAV, затем сообщает только то, что может наблюдать существующий codec. Optional values ниже — leading silence, gain per mille, noise peak, noise seed и hard-clip threshold. + +```bash +cargo run -p adlp-cli -- measure-acoustic1 acoustic1.wav 137 500 200 2885812225 8000 +``` + +Команда выводит accepted/rejected codec result, sample counts, bounded acquisition offset и ADLP profile. Числа воспроизводимы для этого exact WAV и parameter vector; они не являются BER, SNR, range, room-noise, clock-drift или device compatibility metrics. Полные transform order и bounds приведены в [Acoustic-2 contract](../../spec/acoustic-2_RU.md). + ## Что доказывает тест — и чего не доказывает | Тест доказывает | Тест не доказывает | | --- | --- | | Реализованный ADLP v1 object переживает один детерминированный WAV encode/decode cycle. | Что waveform пройдёт через speech-band audio, динамик, микрофон, кабель, Bluetooth adapter или радио. | | Final integrity check обнаруживает intentional corrupted-frame cases из Rust-тестов; Acoustic-1 также имеет bounded noise, framing, FEC и golden-vector regression cases. | Что callsign принадлежит человеку, устройству или ключу. | +| Acoustic-2 transform vector имеет deterministic PCM output и codec-observable acquisition values. | Что synthetic gain/noise/clip/deletion values предсказывают physical audio channel. | | Тест переносим между Rust development environments. | Что передача зашифрована, конфиденциальна или authenticated. | ## Следующие шаги -Прочитайте [Аудиомаршруты](audio-routes_RU.md), чтобы понять, почему WAV является reference transport и что требуется до статуса supported для live route. Формат объекта описан в [спецификации ADLP v1](../../spec/protocol-v1_RU.md), а experimental carrier compatibility — в [Acoustic-1 RFC](../../spec/acoustic-1_RU.md). +Прочитайте [Аудиомаршруты](audio-routes_RU.md), чтобы понять, почему WAV является reference transport и что требуется до статуса supported для live route. Формат объекта описан в [спецификации ADLP v1](../../spec/protocol-v1_RU.md), experimental carrier compatibility — в [Acoustic-1 RFC](../../spec/acoustic-1_RU.md), а controlled measurements — в [Acoustic-2 contract](../../spec/acoustic-2_RU.md). diff --git a/docs/reference/platform-support.md b/docs/reference/platform-support.md index 8a7d991..8a8d670 100644 --- a/docs/reference/platform-support.md +++ b/docs/reference/platform-support.md @@ -1,23 +1,23 @@ # Platform support -**Last reviewed:** 2026-08-19 · **English (canonical)** · [Русский](platform-support_RU.md) +**Last reviewed:** 2026-08-20 · **English (canonical)** · [Русский](platform-support_RU.md) AudioModem keeps Flutter runners for Android, iOS, Windows, macOS, Linux and Web in one application directory. The Rust workspace supplies the container and WAV bootstrap codec. This structure is an implementation target, not a claim that every platform currently supports every route. ## Status matrix -| Platform | Flutter runner | Rust/WAV integration | Live audio | Notes | -| --- | --- | --- | --- | --- | -| Android | Scaffolded | Not connected | Planned | Requires microphone permission and platform route adapter. | -| iOS | Scaffolded | Not connected | Planned | Requires microphone permission and platform route adapter. | -| Windows | Scaffolded | Not connected | Planned | Requires native device enumeration and routing tests. | -| macOS | Scaffolded | Not connected | Planned | Requires native device enumeration and routing tests. | -| Linux | Scaffolded | Not connected | Planned | Requires backend selection and desktop audio tests. | -| Web | Scaffolded | Not connected | Research | Requires WASM codec integration and browser Web Audio constraints. | +| Platform | Flutter runner | Rust/WAV integration | Local WAV workflow | Live audio | Notes | +| --- | --- | --- | --- | --- | --- | +| Android | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires target build, permission and route-adapter acceptance tests. | +| iOS | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires target build, permission and route-adapter acceptance tests. | +| Windows | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires native device enumeration and routing tests. | +| macOS | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires native device enumeration and routing tests. | +| Linux | Debug bundle verified | Native Cargokit/Rust bridge build verified | Source implementation; dialog interaction not acceptance-tested | Planned | Requires backend selection, runtime dialog and desktop audio tests. | +| Web | Release build verified | Unavailable by design: no WASM codec | Picker UI can build; native Rust decode is unavailable | Research | Requires WASM codec integration and browser Web Audio constraints. | ## Interpretation -“Scaffolded” means the Flutter project has a platform runner and the UI can be built for that target. It does **not** mean the Rust codec, local file handling, microphone permission, playback, Bluetooth routing or acoustic transmission is available. A row becomes “supported” only when a reproducible build, automated checks, a documented compatibility note and a route-specific acceptance test exist. +“Scaffolded” means the Flutter project has a platform runner. “Bridge source integrated” means the shared Flutter/Rust source contains the bridge and file workflow; it is not a target runtime claim. Linux is the only native target with a verified debug bundle in this repository. No row implies microphone permission, playback, Bluetooth routing, device compatibility or acoustic transmission. A route becomes “supported” only when a reproducible build, automated checks, a documented compatibility note and a route-specific acceptance test exist. ## Compatibility reports diff --git a/docs/reference/platform-support_RU.md b/docs/reference/platform-support_RU.md index e11680a..66bd0ee 100644 --- a/docs/reference/platform-support_RU.md +++ b/docs/reference/platform-support_RU.md @@ -2,24 +2,24 @@ [English (canonical)](platform-support.md) · **Русский перевод** -> **Translation of:** [docs/reference/platform-support.md](platform-support.md). **Last synced:** 2026-08-19. +> **Translation of:** [docs/reference/platform-support.md](platform-support.md). **Last synced:** 2026-08-20. AudioModem хранит Flutter runners для Android, iOS, Windows, macOS, Linux и Web в одном application directory. Rust workspace предоставляет container и WAV bootstrap codec. Эта структура — implementation target, а не утверждение, что каждая платформа уже поддерживает каждый route. ## Матрица статуса -| Платформа | Flutter runner | Rust/WAV integration | Live audio | Примечание | -| --- | --- | --- | --- | --- | -| Android | Scaffolded | Не подключён | Планируется | Требуются microphone permission и platform route adapter. | -| iOS | Scaffolded | Не подключён | Планируется | Требуются microphone permission и platform route adapter. | -| Windows | Scaffolded | Не подключён | Планируется | Требуются native device enumeration и routing tests. | -| macOS | Scaffolded | Не подключён | Планируется | Требуются native device enumeration и routing tests. | -| Linux | Scaffolded | Не подключён | Планируется | Требуются backend selection и desktop audio tests. | -| Web | Scaffolded | Не подключён | Исследование | Требуются WASM codec integration и browser Web Audio constraints. | +| Платформа | Flutter runner | Rust/WAV integration | Локальный WAV workflow | Live audio | Примечание | +| --- | --- | --- | --- | --- | --- | +| Android | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны target build, permission и route-adapter acceptance tests. | +| iOS | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны target build, permission и route-adapter acceptance tests. | +| Windows | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны native device enumeration и routing tests. | +| macOS | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны native device enumeration и routing tests. | +| Linux | Debug bundle проверен | Native Cargokit/Rust bridge build проверен | Source implementation; dialog interaction не acceptance-tested | Планируется | Нужны backend selection, runtime dialog и desktop audio tests. | +| Web | Release build проверен | Unavailable by design: нет WASM codec | Picker UI может собраться; native Rust decode недоступен | Исследование | Нужны WASM codec integration и browser Web Audio constraints. | ## Интерпретация -“Scaffolded” означает, что Flutter project содержит platform runner и UI можно собрать для данного target. Это **не** означает, что доступны Rust codec, local file handling, microphone permission, playback, Bluetooth routing или acoustic transmission. Строка станет “supported” только после reproducible build, automated checks, documented compatibility note и route-specific acceptance test. +“Scaffolded” означает, что Flutter project содержит platform runner. “Bridge source integrated” означает, что общий Flutter/Rust source содержит bridge и file workflow; это не target runtime claim. Linux — единственный native target с verified debug bundle в этом репозитории. Ни одна строка не означает microphone permission, playback, Bluetooth routing, device compatibility или acoustic transmission. Route получает статус “supported” только после reproducible build, automated checks, documented compatibility note и route-specific acceptance test. ## Compatibility reports diff --git a/docs/research/acoustic-2-measurement-sources.md b/docs/research/acoustic-2-measurement-sources.md new file mode 100644 index 0000000..a285226 --- /dev/null +++ b/docs/research/acoustic-2-measurement-sources.md @@ -0,0 +1,21 @@ +# Acoustic-2 measurement research notes + +**Last reviewed:** 2026-08-20 · **English (canonical)** + +Acoustic-2 is a repository-only controlled PCM measurement milestone. It will not represent a microphone, speaker, room, operating system audio stack, radio or Bluetooth path. Its purpose is to make the effects of declared sample-domain transforms reproducible before a separate device-acceptance effort begins. + +| Source | Relevant finding | Consequence for this milestone | +| --- | --- | --- | +| Sanchez, *FSK Demodulation and Bit String Extraction* | Timing recovery aligns sampling instants with symbol boundaries, improves extraction in the presence of timing offset/jitter, and carries added complexity.[1] | Measure acquisition offset and provide a deterministic bounded offset transform; do not claim a general timing-recovery loop. | +| NCC Group, *Developing an FSK receiver step-by-step* | A preamble marks data start and helps synchronize a receiver; receiver development benefits from recorded signals, filtering and explicit clock-recovery stages.[2] | Keep preamble acquisition inspectable and expose measurement results for a known WAV input. | +| Chaudhari, *FSK Demodulation in GNU Radio* | FSK timing synchronization requires a receiver strategy appropriate to the actual waveform and operating conditions; generic timing blocks are not a universal production solution.[3] | Treat controlled acquisition as an experiment with declared bounds, not as device interoperability evidence. | + +## Measurement boundary + +The first harness may apply deterministic leading silence, gain scaling, hard clipping, integer-domain additive noise and fixed sample drops. It will report whether a generated WAV is accepted plus codec-observable values such as selected acquisition offset and samples consumed. It will not manufacture BER, SNR, range, room-noise, clock-drift or device-compatibility claims from synthetic data. + +## References + +[1]: https://arxiv.org/html/2402.17777v1 "FSK Demodulation and Bit String Extraction: A Python-Centric Approach in SDR Systems" +[2]: https://nccgroup.github.io/RFTM/fsk_receiver.html "RF Testing Methodology: Developing an FSK receiver step-by-step" +[3]: https://wirelesspi.com/fsk-demodulation-in-gnu-radio/ "FSK Demodulation in GNU Radio" diff --git a/docs/research/acoustic-2-measurement-sources_RU.md b/docs/research/acoustic-2-measurement-sources_RU.md new file mode 100644 index 0000000..0e905aa --- /dev/null +++ b/docs/research/acoustic-2-measurement-sources_RU.md @@ -0,0 +1,23 @@ +# Исследовательские заметки по измерениям Acoustic-2 + +[English (canonical)](acoustic-2-measurement-sources.md) · **Русский перевод** + +> **Translation of:** [docs/research/acoustic-2-measurement-sources.md](acoustic-2-measurement-sources.md). **Last synced:** 2026-08-20. + +Acoustic-2 — repository-only controlled PCM measurement milestone. Он не представляет микрофон, динамик, помещение, operating system audio stack, radio или Bluetooth path. Его цель — сделать эффекты declared sample-domain transforms воспроизводимыми до начала отдельной device-acceptance работы. + +| Источник | Значимое наблюдение | Следствие для этого milestone | +| --- | --- | --- | +| Sanchez, *FSK Demodulation and Bit String Extraction* | Timing recovery выравнивает sampling instants с symbol boundaries, уменьшает ошибки при timing offset/jitter и добавляет complexity.[1] | Измерять acquisition offset и предоставлять deterministic bounded offset transform; не заявлять general timing-recovery loop. | +| NCC Group, *Developing an FSK receiver step-by-step* | Preamble отмечает начало data и помогает synchronise receiver; development receiver выигрывает от recorded signals, filtering и explicit clock-recovery stages.[2] | Сохранять preamble acquisition inspectable и показывать measurement results для известного WAV input. | +| Chaudhari, *FSK Demodulation in GNU Radio* | FSK timing synchronization требует receiver strategy, соответствующей actual waveform и operating conditions; generic timing blocks не являются universal production solution.[3] | Рассматривать controlled acquisition как experiment с declared bounds, а не как device interoperability evidence. | + +## Граница measurement + +Первый harness может применять deterministic leading silence, gain scaling, hard clipping, integer-domain additive noise и fixed sample drops. Он сообщает, принят ли generated WAV, и codec-observable values, такие как selected acquisition offset и samples consumed. Он не создаёт BER, SNR, range, room-noise, clock-drift или device-compatibility claims из synthetic data. + +## Ссылки + +[1]: https://arxiv.org/html/2402.17777v1 "FSK Demodulation and Bit String Extraction: A Python-Centric Approach in SDR Systems" +[2]: https://nccgroup.github.io/RFTM/fsk_receiver.html "RF Testing Methodology: Developing an FSK receiver step-by-step" +[3]: https://wirelesspi.com/fsk-demodulation-in-gnu-radio/ "FSK Demodulation in GNU Radio" diff --git a/docs/roadmap.md b/docs/roadmap.md index d3ad5f3..1c3dbef 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -11,6 +11,7 @@ This roadmap describes intended order, not dates or guaranteed delivery. It dist | Flutter ↔ Rust bridge | Completed | Native UI invokes reviewed Rust ADLP/WAV encode/decode; web reports the native codec unavailable. | | Local WAV file workflow | Completed | User-selected save/open dialogs export/import verified WAV bytes; file-object payloads remain future work. | | Acoustic-1 | Experimental controlled carrier | B-FSK framing, bounded synchronisation, Hamming(7,4), profile-driven symbols and golden vectors exist for PCM/WAV tests; no speaker-to-microphone claim. | +| Acoustic-2 | Experimental measurement harness | Declared integer PCM transforms, bounded acquisition observables and golden measurement contract exist around Acoustic-1; no device or channel metric claim. | | Audio adapters | Planned | Capture/playback, cable, Bluetooth and radio-interface adapters with per-platform acceptance tests. | | Trust and encryption | Planned RFC | Key lifecycle, manual/QR exchange, authenticated encryption, verification UX and independent security review. | | Release engineering | Planned | Signed packages, compatibility matrix, changelog, SBOM/checksums and clear support policy. | diff --git a/docs/roadmap_RU.md b/docs/roadmap_RU.md index a57d8e7..07f1a34 100644 --- a/docs/roadmap_RU.md +++ b/docs/roadmap_RU.md @@ -13,6 +13,7 @@ | Flutter ↔ Rust bridge | Завершён | Native UI вызывает reviewed Rust ADLP/WAV encode/decode; web сообщает, что native codec недоступен. | | Локальный WAV file workflow | Завершён | User-selected save/open dialogs экспортируют/импортируют проверенные WAV bytes; file-object payloads остаются будущей работой. | | Acoustic-1 | Экспериментальный controlled carrier | B-FSK framing, bounded synchronisation, Hamming(7,4), profile-driven symbols и golden vectors существуют для PCM/WAV tests; speaker-to-microphone claim отсутствует. | +| Acoustic-2 | Экспериментальный measurement harness | Вокруг Acoustic-1 существуют declared integer PCM transforms, bounded acquisition observables и golden measurement contract; device или channel metric claim отсутствует. | | Audio adapters | Планируется | Capture/playback, cable, Bluetooth и radio-interface adapters с per-platform acceptance tests. | | Trust и encryption | Planned RFC | Key lifecycle, manual/QR exchange, authenticated encryption, verification UX и independent security review. | | Release engineering | Планируется | Signed packages, compatibility matrix, changelog, SBOM/checksums и clear support policy. | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 7765164..0af696c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,16 +1,19 @@ # Troubleshooting -**Last reviewed:** 2026-08-19 · **English (canonical)** · [Русский](troubleshooting_RU.md) +**Last reviewed:** 2026-08-20 · **English (canonical)** · [Русский](troubleshooting_RU.md) -This page documents the currently observable bootstrap path. It does not supply workarounds for live capture, Bluetooth or radio because those routes are not implemented. A useful report contains facts that another contributor can reproduce. +This page documents the currently observable bootstrap, Acoustic-1 and Acoustic-2 controlled PCM paths. It does not supply workarounds for live capture, Bluetooth or radio because those routes are not implemented. A useful report contains facts that another contributor can reproduce. | Symptom | First check | Expected outcome | | --- | --- | --- | | `cargo run` cannot find a package | Run from the repository root and use `-p adlp-cli`. | Cargo finds the workspace member. | | `encode-text` rejects the profile | Use `reliable`, `balanced`, `fast` or `narrowband`. | The CLI accepts the profile or defaults to `balanced`. | | `decode` rejects a WAV file | Confirm that the file came from the bootstrap CLI and was not transcoded. | The decoder accepts canonical mono 48 kHz signed 16-bit PCM WAV carrying the bootstrap signal. | +| `decode-acoustic1` rejects a WAV file | Confirm the carrier command matches the file and the file is canonical 48 kHz PCM. | The decoder accepts only Acoustic-1 framing; a bootstrap WAV is intentionally rejected. | +| `measure-acoustic1` rejects a transform | Record every supplied transform argument and test the unmodified file first. | Invalid bounds, periodic deletion or failed Acoustic-1 framing return an error; no payload/measurement is printed. | | Decoded text is missing | Preserve the CLI output and file checksum; run `cargo test --workspace`. | The decoder must not print a payload after a failed framing or CRC-32C check. | -| A live route is missing in the Flutter UI | Check the platform support matrix. | This is expected until the Rust bridge and live-audio adapters are implemented. | +| A native codec is unavailable in the web build | Use a native runner for the current Rust codec workflows. | This is expected: the web target deliberately has no WASM codec. | +| A live route is missing in the Flutter UI | Check the platform support matrix. | The native bridge/local WAV workflow exists, but live-audio adapters are still not implemented. | ## Report template @@ -22,6 +25,8 @@ Operating system and version: Rust version: Command run: Selected profile: +Selected carrier: +Acoustic-2 transform arguments, if used: Expected result: Actual result: Reproduction steps: diff --git a/docs/troubleshooting_RU.md b/docs/troubleshooting_RU.md index b6904e2..ac90c34 100644 --- a/docs/troubleshooting_RU.md +++ b/docs/troubleshooting_RU.md @@ -2,17 +2,20 @@ [English (canonical)](troubleshooting.md) · **Русский перевод** -> **Translation of:** [docs/troubleshooting.md](troubleshooting.md). **Last synced:** 2026-08-19. +> **Translation of:** [docs/troubleshooting.md](troubleshooting.md). **Last synced:** 2026-08-20. -Эта страница документирует наблюдаемый bootstrap path. Она не предлагает workaround для live capture, Bluetooth или radio, потому что эти routes не реализованы. Полезный report содержит факты, которые может воспроизвести другой contributor. +Эта страница документирует наблюдаемые bootstrap, Acoustic-1 и Acoustic-2 controlled PCM paths. Она не предлагает workaround для live capture, Bluetooth или radio, потому что эти routes не реализованы. Полезный report содержит факты, которые может воспроизвести другой contributor. | Симптом | Первая проверка | Ожидаемый результат | | --- | --- | --- | | `cargo run` не находит package | Запустите из корня repository и укажите `-p adlp-cli`. | Cargo находит workspace member. | | `encode-text` отклоняет profile | Используйте `reliable`, `balanced`, `fast` или `narrowband`. | CLI принимает profile или использует `balanced` по умолчанию. | | `decode` отклоняет WAV file | Убедитесь, что файл создан bootstrap CLI и не был transcoded. | Decoder принимает canonical mono 48 kHz signed 16-bit PCM WAV с bootstrap signal. | +| `decode-acoustic1` отклоняет WAV file | Убедитесь, что carrier command соответствует файлу, а файл является canonical 48 kHz PCM. | Decoder принимает только Acoustic-1 framing; bootstrap WAV намеренно отклоняется. | +| `measure-acoustic1` отклоняет transform | Зафиксируйте каждый supplied transform argument и сначала проверьте unmodified file. | Invalid bounds, periodic deletion или failed Acoustic-1 framing возвращают error; payload/measurement не печатается. | | Decoded text отсутствует | Сохраните CLI output и file checksum; выполните `cargo test --workspace`. | Decoder не должен печатать payload после failed framing или CRC-32C check. | -| Live route отсутствует в Flutter UI | Проверьте platform support matrix. | Это ожидаемо до реализации Rust bridge и live-audio adapters. | +| Native codec недоступен в web build | Используйте native runner для текущих Rust codec workflows. | Это ожидаемо: web target намеренно не имеет WASM codec. | +| Live route отсутствует в Flutter UI | Проверьте platform support matrix. | Native bridge/local WAV workflow существуют, но live-audio adapters всё ещё не реализованы. | ## Шаблон отчёта @@ -24,6 +27,8 @@ Operating system and version: Rust version: Command run: Selected profile: +Selected carrier: +Acoustic-2 transform arguments, если использовались: Expected result: Actual result: Reproduction steps: diff --git a/site/index.html b/site/index.html index b6bbe19..08075e9 100644 --- a/site/index.html +++ b/site/index.html @@ -12,10 +12,10 @@

v0.1 Experimental project · Rust + Flutter

Move data
through sound.

AudioModem turns text and files into a versioned object, then into PCM. Today that path is verified through WAV; later it can use a speaker, cable or radio audio interface.

Try WAVView source

No accounts, no cloud and no hidden network transport.

ADLP v1
Text or file
Versioned object
PCM signal

WAVaudio route

What it does

One object. Several delivery paths.

The data format does not depend on a speaker, microphone, cable or WAV file. A route delivers PCM; ADLP describes the object, profile and integrity check.

-

First test

Start with a reproducible WAV round trip.

WAV removes unknown live-audio characteristics. The native app can export a verified WAV and import it for Rust-side validation; the experimental Acoustic-1 B-FSK carrier has controlled WAV tests, not a live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Hello" reliable
+      

First test

Start with a reproducible WAV round trip.

WAV removes unknown live-audio characteristics. The native app can export a verified WAV and import it for Rust-side validation; Acoustic-1 has a controlled B-FSK carrier and Acoustic-2 measures declared PCM transforms, neither of which is a live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Hello" reliable
  cargo run -p adlp-cli -- decode hello.wav
Open the short guide →

Architecture

A small app over an independent Rust core.

FlutterUI, routes, file selection and diagnostics
→
Rust bridgeOne codec implementation for the client
→
ADLP + DSPContainer, WAV and future PHY profiles
Architecture and stack →
-

Status

Current slice—without promises beyond the implementation.

✓ ADLP v1 + CRC-32C
done
✓ Deterministic PCM/WAV bootstrap
done
✓ Flutter ↔ Rust bridge
native text-to-WAV verification
✓ Local WAV import / export
native file dialogs + Rust validation
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
• Live audio and encryption
planned
See roadmap →
+

Status

Current slice—without promises beyond the implementation.

✓ ADLP v1 + CRC-32C
done
✓ Deterministic PCM/WAV bootstrap
done
✓ Flutter ↔ Rust bridge
native text-to-WAV verification
✓ Local WAV import / export
native file dialogs + Rust validation
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
• Live audio and encryption
planned
See roadmap →
diff --git a/site/ru/index.html b/site/ru/index.html index d858211..6e72f77 100644 --- a/site/ru/index.html +++ b/site/ru/index.html @@ -12,10 +12,10 @@

v0.1 Экспериментальный проект · Rust + Flutter

Передача данных
через звук.

AudioModem превращает текст и файлы в версионированный объект, затем в PCM. Сегодня этот путь проверяется через WAV; в будущем он сможет работать с динамиком, кабелем и радиоаудиотрактом.

Попробовать WAVИсходный код

Без аккаунтов, без облака и без скрытого сетевого транспорта.

ADLP v1
Текст или файл
Versioned object
PCM signal

WAVаудиомаршрут

Как это устроено

Один объект. Несколько путей доставки.

Формат данных не зависит от динамика, микрофона, кабеля или WAV-файла. Маршрут доставляет PCM; ADLP описывает объект, профиль и контроль целостности.

-

Первый тест

Начните с воспроизводимого WAV round trip.

WAV исключает неизвестные свойства live-аудиотракта. Native app может экспортировать проверенный WAV и импортировать его для Rust-side validation; экспериментальный Acoustic-1 B-FSK carrier имеет controlled WAV tests, а не live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Привет" reliable
+      

Первый тест

Начните с воспроизводимого WAV round trip.

WAV исключает неизвестные свойства live-аудиотракта. Native app может экспортировать проверенный WAV и импортировать его для Rust-side validation; Acoustic-1 имеет controlled B-FSK carrier, а Acoustic-2 измеряет declared PCM transforms — ни один из них не является live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Привет" reliable
  cargo run -p adlp-cli -- decode hello.wav
Открыть краткое руководство →

Архитектура

Небольшое приложение поверх независимого Rust core.

FlutterUI, маршруты, выбор файла и диагностика
→
Rust bridgeОдна реализация codec для клиента
→
ADLP + DSPКонтейнер, WAV и будущие PHY profiles
Архитектура и стек →
-

Статус

Текущий срез — без обещаний сверх реализации.

✓ ADLP v1 + CRC-32C
готово
✓ Детерминированный PCM/WAV bootstrap
готово
✓ Flutter ↔ Rust bridge
native text-to-WAV verification
✓ Локальный WAV import / export
native file dialogs + Rust validation
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
• Live audio и encryption
план
Посмотреть дорожную карту →
+

Статус

Текущий срез — без обещаний сверх реализации.

✓ ADLP v1 + CRC-32C
готово
✓ Детерминированный PCM/WAV bootstrap
готово
✓ Flutter ↔ Rust bridge
native text-to-WAV verification
✓ Локальный WAV import / export
native file dialogs + Rust validation
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
• Live audio и encryption
план
Посмотреть дорожную карту →
diff --git a/spec/acoustic-2.md b/spec/acoustic-2.md new file mode 100644 index 0000000..f725a85 --- /dev/null +++ b/spec/acoustic-2.md @@ -0,0 +1,41 @@ +# Acoustic-2 controlled measurement profile + +**Status:** Experimental measurement contract · **English (canonical)** · [Русский](acoustic-2_RU.md) + +Acoustic-2 is not a new ADLP wire format or a live-audio carrier. It is a reproducible measurement layer around the existing experimental Acoustic-1 PCM/WAV codec. It lets contributors apply declared integer-domain sample transforms, invoke the unchanged Acoustic-1 decoder and record a small set of codec-observable acquisition values. + +The resulting measurement is evidence only for the named input WAV, exact transform parameters and current codec implementation. It is not evidence of a speaker, microphone, room, OS audio stack, cable, Bluetooth device, radio interface, SNR, BER, range, clock drift or interoperability. + +## Input and output contract + +The input is a canonical mono 16-bit PCM, 48 kHz WAV accepted by the Acoustic-1 decoder. The harness first parses the canonical WAV, applies the transform sequence below, re-encodes canonical WAV bytes and calls the existing `acoustic1::decode_wav` function. It never parses ADLP bytes itself. + +| Field | Type and bound | Meaning | +| --- | --- | --- | +| `leading_silence_samples` | `0..=480` | Zero-valued samples inserted before the frame. The upper bound equals Acoustic-1's bounded acquisition search window. | +| `gain_per_mille` | `1..=1,000` | Fixed integer gain applied to every sample, then saturated to signed 16-bit PCM. It models attenuation only. | +| `noise_peak` | `0..=1,000` | Peak absolute integer additive noise from the defined deterministic generator. It is not an SNR value. | +| `noise_seed` | `u32` | Seed for the defined linear congruential generator; identical inputs and seed must yield identical samples. | +| `clip_abs` | absent or `1..=32,767` | Symmetric hard clip threshold applied after gain and noise. | +| `drop_every_nth_sample` | absent or `>=2` | Remove every nth transformed sample. This represents a fixed sample deletion experiment, not resampling, clock drift or jitter. | + +Transforms are ordered as **gain → additive noise → clip → periodic sample deletion → leading silence**. Saturating integer arithmetic is mandatory. A result reports the input/output sample counts, dropped-sample count, transform descriptor, selected Acoustic-1 acquisition offset, samples consumed and decoded ADLP profile only when the existing decoder accepts the result. + +## Measurement cases + +The repository regression suite must cover at least the following deterministic cases using a fixed Acoustic-1 object. + +| Case | Expected result | Claim excluded | +| --- | --- | --- | +| Baseline, no transform | Accepted with candidate offset `0`. | Any live route. | +| Bounded leading silence | Accepted with an acquisition candidate inside the 480-sample search window. | General timing recovery. | +| Attenuation and bounded seeded noise | Accepted under the exact declared parameters. | SNR threshold or noisy-room tolerance. | +| Hard clipping | Accepted or rejected only according to the fixed declared vector. | Microphone/speaker distortion tolerance. | +| Periodic deletion | Rejection under the fixed declared vector. | Clock-drift, resampling or jitter behavior. | +| Offset beyond 480 samples | Rejection. | Arbitrary frame acquisition. | + +## Compatibility and evolution + +This profile does not alter Acoustic-1 framing, Hamming(7,4), ADLP CRC-32C or either existing golden WAV. A result change caused by modified transform semantics, decode behavior or fixture bytes is a measurement compatibility change and must include updated tests, rationale and release notes. + +Before a live-audio adapter can use these results, the project needs a separate route RFC, device classes, capture/playback settings, accepted measurement method, raw recordings, reproducible scripts and a declared acceptance matrix. The background research and limits are recorded in [Acoustic-2 measurement sources](../docs/research/acoustic-2-measurement-sources.md). diff --git a/spec/acoustic-2_RU.md b/spec/acoustic-2_RU.md new file mode 100644 index 0000000..234ce36 --- /dev/null +++ b/spec/acoustic-2_RU.md @@ -0,0 +1,43 @@ +# Контролируемый measurement profile Acoustic-2 + +[English (canonical)](acoustic-2.md) · **Русский перевод** + +> **Translation of:** [spec/acoustic-2.md](acoustic-2.md). **Last synced:** 2026-08-20. Английский оригинал нормативен для compatibility implementation. + +Acoustic-2 не является новым ADLP wire format или live-audio carrier. Это воспроизводимый measurement layer вокруг существующего экспериментального Acoustic-1 PCM/WAV codec. Он позволяет contributors применять объявленные integer-domain sample transforms, вызывать неизменённый Acoustic-1 decoder и записывать небольшой набор codec-observable acquisition values. + +Полученное measurement является evidence только для named input WAV, точных transform parameters и текущей реализации codec. Оно не является evidence для динамика, микрофона, помещения, OS audio stack, кабеля, Bluetooth device, radio interface, SNR, BER, дальности, clock drift или interoperability. + +## Контракт входа и выхода + +Входом является canonical mono 16-bit PCM, 48 kHz WAV, принимаемый Acoustic-1 decoder. Harness сначала разбирает canonical WAV, применяет указанную ниже transform sequence, повторно кодирует canonical WAV bytes и вызывает существующую функцию `acoustic1::decode_wav`. Он никогда не разбирает ADLP bytes самостоятельно. + +| Поле | Тип и bound | Значение | +| --- | --- | --- | +| `leading_silence_samples` | `0..=480` | Zero-valued samples, вставляемые перед frame. Upper bound равен Acoustic-1 bounded acquisition search window. | +| `gain_per_mille` | `1..=1,000` | Fixed integer gain для каждого sample с последующей saturating операцией в signed 16-bit PCM. Моделирует только attenuation. | +| `noise_peak` | `0..=1,000` | Peak absolute integer additive noise из определённого deterministic generator. Это не SNR value. | +| `noise_seed` | `u32` | Seed определённого linear congruential generator; одинаковые inputs и seed обязаны дать одинаковые samples. | +| `clip_abs` | отсутствует или `1..=32,767` | Symmetric hard clip threshold после gain и noise. | +| `drop_every_nth_sample` | отсутствует или `>=2` | Удалить каждый nth transformed sample. Это fixed sample deletion experiment, а не resampling, clock drift или jitter. | + +Transforms применяются в порядке **gain → additive noise → clip → periodic sample deletion → leading silence**. Saturating integer arithmetic обязателен. Result сообщает input/output sample counts, dropped-sample count, transform descriptor, выбранный Acoustic-1 acquisition offset, samples consumed и decoded ADLP profile только когда существующий decoder принимает результат. + +## Measurement cases + +Repository regression suite обязана покрывать как минимум следующие deterministic cases с fixed Acoustic-1 object. + +| Case | Ожидаемый результат | Исключённое утверждение | +| --- | --- | --- | +| Baseline без transform | Accepted с candidate offset `0`. | Любой live route. | +| Bounded leading silence | Accepted с acquisition candidate внутри 480-sample search window. | General timing recovery. | +| Attenuation и bounded seeded noise | Accepted при точно объявленных parameters. | SNR threshold или noisy-room tolerance. | +| Hard clipping | Accepted или rejected только по fixed declared vector. | Microphone/speaker distortion tolerance. | +| Periodic deletion | Rejection при fixed declared vector. | Clock-drift, resampling или jitter behavior. | +| Offset свыше 480 samples | Rejection. | Arbitrary frame acquisition. | + +## Compatibility и дальнейшее развитие + +Этот profile не меняет Acoustic-1 framing, Hamming(7,4), ADLP CRC-32C или существующие golden WAV. Изменение result из-за transform semantics, decode behavior или fixture bytes является measurement compatibility change и требует updated tests, rationale и release notes. + +До использования этих results live-audio adapter'ом проекту нужен отдельный route RFC, device classes, capture/playback settings, accepted measurement method, raw recordings, reproducible scripts и declared acceptance matrix. Исследовательские основания и limits зафиксированы в [Acoustic-2 measurement sources](../docs/research/acoustic-2-measurement-sources.md). diff --git a/tools/adlp-cli/src/main.rs b/tools/adlp-cli/src/main.rs index ddff5bd..24e57df 100644 --- a/tools/adlp-cli/src/main.rs +++ b/tools/adlp-cli/src/main.rs @@ -1,7 +1,7 @@ use std::{env, error::Error, fs, process}; use adlp_protocol::{ObjectKind, TransferProfile, WireObject}; -use audio_modem_core::{acoustic1, decode_wav, encode_wav}; +use audio_modem_core::{acoustic1, acoustic2, decode_wav, encode_wav}; fn main() { if let Err(error) = run() { @@ -17,6 +17,7 @@ fn run() -> Result<(), Box> { Some("encode-acoustic1-text") => encode_text(&args, true), Some("decode") => decode(&args, false), Some("decode-acoustic1") => decode(&args, true), + Some("measure-acoustic1") => measure_acoustic1(&args), _ => Err("unknown command".into()), } } @@ -81,6 +82,54 @@ fn decode(args: &[String], acoustic1_carrier: bool) -> Result<(), Box Ok(()) } +fn measure_acoustic1(args: &[String]) -> Result<(), Box> { + let input = args.get(2).ok_or("missing input WAV path")?; + let mut impairment = acoustic2::PcmImpairment::default(); + if let Some(value) = args.get(3) { + impairment.leading_silence_samples = value.parse()?; + } + if let Some(value) = args.get(4) { + impairment.gain_per_mille = value.parse()?; + } + if let Some(value) = args.get(5) { + impairment.noise_peak = value.parse()?; + } + if let Some(value) = args.get(6) { + impairment.noise_seed = value.parse()?; + } + if let Some(value) = args.get(7) { + impairment.clip_abs = Some(value.parse()?); + } + if let Some(value) = args.get(8) { + impairment.drop_every_nth_sample = Some(value.parse()?); + } + let measurement = acoustic2::measure_acoustic1_wav(&fs::read(input)?, &impairment)?; + println!("measurement: accepted"); + println!("carrier: acoustic1"); + println!("profile: {}", measurement.profile.as_str()); + println!("input_samples: {}", measurement.input_samples); + println!("output_samples: {}", measurement.output_samples); + println!("dropped_samples: {}", measurement.dropped_samples); + println!( + "leading_silence_samples: {}", + measurement.leading_silence_samples + ); + println!( + "acquisition_offset_samples: {}", + measurement.acquisition_offset_samples + ); + println!("samples_consumed: {}", measurement.samples_consumed); + println!("gain_per_mille: {}", impairment.gain_per_mille); + println!("noise_peak: {}", impairment.noise_peak); + println!("noise_seed: {}", impairment.noise_seed); + println!("clip_abs: {:?}", impairment.clip_abs); + println!( + "drop_every_nth_sample: {:?}", + impairment.drop_every_nth_sample + ); + Ok(()) +} + fn parse_profile(value: &str) -> Result> { match value { "reliable" => Ok(TransferProfile::Reliable), @@ -92,5 +141,5 @@ fn parse_profile(value: &str) -> Result> { } fn usage() -> &'static str { - "Usage:\n adlp-cli encode-text [reliable|balanced|fast|narrowband]\n adlp-cli decode \n adlp-cli encode-acoustic1-text [reliable|balanced|fast|narrowband]\n adlp-cli decode-acoustic1 " + "Usage:\n adlp-cli encode-text [reliable|balanced|fast|narrowband]\n adlp-cli decode \n adlp-cli encode-acoustic1-text [reliable|balanced|fast|narrowband]\n adlp-cli decode-acoustic1 \n adlp-cli measure-acoustic1 [leading-silence] [gain-per-mille] [noise-peak] [noise-seed] [clip-abs] [drop-every-nth]" }