From 17ae7ced0b96769c81276f4083f30db81602c966 Mon Sep 17 00:00:00 2001 From: Matthew Baranov Date: Thu, 20 Aug 2026 18:07:05 +0000 Subject: [PATCH] feat: add constrained live-audio adapter contract --- .github/workflows/site.yml | 2 + apps/audio_modem/lib/main.dart | 22 ++++- .../lib/platform/live_audio_adapter.dart | 88 +++++++++++++++++++ apps/audio_modem/test/widget_test.dart | 59 +++++++++++++ docs/README.md | 2 + docs/README_RU.md | 2 + docs/operations/device-acceptance.md | 2 +- docs/operations/device-acceptance_RU.md | 2 +- docs/research/live-audio-adapter-sources.md | 21 +++++ .../research/live-audio-adapter-sources_RU.md | 23 +++++ docs/roadmap.md | 1 + docs/roadmap_RU.md | 1 + site/index.html | 2 +- site/ru/index.html | 2 +- spec/live-audio-adapter-v1.md | 59 +++++++++++++ spec/live-audio-adapter-v1_RU.md | 61 +++++++++++++ 16 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 apps/audio_modem/lib/platform/live_audio_adapter.dart create mode 100644 docs/research/live-audio-adapter-sources.md create mode 100644 docs/research/live-audio-adapter-sources_RU.md create mode 100644 spec/live-audio-adapter-v1.md create mode 100644 spec/live-audio-adapter-v1_RU.md diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 0ade696..655e136 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -43,10 +43,12 @@ jobs: "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" "docs/research/device-acceptance-sources.md:docs/research/device-acceptance-sources_RU.md" + "docs/research/live-audio-adapter-sources.md:docs/research/live-audio-adapter-sources_RU.md" "docs/operations/device-acceptance.md:docs/operations/device-acceptance_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" + "spec/live-audio-adapter-v1.md:spec/live-audio-adapter-v1_RU.md" ) for pair in "${pairs[@]}"; do source_path="${pair%%:*}" diff --git a/apps/audio_modem/lib/main.dart b/apps/audio_modem/lib/main.dart index ad2469e..7c01a68 100644 --- a/apps/audio_modem/lib/main.dart +++ b/apps/audio_modem/lib/main.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'bridge/wav_bootstrap_bridge.dart'; +import 'platform/live_audio_adapter.dart'; import 'platform/wav_file_adapter.dart'; Future main() async { @@ -19,7 +20,11 @@ Future main() async { ); } runApp( - AudioModemApp(bridge: bridge, fileAdapter: const PlatformWavFileAdapter()), + AudioModemApp( + bridge: bridge, + fileAdapter: const PlatformWavFileAdapter(), + liveAudioAdapter: const UnavailableLiveAudioAdapter(), + ), ); } @@ -28,10 +33,12 @@ class AudioModemApp extends StatelessWidget { super.key, required this.bridge, required this.fileAdapter, + this.liveAudioAdapter = const UnavailableLiveAudioAdapter(), }); final WavBootstrapBridge bridge; final WavFileAdapter fileAdapter; + final LiveAudioAdapter liveAudioAdapter; @override Widget build(BuildContext context) { @@ -47,7 +54,11 @@ class AudioModemApp extends StatelessWidget { scaffoldBackgroundColor: const Color(0xFFFFFCF5), useMaterial3: true, ), - home: TransferWorkbench(bridge: bridge, fileAdapter: fileAdapter), + home: TransferWorkbench( + bridge: bridge, + fileAdapter: fileAdapter, + liveAudioAdapter: liveAudioAdapter, + ), ); } } @@ -57,10 +68,12 @@ class TransferWorkbench extends StatefulWidget { super.key, required this.bridge, required this.fileAdapter, + required this.liveAudioAdapter, }); final WavBootstrapBridge bridge; final WavFileAdapter fileAdapter; + final LiveAudioAdapter liveAudioAdapter; @override State createState() => _TransferWorkbenchState(); @@ -91,7 +104,8 @@ class _TransferWorkbenchState extends State { Future _buildAndVerifyWav() async { if (_route != TransferRoute.wav) { _showMessage( - 'Первый Rust bridge реализован только для WAV. Выберите WAV-маршрут.', + widget.liveAudioAdapter.availability.reason ?? + 'Live-audio маршрут недоступен. Выберите WAV-маршрут.', ); return; } @@ -540,7 +554,7 @@ class _TransferWorkbenchState extends State { ], const SizedBox(height: 12), const Text( - 'WAV можно экспортировать и импортировать локально. Live audio, cable, Bluetooth и radio adapters ещё не реализованы.', + 'WAV можно экспортировать и импортировать локально. Live-audio adapter contract добавлен, но capture, playback, cable, Bluetooth и radio adapters ещё не реализованы.', style: TextStyle( fontSize: 12, height: 1.4, diff --git a/apps/audio_modem/lib/platform/live_audio_adapter.dart b/apps/audio_modem/lib/platform/live_audio_adapter.dart new file mode 100644 index 0000000..53b7922 --- /dev/null +++ b/apps/audio_modem/lib/platform/live_audio_adapter.dart @@ -0,0 +1,88 @@ +// Product-layer live-audio contract. This file deliberately does not initialize +// a recording/playback plugin, request permissions, or access audio hardware. + +import 'dart:typed_data'; + +enum PcmSampleFormat { signedPcm16LittleEndian } + +class PcmStreamFormat { + const PcmStreamFormat({ + required this.sampleRateHz, + required this.channels, + required this.sampleFormat, + }); + + static const audioModemV1 = PcmStreamFormat( + sampleRateHz: 48000, + channels: 1, + sampleFormat: PcmSampleFormat.signedPcm16LittleEndian, + ); + + final int sampleRateHz; + final int channels; + final PcmSampleFormat sampleFormat; + + bool get isAudioModemV1 => + sampleRateHz == audioModemV1.sampleRateHz && + channels == audioModemV1.channels && + sampleFormat == audioModemV1.sampleFormat; +} + +class LiveAudioAvailability { + const LiveAudioAvailability.unavailable(this.reason) + : isAvailable = false, + supportedFormat = PcmStreamFormat.audioModemV1; + + const LiveAudioAvailability.available({required this.supportedFormat}) + : isAvailable = true, + reason = null; + + final bool isAvailable; + final String? reason; + final PcmStreamFormat supportedFormat; +} + +abstract interface class LiveAudioAdapter { + LiveAudioAvailability get availability; + + Future startPlayback({ + required Uint8List pcmFrames, + required PcmStreamFormat format, + }); + + Stream startCapture({required PcmStreamFormat format}); + + Future stop(); + + Future dispose(); +} + +class UnavailableLiveAudioAdapter implements LiveAudioAdapter { + const UnavailableLiveAudioAdapter([ + this._reason = 'Live-audio adapters are not implemented. Build or import a WAV instead.', + ]); + + final String _reason; + + @override + LiveAudioAvailability get availability => + LiveAudioAvailability.unavailable(_reason); + + StateError _error() => StateError(_reason); + + @override + Future startPlayback({ + required Uint8List pcmFrames, + required PcmStreamFormat format, + }) => Future.error(_error()); + + @override + Stream startCapture({required PcmStreamFormat format}) => + Stream.error(_error()); + + @override + Future stop() => Future.error(_error()); + + @override + Future dispose() => Future.value(); +} diff --git a/apps/audio_modem/test/widget_test.dart b/apps/audio_modem/test/widget_test.dart index 19c31ed..1d7d6bd 100644 --- a/apps/audio_modem/test/widget_test.dart +++ b/apps/audio_modem/test/widget_test.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:audio_modem/bridge/wav_bootstrap_bridge.dart'; import 'package:audio_modem/main.dart'; +import 'package:audio_modem/platform/live_audio_adapter.dart'; import 'package:audio_modem/platform/wav_file_adapter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -72,6 +73,33 @@ class _FakeWavFileAdapter implements WavFileAdapter { } void main() { + test('live-audio v1 format is explicitly PCM16 mono at 48 kHz', () { + expect(PcmStreamFormat.audioModemV1.isAudioModemV1, isTrue); + const incompatible = PcmStreamFormat( + sampleRateHz: 44100, + channels: 2, + sampleFormat: PcmSampleFormat.signedPcm16LittleEndian, + ); + expect(incompatible.isAudioModemV1, isFalse); + }); + + test('unavailable live-audio adapter cannot start a route', () async { + const adapter = UnavailableLiveAudioAdapter('No tested live route.'); + expect(adapter.availability.isAvailable, isFalse); + expect(adapter.availability.reason, 'No tested live route.'); + await expectLater( + adapter.startPlayback( + pcmFrames: Uint8List(0), + format: PcmStreamFormat.audioModemV1, + ), + throwsA(isA()), + ); + await expectLater( + adapter.startCapture(format: PcmStreamFormat.audioModemV1).first, + throwsA(isA()), + ); + }); + testWidgets('send workbench builds and verifies an in-memory WAV flow', ( tester, ) async { @@ -145,4 +173,35 @@ void main() { expect(find.text('WAV object проверен.'), findsOneWidget); expect(find.text('Источник: received.wav'), findsOneWidget); }); + + testWidgets('live route selection remains unavailable and does not encode', ( + tester, + ) async { + final bridge = _FakeWavBridge(); + await tester.pumpWidget( + AudioModemApp( + bridge: bridge, + fileAdapter: _FakeWavFileAdapter(), + liveAudioAdapter: const UnavailableLiveAudioAdapter( + 'No tested live route.', + ), + ), + ); + + final speakerRoute = find.text('Динамик'); + await tester.ensureVisible(speakerRoute); + await tester.tap(speakerRoute); + await tester.pumpAndSettle(); + + final buildButton = find.widgetWithText( + FilledButton, + 'Собрать и проверить WAV', + ); + await tester.ensureVisible(buildButton); + await tester.tap(buildButton); + await tester.pumpAndSettle(); + + expect(find.text('No tested live route.'), findsOneWidget); + expect(bridge.encodedCarrier, isNull); + }); } diff --git a/docs/README.md b/docs/README.md index a30c8bd..5648553 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,9 +18,11 @@ This directory holds the human-readable technical documentation for AudioModem. | 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 | | Research | [Device-acceptance sources](research/device-acceptance-sources.md) | [Источники device acceptance](research/device-acceptance-sources_RU.md) | Reproducible evidence, metadata and privacy constraints | +| Research | [Live-audio adapter sources](research/live-audio-adapter-sources.md) | [Источники live-audio adapter](research/live-audio-adapter-sources_RU.md) | Session, permission, focus and lifecycle 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 | +| Specification | [Live-audio adapter v1](../spec/live-audio-adapter-v1.md) | [Live-audio adapter v1 на русском](../spec/live-audio-adapter-v1_RU.md) | Typed unavailable-first contract for future routes | ## Translation convention diff --git a/docs/README_RU.md b/docs/README_RU.md index 345990a..58d41db 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -20,9 +20,11 @@ | 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 | | Research | [Device-acceptance sources](research/device-acceptance-sources.md) | [Источники device acceptance](research/device-acceptance-sources_RU.md) | Ограничения reproducible evidence, metadata и privacy | +| Research | [Live-audio adapter sources](research/live-audio-adapter-sources.md) | [Источники live-audio adapter](research/live-audio-adapter-sources_RU.md) | Ограничения session, permission, focus и lifecycle | | 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 | +| Specification | [Live-audio adapter v1](../spec/live-audio-adapter-v1.md) | [Live-audio adapter v1 на русском](../spec/live-audio-adapter-v1_RU.md) | Typed unavailable-first contract для будущих routes | ## Правило поддержки перевода diff --git a/docs/operations/device-acceptance.md b/docs/operations/device-acceptance.md index 96fa138..92f699c 100644 --- a/docs/operations/device-acceptance.md +++ b/docs/operations/device-acceptance.md @@ -51,7 +51,7 @@ The committed fixture above is intentionally an **unexecuted template**, not a d | Candidate-route gate | Repeated real runs, declared device/route settings, fixture hashes, failures and a maintainer review are available. | `experimental route candidate`. | | Supported-route gate | A separate adapter RFC, reproducible target build, route-specific acceptance tests, published compatibility note and maintainer approval all exist. | `supported` for only the documented scope. | -The schema cannot establish the last three gates by itself. It only prevents incomplete evidence from being mistaken for a measurement. The existing [audio-route boundaries](../guides/audio-routes.md) and [platform matrix](../reference/platform-support.md) remain authoritative for current support status. +The [constrained live-audio adapter RFC](../../spec/live-audio-adapter-v1.md) closes only the contract gate with an unavailable implementation. It neither enables a route nor supplies the physical-route evidence, platform implementation or tests required by the later gates. The schema cannot establish the last three gates by itself. It only prevents incomplete evidence from being mistaken for a measurement. The existing [audio-route boundaries](../guides/audio-routes.md) and [platform matrix](../reference/platform-support.md) remain authoritative for current support status. ## References diff --git a/docs/operations/device-acceptance_RU.md b/docs/operations/device-acceptance_RU.md index 53f959a..ed2beaa 100644 --- a/docs/operations/device-acceptance_RU.md +++ b/docs/operations/device-acceptance_RU.md @@ -53,7 +53,7 @@ Committed fixture выше намеренно является **unexecuted temp | Candidate-route gate | Доступны repeated real runs, declared device/route settings, fixture hashes, failures и maintainer review. | `experimental route candidate`. | | Supported-route gate | Существуют separate adapter RFC, reproducible target build, route-specific acceptance tests, published compatibility note и maintainer approval. | `supported` только для documented scope. | -Schema сама по себе не устанавливает последние три gates. Она лишь не даёт принять incomplete evidence за measurement. Текущие статусы остаются в [границах audio routes](../guides/audio-routes_RU.md) и [platform matrix](../reference/platform-support_RU.md). +[Constrained live-audio adapter RFC](../../spec/live-audio-adapter-v1_RU.md) закрывает только contract gate unavailable implementation'ом. Он не включает route и не даёт physical-route evidence, platform implementation или tests, требуемые поздними gates. Schema сама по себе не устанавливает последние три gates. Она лишь не даёт принять incomplete evidence за measurement. Текущие статусы остаются в [границах audio routes](../guides/audio-routes_RU.md) и [platform matrix](../reference/platform-support_RU.md). ## Ссылки diff --git a/docs/research/live-audio-adapter-sources.md b/docs/research/live-audio-adapter-sources.md new file mode 100644 index 0000000..f3c3035 --- /dev/null +++ b/docs/research/live-audio-adapter-sources.md @@ -0,0 +1,21 @@ +# Constrained live-audio adapter research notes + +**Last reviewed:** 2026-08-20 · **English (canonical)** + +The first live-audio milestone must define a typed application boundary before choosing a runtime plugin or declaring a route. The contract will treat permission, session activation, interruption, playback, capture and disposal as separately observable states. It will not infer a stable sample rate, hardware route, acoustic result or platform support from a plugin dependency. + +| Source | Relevant finding | RFC consequence | +| --- | --- | --- | +| Flutter cookbook, *Record or stream audio input* | Audio input requires user permission and may require platform-specific configuration; capture configuration, streaming, stopping and disposal are distinct operations.[1] | Require explicit permission and lifecycle outcomes; do not expose capture bytes until a platform adapter exists and reports a supported PCM format. | +| `audio_session` documentation | iOS has an app-wide shared audio session; Android attributes are applied per player/track. Activation can be denied, and interruption/device events require a policy.[2] | Make session activation, interruption and route-change events explicit; the core contract must not own platform-session policy silently. | +| Android audio-focus guidance | Playback should request focus immediately before use, handle focus loss, and abandon focus when finished; focus behavior differs by Android version and context.[3] | Playback is an opt-in command whose failure does not start output. No automatic resume or ducking policy is promised in v1. | + +## RFC posture + +The constrained adapter contract will ship only an unavailable implementation. It gives the Flutter workbench a stable dependency seam and a truthful status message while device-acceptance reports, a single-route platform RFC and hardware evidence are still absent. No recording package, audio-session package or microphone permission is added in this milestone. + +## References + +[1]: https://docs.flutter.dev/cookbook/audio/record "Record or stream audio input" +[2]: https://pub.dev/packages/audio_session "audio_session package documentation" +[3]: https://developer.android.com/media/optimize/audio-focus "Manage audio focus" diff --git a/docs/research/live-audio-adapter-sources_RU.md b/docs/research/live-audio-adapter-sources_RU.md new file mode 100644 index 0000000..6cce638 --- /dev/null +++ b/docs/research/live-audio-adapter-sources_RU.md @@ -0,0 +1,23 @@ +# Исследовательские заметки по constrained live-audio adapter + +[English (canonical)](live-audio-adapter-sources.md) · **Русский перевод** + +> **Translation of:** [docs/research/live-audio-adapter-sources.md](live-audio-adapter-sources.md). **Last synced:** 2026-08-20. + +Первый live-audio milestone должен определить typed application boundary до выбора runtime plugin или заявления route. Contract будет рассматривать permission, session activation, interruption, playback, capture и disposal как отдельно observable states. Он не будет выводить stable sample rate, hardware route, acoustic result или platform support из plugin dependency. + +| Источник | Значимое наблюдение | Следствие для RFC | +| --- | --- | --- | +| Flutter cookbook, *Record or stream audio input* | Audio input требует user permission и может требовать platform-specific configuration; capture configuration, streaming, stopping и disposal являются отдельными operations.[1] | Требовать explicit permission и lifecycle outcomes; не выдавать capture bytes, пока platform adapter не существует и не сообщает supported PCM format. | +| Документация `audio_session` | iOS имеет app-wide shared audio session; Android attributes применяются per player/track. Activation может быть denied, а interruption/device events требуют policy.[2] | Сделать session activation, interruption и route-change events explicit; core contract не должен молча владеть platform-session policy. | +| Android audio-focus guidance | Playback должен request focus непосредственно перед use, обрабатывать focus loss и abandon focus после завершения; behavior различается между Android versions и contexts.[3] | Playback — opt-in command, failure которого не начинает output. В v1 не обещается automatic resume или ducking policy. | + +## Позиция RFC + +Constrained adapter contract поставляется только с unavailable implementation. Он даёт Flutter workbench stable dependency seam и truthful status message, пока отсутствуют device-acceptance reports, single-route platform RFC и hardware evidence. В этом milestone не добавляются recording package, audio-session package или microphone permission. + +## Ссылки + +[1]: https://docs.flutter.dev/cookbook/audio/record "Record or stream audio input" +[2]: https://pub.dev/packages/audio_session "audio_session package documentation" +[3]: https://developer.android.com/media/optimize/audio-focus "Manage audio focus" diff --git a/docs/roadmap.md b/docs/roadmap.md index 1af9a7c..6d9a755 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -13,6 +13,7 @@ This roadmap describes intended order, not dates or guaranteed delivery. It dist | 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. | | Device-acceptance infrastructure | Completed reporting/tooling contract | Schema, validator, privacy-preserving intake template and decision gates prepare future evidence; no physical-route observation or supported device claim exists. | +| Live-audio adapter contract | Completed unavailable scaffold | Typed PCM/lifecycle boundary and unavailable behavior tests exist; no plugin, permission, capture, playback or physical route is enabled. | | 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 873cf95..3e42dbf 100644 --- a/docs/roadmap_RU.md +++ b/docs/roadmap_RU.md @@ -15,6 +15,7 @@ | 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 отсутствует. | | Device-acceptance infrastructure | Завершён reporting/tooling contract | Schema, validator, privacy-preserving intake template и decision gates готовят future evidence; physical-route observation или supported device claim отсутствуют. | +| Live-audio adapter contract | Завершён unavailable scaffold | Существуют typed PCM/lifecycle boundary и unavailable behavior tests; plugin, permission, capture, playback или physical route не включены. | | 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/site/index.html b/site/index.html index bde3040..a4bbe8d 100644 --- a/site/index.html +++ b/site/index.html @@ -15,7 +15,7 @@

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
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema and review gates; no device result
• 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
✓ Device-acceptance protocol
evidence schema and review gates; no device result
✓ Live-audio adapter contract
typed unavailable scaffold; no capture, playback or route
• Live audio and encryption
planned
See roadmap →
diff --git a/site/ru/index.html b/site/ru/index.html index 01d092e..691ea40 100644 --- a/site/ru/index.html +++ b/site/ru/index.html @@ -15,7 +15,7 @@

Первый тест

Начните с воспроизводимого 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
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema и review gates; device result отсутствует
• 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
✓ Device-acceptance protocol
evidence schema и review gates; device result отсутствует
✓ Live-audio adapter contract
typed unavailable scaffold; capture, playback и route отсутствуют
• Live audio и encryption
план
Посмотреть дорожную карту →
diff --git a/spec/live-audio-adapter-v1.md b/spec/live-audio-adapter-v1.md new file mode 100644 index 0000000..e4fc07d --- /dev/null +++ b/spec/live-audio-adapter-v1.md @@ -0,0 +1,59 @@ +# Constrained live-audio adapter v1 + +**Status:** Draft implementation contract · **English (canonical)** · [Русский](live-audio-adapter-v1_RU.md) + +This RFC defines the application boundary for a future live audio route. It does not add capture, playback, microphone permission, audio focus, device enumeration, cable routing, Bluetooth routing, radio support or a supported platform. The initial implementation provides an unavailable adapter only, so the UI and tests can distinguish a designed interface from a functioning route. + +## Scope and first-route constraint + +The first physical route, if and only if it is separately approved, will be **one local speaker-to-microphone simplex path** with a single documented platform/device scope. It may carry one selected ADLP carrier as raw mono PCM16 frames. A route RFC, platform implementation, complete device-acceptance evidence and target-specific tests must exist before that adapter is enabled. + +The v1 contract does not select a Flutter recording/playback package. Flutter guidance shows that permission, capture configuration, stream control and disposal are separate responsibilities; platform support varies by encoding and target.[1] Session configuration and focus ownership are likewise platform-specific, including app-wide iOS session behavior and Android per-track attributes.[2] [3] + +## Typed contract + +The Flutter-facing interface has three concerns. + +| Type | Responsibility | Required invariant | +| --- | --- | --- | +| `PcmStreamFormat` | Describes future raw frames: sample rate, channels and sample format. | V1 accepts only `48,000 Hz`, mono, signed PCM16 little-endian. WAV containers are not accepted. | +| `LiveAudioAvailability` | Reports whether a concrete adapter is enabled and why not. | `isAvailable == false` must never imply permission, capture, playback or device discovery. | +| `LiveAudioAdapter` | Reserves `startPlayback`, `startCapture` and `stop` lifecycle commands. | A command may begin only after a platform adapter has verified format, permission and session/focus activation. | + +An unavailable adapter must return a failed future for every lifecycle command with the same public reason it exposes in `availability`. It must not request a permission, initialize a plugin, access a microphone, enumerate hardware, emit captured bytes or play audio. + +## Lifecycle and state boundary + +The contract names states but does not implement a route state machine yet. + +```text +unavailable ──(approved platform adapter)──> idle +idle ──(permission + session/focus granted)──> active playback | active capture +active ──(interrupt/route change/user stop)──> idle +any state ──(dispose)──> disposed +``` + +The future adapter is responsible for converting native permission, audio-session/focus, interruption and route-change signals into truthful failure or stop events. It must not automatically resume after an interruption in v1. On Android, focus may be denied or delayed and should be handled before output begins; on iOS audio-session settings are shared across the app, so adapter/plugin ownership must be declared before activation.[2] [3] + +## Required future acceptance gates + +| Gate | Required before enabling a route | Insufficient evidence | +| --- | --- | --- | +| Contract gate | This RFC, a typed adapter, unavailable behavior tests and no hidden plugin initialization. | A dependency listed in `pubspec.yaml`. | +| Platform gate | One approved platform implementation with permission/session/focus handling and a reproducible native build. | A generic cross-platform interface. | +| Device gate | Reviewed `physical_route` reports under the [device-acceptance protocol](../docs/operations/device-acceptance.md), including fixture hashes, route settings and failures. | A schema-valid template or a controlled Acoustic-2 result. | +| Route gate | Target-specific integration tests, documented limits and maintainer approval. | A successful run on an undocumented device. | + +## Security and privacy + +Live routes can capture private ambient audio. The adapter must be opt-in, should minimize retained audio, and must not persist raw frames or callsigns automatically. Any device report follows the recording availability and privacy rules in the device-acceptance protocol. Encryption remains separate from route transport and is not implied by a live adapter. + +## Non-goals + +This RFC does not provide automatic gain control, echo cancellation, resampling, timing recovery, FEC changes, duplex mode, background execution, Bluetooth/cable/radio routing, device discovery, audio visualization, Web Audio, microphone permission UI, playback, capture or physical interoperability. + +## References + +[1]: https://docs.flutter.dev/cookbook/audio/record "Record or stream audio input" +[2]: https://pub.dev/packages/audio_session "audio_session package documentation" +[3]: https://developer.android.com/media/optimize/audio-focus "Manage audio focus" diff --git a/spec/live-audio-adapter-v1_RU.md b/spec/live-audio-adapter-v1_RU.md new file mode 100644 index 0000000..f1ace55 --- /dev/null +++ b/spec/live-audio-adapter-v1_RU.md @@ -0,0 +1,61 @@ +# Constrained live-audio adapter v1 + +[English (canonical)](live-audio-adapter-v1.md) · **Русский перевод** + +> **Translation of:** [spec/live-audio-adapter-v1.md](live-audio-adapter-v1.md). **Last synced:** 2026-08-20. Английский оригинал определяет implementation contract. + +Этот RFC определяет application boundary будущего live audio route. Он не добавляет capture, playback, microphone permission, audio focus, device enumeration, cable routing, Bluetooth routing, radio support или supported platform. Начальная реализация предоставляет только unavailable adapter, чтобы UI и tests отличали designed interface от работающего route. + +## Scope и ограничение первого route + +Первый physical route, только после отдельного approval, будет **одним local speaker-to-microphone simplex path** с одним documented platform/device scope. Он может передавать один selected ADLP carrier как raw mono PCM16 frames. Route RFC, platform implementation, complete device-acceptance evidence и target-specific tests должны существовать до включения adapter. + +V1 contract не выбирает Flutter recording/playback package. Flutter guidance показывает, что permission, capture configuration, stream control и disposal — отдельные responsibilities, а platform support varies по encoding и target.[1] Session configuration и focus ownership также platform-specific, включая app-wide iOS session behavior и Android per-track attributes.[2] [3] + +## Typed contract + +Flutter-facing interface имеет три concerns. + +| Type | Responsibility | Required invariant | +| --- | --- | --- | +| `PcmStreamFormat` | Описывает будущие raw frames: sample rate, channels и sample format. | V1 принимает только `48,000 Hz`, mono, signed PCM16 little-endian. WAV containers не принимаются. | +| `LiveAudioAvailability` | Сообщает, включён ли concrete adapter и почему нет. | `isAvailable == false` никогда не означает permission, capture, playback или device discovery. | +| `LiveAudioAdapter` | Резервирует lifecycle commands `startPlayback`, `startCapture` и `stop`. | Command может начаться только после проверки format, permission и session/focus activation platform adapter'ом. | + +Unavailable adapter обязан возвращать failed future для каждого lifecycle command с той же public reason, что и в `availability`. Он не должен запрашивать permission, инициализировать plugin, обращаться к microphone, перечислять hardware, выдавать captured bytes или воспроизводить audio. + +## Lifecycle и state boundary + +Contract называет states, но пока не реализует route state machine. + +```text +unavailable ──(approved platform adapter)──> idle +idle ──(permission + session/focus granted)──> active playback | active capture +active ──(interrupt/route change/user stop)──> idle +any state ──(dispose)──> disposed +``` + +Future adapter отвечает за преобразование native permission, audio-session/focus, interruption и route-change signals в truthful failure или stop events. Он не должен автоматически resume после interruption в v1. На Android focus может быть denied или delayed и должен быть обработан до начала output; на iOS audio-session settings shared во всём app, поэтому adapter/plugin ownership должен быть declared до activation.[2] [3] + +## Required future acceptance gates + +| Gate | Требуется до включения route | Недостаточное evidence | +| --- | --- | --- | +| Contract gate | Этот RFC, typed adapter, unavailable behavior tests и отсутствие hidden plugin initialization. | Dependency в `pubspec.yaml`. | +| Platform gate | Один approved platform implementation с permission/session/focus handling и reproducible native build. | Generic cross-platform interface. | +| Device gate | Reviewed `physical_route` reports по [device-acceptance protocol](../docs/operations/device-acceptance_RU.md), включая fixture hashes, route settings и failures. | Schema-valid template или controlled Acoustic-2 result. | +| Route gate | Target-specific integration tests, documented limits и maintainer approval. | Successful run на undocumented device. | + +## Security и privacy + +Live routes могут захватывать private ambient audio. Adapter должен быть opt-in, минимизировать retained audio и не сохранять raw frames или callsigns автоматически. Любой device report следует правилам recording availability и privacy из device-acceptance protocol. Encryption остаётся отдельным от route transport и не подразумевается live adapter'ом. + +## Non-goals + +Этот RFC не предоставляет automatic gain control, echo cancellation, resampling, timing recovery, FEC changes, duplex mode, background execution, Bluetooth/cable/radio routing, device discovery, audio visualization, Web Audio, microphone permission UI, playback, capture или physical interoperability. + +## Ссылки + +[1]: https://docs.flutter.dev/cookbook/audio/record "Record or stream audio input" +[2]: https://pub.dev/packages/audio_session "audio_session package documentation" +[3]: https://developer.android.com/media/optimize/audio-focus "Manage audio focus"