One room. Every language.
同一场会议,每个人的语言。
Visual design: Current UI design.
Kanal turns one laptop into a live translation host for meetings with no shared language. It captures the conversation, presents transcripts and translations side by side, and gives every participant a QR code for a read-only view in their own language. Kanal was built for Chinese, German, and Polish technical meetings, where getting a part number, tolerance, or delivery date right matters more than making the interface feel like a chat app.
The desktop host is built with Avalonia on .NET 10. Speech recognition and translation sit behind provider interfaces, so cloud and local stages can be combined without changing the room model or the mobile client.
Kanal is under active development. The scripted demo needs no API key and is ready to explore once
KANAL_ENV=developmentis set. Live meetings currently require cloud transcription. Read Current limitations and roadmap before relying on Kanal in a meeting.
Kanal has two separate network boundaries: the speech pipeline and the mobile-caption relay. “Local” in a mode refers to the speech stage; it does not mean that the entire meeting runs offline.
| Data | Where it goes |
|---|---|
| Microphone audio | Sent to the cloud speech provider in cloud-transcription modes. It stays on the host in local-transcription modes once a local ASR provider exists. |
| Captions and room state | Sent through the authenticated Kanal gateway (a Cloudflare Worker) to the meeting's private room object, which fans them out to joined phones. Messages include transcript text, translations, speaker labels, language configuration, and pause/recording/lifecycle state. Nothing is stored server-side. |
| Joined-phone cache | The mobile client stores the current room transcript and state in browser localStorage so it can render before a reconnect snapshot arrives. |
| Local recording | Live microphone modes record a WAV file by default in the configured audio folder. Recording pauses with the room and can be disabled in Settings. Kanal never publishes the WAV file. |
| API keys and preferences | Gladia keys selected in the UI are stored as plain JSON in the platform application-data directory. The relay host token is supplied only through the operator machine's runtime environment. |
| Local translation models | Downloaded from the model catalog to the platform application-data directory and loaded in-process with llama.cpp. Model files and generated translations stay on the host, apart from captions sent to the relay. |
Kanal ships without a backing-store URL or bundled API key. The relay is a self-contained Cloudflare Worker, and the Worker address is the only relay address clients see. The join QR contains that public address and a receive-only, room-scoped ticket. Anyone who obtains the bearer invitation can read the room until the ticket expires (currently after 12 hours), but cannot create rooms, publish messages, or enumerate other rooms. Creating a room requires a per-device credential issued by the gateway operator, so one lost laptop can be revoked without rotating credentials on every other machine. Phones need network access to the relay to receive captions.
Linux can build the solution, but Kanal does not yet provide a Linux microphone-capture backend. Packaged desktop releases are not available yet, so run Kanal from source:
git clone https://github.com/TONiiV/Kanal.git
cd Kanal
KANAL_ENV=development dotnet run --project src/Kanal.Host
The first screen selects Chinese, German, and Polish by default. Leave Demo — scripted selected
and press Start. A repeatable trilingual script runs through the real room orchestrator without
an API key or microphone. Scan the displayed QR code to try the mobile view; mobile delivery still
uses the configured caption relay. Demo is offered only when KANAL_ENV is development (any
letter case); in Windows PowerShell, run $env:KANAL_ENV = "development" first.
For a live room:
GLADIA_API_KEY before launching Kanal.A mode is a preset for the transcription and translation stages. Availability is calculated from the providers, key, and downloaded model on the current machine; unavailable rows remain visible and explain what is missing.
| Mode | Transcription | Translation | Speech-pipeline data sent off the host | Current availability |
|---|---|---|---|---|
| Demo — scripted | Scripted | Scripted, or the selected downloaded local model | Nothing | Offered only with KANAL_ENV=development; no keys needed |
| Cloud transcription · cloud translation | Gladia live | Gladia live | Audio | Available with a Gladia key |
| Cloud transcription · local translation | Gladia live | Selected local GGUF model | Audio | Available with a Gladia key and downloaded model |
| Local transcription · cloud translation | Not implemented | Standalone cloud MT not implemented | Text only | Unavailable |
| Local transcription · local translation | Not implemented | Selected local GGUF model | Nothing | Unavailable until local ASR lands |
When started from the production UI, every mode publishes text and room state through the mobile
relay. Cloud-to-local mode disables translation inside the cloud ASR session; the
capability-driven orchestrator then sends final transcripts to the local IMtProvider.
Most configuration is available from the in-app Settings window:
Preferences are written to Kanal/settings.json beneath the operating system's application-data
directory. Downloaded models live in the adjacent Kanal/models directory, and log files in
Kanal/logs. Transcript exports and recordings default to Documents/Kanal.
Logs are written with NLog: one file per day (kanal-<date>.log), rolled over once it passes the
configured size, kept for two weeks, and never sent anywhere.
Environment variables override connection defaults:
| Variable | Purpose |
|---|---|
GLADIA_API_KEY | Fallback speech-provider key when no stored named key is selected |
KANAL_RELAY_URL | Public HTTPS endpoint of the deployed kanal-relay Worker |
KANAL_RELAY_HOST_TOKEN | This desktop's device credential, obtained once with an activation code; runtime only, never part of a build or QR |
KANAL_WEB_URL | Base URL of the static mobile client placed in the join QR code |
KANAL_ENV | development adds Demo — scripted to the mode list; any other value, or none, leaves it out |
The relay remains disabled rather than silently using a public fallback when either relay variable
is absent.
KANAL_RELAY_URL is an address, not a credential: every gateway route still requires the device
credential or a role-scoped room ticket. Missing configuration or a gateway failure does not stop
transcription: the meeting continues without a QR code, and the status bar reports that mobile
delivery is unavailable. Deployment and device-activation commands are in
gateway/README.md.
The default web URL is https://toniiv.github.io/Kanal/. To self-host it, serve
web/index.html over HTTPS and point KANAL_WEB_URL at that URL. The page has no
runtime import, project configuration, external font, or stylesheet dependency. On Start, the
host puts only the gateway address, 12-hour reader ticket, random room capability, and public P-256
verification key in the URL fragment; the fragment is not sent to the web host.
microphone / demo script
│
▼
IAsrProvider ── partials/finals ──► MeetingSession ──► authoritative RoomState
│ │
finals, when ASR │ ├──► Avalonia host columns
cannot translate ▼ └──► transcript export
IMtProvider
│
▼
IRelayPublisher
│
▼
read-only mobile clients
The host is the single authority; clients are projections of its state. MeetingSession branches
on provider capabilities, never on vendor names. Relay transport is isolated behind
IRelayPublisher. Speaker merges are non-destructive: existing utterances retain their original
diarization tag, while clients resolve the canonical speaker at render time.
| Project | Responsibility |
|---|---|
src/Kanal.Core | Provider contracts, room/domain model, orchestration, relay protocol and authenticated gateway publisher |
src/Kanal.Audio | 16 kHz mono PCM16 capture, Windows WASAPI, macOS AudioQueue/CoreAudio, resampling and WAV support |
src/Kanal.Providers.Gladia | Gladia live-v2 session setup, WebSocket streaming, reconnect and wire normalization |
src/Kanal.Providers.LocalMt | In-process llama.cpp translation, prompts, model catalog and downloads |
src/Kanal.Host | Avalonia operator UI, pipeline planning, settings, recording, QR generation and export |
tests/Kanal.Core.UnitTests | Unit tests for audio, providers, serialization, room state, orchestration, and other non-visual services |
tests/Kanal.UI.UnitTests | Headless unit tests for deterministic host view-model and application-state behavior; rendering and layout are intentionally out of scope |
web/index.html | Static mobile client; docs/index.html is its byte-identical GitHub Pages copy |
gateway/ | Relay gateway: Cloudflare Worker plus per-room and device-registry Durable Objects, with its own vitest suite |
tools/Kanal.Doctor | Microphone and live-ASR diagnostics |
The original product requirements and design trade-offs are documented in Chinese in
docs/PRD-v0.4.md. Implementation decisions and measured findings live in
docs/PROGRESS.md.
Build and run the complete test suite from the repository root:
dotnet build Kanal.slnx --configuration Release
dotnet test tests/Kanal.Core.UnitTests/Kanal.Core.UnitTests.csproj --configuration Release --no-build
dotnet test tests/Kanal.UI.UnitTests/Kanal.UI.UnitTests.csproj --configuration Release --no-build
The Core suite covers the room model, audio pipeline, providers, wire protocol, orchestration, and non-visual services. The UI suite uses headless Avalonia only to exercise deterministic view-model and application-state behavior; pixel, layout, style, and window-rendering assertions are out of scope. CI also enforces that the deployable web client and its GitHub Pages copy stay byte-identical:
cmp web/index.html docs/index.html
For pipeline diagnosis:
# Record five seconds, report levels, and write ./mic-check.wav
dotnet run --project tools/Kanal.Doctor -- mic 5
# With GLADIA_API_KEY set, stream a WAV and print raw + normalized events
dotnet run --project tools/Kanal.Doctor -- gladia mic-check.wav
Issues and focused pull requests are welcome. Before changing behavior, read
CLAUDE.md for repository invariants and .impeccable.md for the
interaction and visual constraints.
docs/PROGRESS.md with relevant plans or design decisions.web/index.html and docs/index.html byte-identical.Open a GitHub issue for a bug, proposal, or deployment question before starting a broad change.
IRelayPublisher, but Kanal ships with no alternative today. The default
workers.dev hostname is blocked in mainland China; participants whose phones roam through a
Chinese carrier need the gateway to use a custom domain.Detailed status, benchmarks, and the next implementation steps are tracked in
docs/PROGRESS.md, rather than duplicated here.
The approved next host UI is specified in Meeting workspace design,
including the HTML prototype and implementation checks. Future automatic titles and a listening
agent are tracked separately in Meeting intelligence design.
These documents describe planned behaviour, not features already available in the desktop host.
The consolidated implementation specification is published as
#64 with the ready-for-agent label.
Kanal is released under the MIT License. The Settings window lists the open-source projects used by Kanal, their licences, and links to the licence text. The texts themselves are not yet bundled with the binary. Downloaded translation models and external services have their own licences and terms. The model catalog identifies the relevant licence for each model and warns when it is not OSI-approved.
Release notes live in CHANGELOG.md and are readable from inside the application
under Settings → Version.
One room. Every language.
同一场会议,每个人的语言。
Visual design: Current UI design.
Kanal turns one laptop into a live translation host for meetings with no shared language. It captures the conversation, presents transcripts and translations side by side, and gives every participant a QR code for a read-only view in their own language. Kanal was built for Chinese, German, and Polish technical meetings, where getting a part number, tolerance, or delivery date right matters more than making the interface feel like a chat app.
The desktop host is built with Avalonia on .NET 10. Speech recognition and translation sit behind provider interfaces, so cloud and local stages can be combined without changing the room model or the mobile client.
Kanal is under active development. The scripted demo needs no API key and is ready to explore once
KANAL_ENV=developmentis set. Live meetings currently require cloud transcription. Read Current limitations and roadmap before relying on Kanal in a meeting.
Kanal has two separate network boundaries: the speech pipeline and the mobile-caption relay. “Local” in a mode refers to the speech stage; it does not mean that the entire meeting runs offline.
| Data | Where it goes |
|---|---|
| Microphone audio | Sent to the cloud speech provider in cloud-transcription modes. It stays on the host in local-transcription modes once a local ASR provider exists. |
| Captions and room state | Sent through the authenticated Kanal gateway (a Cloudflare Worker) to the meeting's private room object, which fans them out to joined phones. Messages include transcript text, translations, speaker labels, language configuration, and pause/recording/lifecycle state. Nothing is stored server-side. |
| Joined-phone cache | The mobile client stores the current room transcript and state in browser localStorage so it can render before a reconnect snapshot arrives. |
| Local recording | Live microphone modes record a WAV file by default in the configured audio folder. Recording pauses with the room and can be disabled in Settings. Kanal never publishes the WAV file. |
| API keys and preferences | Gladia keys selected in the UI are stored as plain JSON in the platform application-data directory. The relay host token is supplied only through the operator machine's runtime environment. |
| Local translation models | Downloaded from the model catalog to the platform application-data directory and loaded in-process with llama.cpp. Model files and generated translations stay on the host, apart from captions sent to the relay. |
Kanal ships without a backing-store URL or bundled API key. The relay is a self-contained Cloudflare Worker, and the Worker address is the only relay address clients see. The join QR contains that public address and a receive-only, room-scoped ticket. Anyone who obtains the bearer invitation can read the room until the ticket expires (currently after 12 hours), but cannot create rooms, publish messages, or enumerate other rooms. Creating a room requires a per-device credential issued by the gateway operator, so one lost laptop can be revoked without rotating credentials on every other machine. Phones need network access to the relay to receive captions.
Linux can build the solution, but Kanal does not yet provide a Linux microphone-capture backend. Packaged desktop releases are not available yet, so run Kanal from source:
git clone https://github.com/TONiiV/Kanal.git
cd Kanal
KANAL_ENV=development dotnet run --project src/Kanal.Host
The first screen selects Chinese, German, and Polish by default. Leave Demo — scripted selected
and press Start. A repeatable trilingual script runs through the real room orchestrator without
an API key or microphone. Scan the displayed QR code to try the mobile view; mobile delivery still
uses the configured caption relay. Demo is offered only when KANAL_ENV is development (any
letter case); in Windows PowerShell, run $env:KANAL_ENV = "development" first.
For a live room:
GLADIA_API_KEY before launching Kanal.A mode is a preset for the transcription and translation stages. Availability is calculated from the providers, key, and downloaded model on the current machine; unavailable rows remain visible and explain what is missing.
| Mode | Transcription | Translation | Speech-pipeline data sent off the host | Current availability |
|---|---|---|---|---|
| Demo — scripted | Scripted | Scripted, or the selected downloaded local model | Nothing | Offered only with KANAL_ENV=development; no keys needed |
| Cloud transcription · cloud translation | Gladia live | Gladia live | Audio | Available with a Gladia key |
| Cloud transcription · local translation | Gladia live | Selected local GGUF model | Audio | Available with a Gladia key and downloaded model |
| Local transcription · cloud translation | Not implemented | Standalone cloud MT not implemented | Text only | Unavailable |
| Local transcription · local translation | Not implemented | Selected local GGUF model | Nothing | Unavailable until local ASR lands |
When started from the production UI, every mode publishes text and room state through the mobile
relay. Cloud-to-local mode disables translation inside the cloud ASR session; the
capability-driven orchestrator then sends final transcripts to the local IMtProvider.
Most configuration is available from the in-app Settings window:
Preferences are written to Kanal/settings.json beneath the operating system's application-data
directory. Downloaded models live in the adjacent Kanal/models directory, and log files in
Kanal/logs. Transcript exports and recordings default to Documents/Kanal.
Logs are written with NLog: one file per day (kanal-<date>.log), rolled over once it passes the
configured size, kept for two weeks, and never sent anywhere.
Environment variables override connection defaults:
| Variable | Purpose |
|---|---|
GLADIA_API_KEY | Fallback speech-provider key when no stored named key is selected |
KANAL_RELAY_URL | Public HTTPS endpoint of the deployed kanal-relay Worker |
KANAL_RELAY_HOST_TOKEN | This desktop's device credential, obtained once with an activation code; runtime only, never part of a build or QR |
KANAL_WEB_URL | Base URL of the static mobile client placed in the join QR code |
KANAL_ENV | development adds Demo — scripted to the mode list; any other value, or none, leaves it out |
The relay remains disabled rather than silently using a public fallback when either relay variable
is absent.
KANAL_RELAY_URL is an address, not a credential: every gateway route still requires the device
credential or a role-scoped room ticket. Missing configuration or a gateway failure does not stop
transcription: the meeting continues without a QR code, and the status bar reports that mobile
delivery is unavailable. Deployment and device-activation commands are in
gateway/README.md.
The default web URL is https://toniiv.github.io/Kanal/. To self-host it, serve
web/index.html over HTTPS and point KANAL_WEB_URL at that URL. The page has no
runtime import, project configuration, external font, or stylesheet dependency. On Start, the
host puts only the gateway address, 12-hour reader ticket, random room capability, and public P-256
verification key in the URL fragment; the fragment is not sent to the web host.
microphone / demo script
│
▼
IAsrProvider ── partials/finals ──► MeetingSession ──► authoritative RoomState
│ │
finals, when ASR │ ├──► Avalonia host columns
cannot translate ▼ └──► transcript export
IMtProvider
│
▼
IRelayPublisher
│
▼
read-only mobile clients
The host is the single authority; clients are projections of its state. MeetingSession branches
on provider capabilities, never on vendor names. Relay transport is isolated behind
IRelayPublisher. Speaker merges are non-destructive: existing utterances retain their original
diarization tag, while clients resolve the canonical speaker at render time.
| Project | Responsibility |
|---|---|
src/Kanal.Core | Provider contracts, room/domain model, orchestration, relay protocol and authenticated gateway publisher |
src/Kanal.Audio | 16 kHz mono PCM16 capture, Windows WASAPI, macOS AudioQueue/CoreAudio, resampling and WAV support |
src/Kanal.Providers.Gladia | Gladia live-v2 session setup, WebSocket streaming, reconnect and wire normalization |
src/Kanal.Providers.LocalMt | In-process llama.cpp translation, prompts, model catalog and downloads |
src/Kanal.Host | Avalonia operator UI, pipeline planning, settings, recording, QR generation and export |
tests/Kanal.Core.UnitTests | Unit tests for audio, providers, serialization, room state, orchestration, and other non-visual services |
tests/Kanal.UI.UnitTests | Headless unit tests for deterministic host view-model and application-state behavior; rendering and layout are intentionally out of scope |
web/index.html | Static mobile client; docs/index.html is its byte-identical GitHub Pages copy |
gateway/ | Relay gateway: Cloudflare Worker plus per-room and device-registry Durable Objects, with its own vitest suite |
tools/Kanal.Doctor | Microphone and live-ASR diagnostics |
The original product requirements and design trade-offs are documented in Chinese in
docs/PRD-v0.4.md. Implementation decisions and measured findings live in
docs/PROGRESS.md.
Build and run the complete test suite from the repository root:
dotnet build Kanal.slnx --configuration Release
dotnet test tests/Kanal.Core.UnitTests/Kanal.Core.UnitTests.csproj --configuration Release --no-build
dotnet test tests/Kanal.UI.UnitTests/Kanal.UI.UnitTests.csproj --configuration Release --no-build
The Core suite covers the room model, audio pipeline, providers, wire protocol, orchestration, and non-visual services. The UI suite uses headless Avalonia only to exercise deterministic view-model and application-state behavior; pixel, layout, style, and window-rendering assertions are out of scope. CI also enforces that the deployable web client and its GitHub Pages copy stay byte-identical:
cmp web/index.html docs/index.html
For pipeline diagnosis:
# Record five seconds, report levels, and write ./mic-check.wav
dotnet run --project tools/Kanal.Doctor -- mic 5
# With GLADIA_API_KEY set, stream a WAV and print raw + normalized events
dotnet run --project tools/Kanal.Doctor -- gladia mic-check.wav
Issues and focused pull requests are welcome. Before changing behavior, read
CLAUDE.md for repository invariants and .impeccable.md for the
interaction and visual constraints.
docs/PROGRESS.md with relevant plans or design decisions.web/index.html and docs/index.html byte-identical.Open a GitHub issue for a bug, proposal, or deployment question before starting a broad change.
IRelayPublisher, but Kanal ships with no alternative today. The default
workers.dev hostname is blocked in mainland China; participants whose phones roam through a
Chinese carrier need the gateway to use a custom domain.Detailed status, benchmarks, and the next implementation steps are tracked in
docs/PROGRESS.md, rather than duplicated here.
The approved next host UI is specified in Meeting workspace design,
including the HTML prototype and implementation checks. Future automatic titles and a listening
agent are tracked separately in Meeting intelligence design.
These documents describe planned behaviour, not features already available in the desktop host.
The consolidated implementation specification is published as
#64 with the ready-for-agent label.
Kanal is released under the MIT License. The Settings window lists the open-source projects used by Kanal, their licences, and links to the licence text. The texts themselves are not yet bundled with the binary. Downloaded translation models and external services have their own licences and terms. The model catalog identifies the relevant licence for each model and warns when it is not OSI-approved.
Release notes live in CHANGELOG.md and are readable from inside the application
under Settings → Version.