Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @laceyp99
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ user_vocab.json
.ruff_cache/
.pytest_cache/

# mkdocs build output
site/


# Whisper models are cached in user directory, not in project
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,30 @@ Then open [http://127.0.0.1:8000/](http://127.0.0.1:8000/).

## Quick Start

Murmur supports Windows 10/11 and Python 3.12. A microphone and FFmpeg on
`PATH` are required; an NVIDIA GPU and Ollama are optional.
murmur supports Windows 10/11 and Python 3.12. A microphone is required; an
NVIDIA GPU and Ollama are optional.

```powershell
py -3.12 -m venv venv
venv\Scripts\python.exe -m pip install -e ".[dev]"
venv\Scripts\python.exe run.py
```

See [Getting Started](docs/getting-started.md) for CUDA installation, FFmpeg,
Ollama setup, background launch, first-run behavior, and the recording flow.
See [Getting Started](docs/getting-started.md) for CUDA installation, Ollama
setup, background launch, first-run behavior, and the recording flow.

To build the standalone Windows application folder with PyInstaller:

```powershell
powershell -ExecutionPolicy Bypass -File .\build_windows.ps1
```

The validated output is written to `dist\murmur\`. Distribute the complete
folder. For manual per-user use, extract it to
`%LOCALAPPDATA%\Programs\murmur` and optionally create a Desktop shortcut to
`murmur.exe`. Keep the application files together. See [Build a packaged
release](docs/getting-started.md#build-a-packaged-release) for requirements and
repeat-build instructions.

## Development

Expand Down
94 changes: 94 additions & 0 deletions build_windows.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
[CmdletBinding()]
param(
[switch]$SkipInstall
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

if ($env:OS -ne "Windows_NT") {
throw "The murmur release build must run on Windows."
}

$repoRoot = $PSScriptRoot
$pythonPath = Join-Path $repoRoot "venv\Scripts\python.exe"
$generatedPath = Join-Path $repoRoot "build\windows"
$executablePath = Join-Path $repoRoot "dist\murmur\murmur.exe"

if (-not (Test-Path -LiteralPath $pythonPath -PathType Leaf)) {
throw "Create the repository venv before building: py -3.12 -m venv venv"
}

Push-Location $repoRoot
try {
if (-not $SkipInstall) {
& $pythonPath -m pip install -e ".[packaging]"
if ($LASTEXITCODE -ne 0) {
throw "Failed to install murmur packaging dependencies."
}
}

& $pythonPath tools\prepare_windows_build.py `
--repo-root $repoRoot `
--output-dir $generatedPath
if ($LASTEXITCODE -ne 0) {
throw "Failed to prepare Windows build resources."
}

& $pythonPath -m PyInstaller --noconfirm --clean packaging\murmur.spec
if ($LASTEXITCODE -ne 0) {
throw "PyInstaller failed to build murmur."
}

if (-not (Test-Path -LiteralPath $executablePath -PathType Leaf)) {
throw "Expected executable was not created: $executablePath"
}

$versionInfo = (Get-Item -LiteralPath $executablePath).VersionInfo
$expectedMetadata = @{
FileDescription = "murmur - Local Speech-to-Text Hotkey App"
OriginalFilename = "murmur.exe"
ProductName = "murmur"
}
foreach ($field in $expectedMetadata.Keys) {
if ($versionInfo.$field -ne $expectedMetadata[$field]) {
throw "Executable metadata check failed for $field."
}
}

$selfCheckLog = Join-Path $generatedPath "self-check-error.txt"
Remove-Item -LiteralPath $selfCheckLog -ErrorAction SilentlyContinue
$previousSelfCheckLog = $env:MURMUR_PACKAGING_SELF_CHECK_LOG
$env:MURMUR_PACKAGING_SELF_CHECK_LOG = $selfCheckLog
try {
$selfCheck = Start-Process `
-FilePath $executablePath `
-ArgumentList "--packaging-self-check" `
-WindowStyle Hidden `
-PassThru
}
finally {
$env:MURMUR_PACKAGING_SELF_CHECK_LOG = $previousSelfCheckLog
}

if (-not $selfCheck.WaitForExit(120000)) {
Stop-Process -Id $selfCheck.Id -ErrorAction SilentlyContinue
throw "Packaged dependency self-check timed out after 120 seconds."
}
if ($selfCheck.ExitCode -ne 0) {
$failureDetail = if (Test-Path -LiteralPath $selfCheckLog -PathType Leaf) {
Get-Content -LiteralPath $selfCheckLog -Raw
}
else {
"No Python traceback was captured."
}
throw "Packaged dependency self-check failed with exit code $($selfCheck.ExitCode).`n$failureDetail"
}
Remove-Item -LiteralPath $selfCheckLog -ErrorAction SilentlyContinue

Write-Host "Built and validated $executablePath"
Write-Host "Product version: $($versionInfo.ProductVersion)"
}
finally {
Pop-Location
}
8 changes: 4 additions & 4 deletions docs/failure-and-fallbacks.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Fallbacks And Failure Modes

Murmur treats the live pipeline as an optimization, not the only source of
murmur treats the live pipeline as an optimization, not the only source of
truth. The full recording remains available until finalization, so most live
failures degrade to a slower full-recording path instead of losing the user's
dictation. A recording with no captured audio or a final transcription exception
Expand Down Expand Up @@ -49,7 +49,7 @@ flowchart LR
Disabled --> FullFallback
```

A degraded live path does not mean the recording failed. It means Murmur should
A degraded live path does not mean the recording failed. It means murmur should
ignore partial live output and rebuild the final transcript from the full
recording. Live VAD initialization failure is handled as a disabled optimization
and leads to the same fallback when no live text is available.
Expand All @@ -72,7 +72,7 @@ flowchart TB
```

Both `transcribe_segments()` and `transcribe()` perform the local document
cleanup and optional Ollama pass before returning. This gives Murmur three
cleanup and optional Ollama pass before returning. This gives murmur three
chances to produce useful text:

1. Use the live transcript accumulated during recording.
Expand All @@ -82,7 +82,7 @@ chances to produce useful text:
## Clipboard And Logging Outcomes

Finalization can still succeed even if clipboard copy fails. In that case,
Murmur reports the copy failure. The logger runs after the clipboard attempt, so
murmur reports the copy failure. The logger runs after the clipboard attempt, so
if training data logging is enabled and the log write succeeds, the transcript
and source audio are still saved locally in the opt-in training-data area.

Expand Down
71 changes: 55 additions & 16 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Getting Started

Murmur is a Windows-first desktop application. It records microphone audio,
murmur is a Windows-first desktop application. It records microphone audio,
keeps the complete recording in memory, performs local Whisper transcription,
and copies the final text to the system clipboard. The live VAD and
transcription workers reduce the amount of work left after the stop hotkey, but
Expand All @@ -13,11 +13,10 @@ the full recording remains available as a fallback.
| Windows 10 or 11 | Global hotkeys, tray integration, and Windows media/notification features | Yes |
| Python 3.12 | Supported runtime | Yes |
| Microphone | Audio capture through `sounddevice` | Yes |
| FFmpeg on `PATH` | Audio support used by Whisper | Yes |
| NVIDIA GPU and CUDA-enabled PyTorch | Faster Whisper inference | Optional; CPU fallback is supported |
| Ollama server and configured model | Final punctuation/correction pass | Optional; local cleanup remains available |

Murmur's Whisper inference is local after the model is available. Installing
murmur's Whisper inference is local after the model is available. Installing
Python packages and downloading a Whisper model may require internet access.
Ollama defaults to `http://localhost:11434`; configuring a remote Ollama
endpoint sends the final transcript to that endpoint.
Expand All @@ -33,14 +32,14 @@ venv\Scripts\python.exe -m pip install --upgrade pip

For an NVIDIA GPU, install the CUDA-enabled PyTorch wheel recommended by the
[official PyTorch selector](https://pytorch.org/get-started/locally/) before
installing Murmur. The repository only requires `torch`; the exact CUDA wheel
installing murmur. The repository only requires `torch`; the exact CUDA wheel
depends on the driver and Python environment.

```powershell
venv\Scripts\python.exe -m pip install torch --index-url https://download.pytorch.org/whl/cu121
```

Then install Murmur and its development tools:
Then install murmur and its development tools:

```powershell
venv\Scripts\python.exe -m pip install -e ".[dev]"
Expand All @@ -54,9 +53,6 @@ expected:
venv\Scripts\python.exe -c "import torch; print('torch', torch.__version__); print('cuda build', torch.version.cuda); print('cuda available', torch.cuda.is_available()); print('gpu', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'n/a')"
```

Install FFmpeg separately and add the directory containing `ffmpeg.exe` to
`PATH`. Open a new terminal after changing `PATH`.

## Optional Ollama setup

Ollama is enabled by default, but it is not required for the core
Expand All @@ -68,7 +64,7 @@ ollama pull granite4.1:3b
```

Start Ollama using its desktop service or with `ollama serve`. If the service,
model, or request is unavailable, Murmur keeps the locally cleaned transcript
model, or request is unavailable, murmur keeps the locally cleaned transcript
and continues finalization. Use **Settings > LLM Cleanup > Test Ollama
Connection** to check an endpoint and model.

Expand All @@ -91,14 +87,57 @@ venv\Scripts\python.exe -m src
```

`run_background.vbs` is the convenience launcher for the background mode. It
uses `venv\Scripts\pythonw.exe` when the repository virtual environment exists
and otherwise falls back to `pythonw.exe` from `PATH`.
prefers `venv\Scripts\pythonw.exe` for source development, then uses
`dist\murmur\murmur.exe` when a packaged build exists, and finally falls back
to `pythonw.exe` from `PATH`.

## Build a packaged release

The packaged release keeps the Python source workflow above unchanged while
providing a standalone Windows application folder. Build it from the repository
root with PowerShell:

```powershell
powershell -ExecutionPolicy Bypass -File .\build_windows.ps1
```

The script installs the pinned PyInstaller dependency into `venv`, generates
Windows icon and version resources, builds murmur, and runs a packaged dependency
self-check. The first build can take several minutes because Whisper, Torch, and
their native libraries are analyzed.

The release is written to `dist\murmur\`. Keep that directory together when
copying or distributing the application. For a manual per-user installation,
copy or extract the complete folder to:

```text
%LOCALAPPDATA%\Programs\murmur
```

Launch `murmur.exe` from that folder. Do not move the executable out of the
folder or distribute it by itself. An optional Desktop shortcut can point to
`murmur.exe` while leaving the application files together. The **Start with
Windows** setting in murmur controls automatic launch and does not require a
Desktop or Start Menu shortcut.

End users do not need a separate Python or FFmpeg installation. The first
launch can download the configured Whisper model if it is not already in the
user's cache.

For repeat local builds after dependencies are installed, skip the install step:

```powershell
powershell -ExecutionPolicy Bypass -File .\build_windows.ps1 -SkipInstall
```

For maintainers investigating a packaged app that fails to load its model,
see [Diagnose packaged model loading](troubleshooting.md#diagnose-packaged-model-loading-maintainers).

## First launch

```mermaid
flowchart TB
Launch["Launch Murmur"] --> AppIdentity["Set Windows app identity"]
Launch["Launch murmur"] --> AppIdentity["Set Windows app identity"]
AppIdentity --> LoadConfig["Load config from %APPDATA%\\murmur\\config.json"]
LoadConfig --> ConfigState{"Config present and valid?"}
ConfigState -->|no file| CreateDefaults["Create default config"]
Expand All @@ -119,14 +158,14 @@ flowchart TB
The first launch downloads the selected Whisper model if it is not already in
Whisper's user cache. Ollama warmup checks for an installed model but does not
download one. If the configured hotkey is invalid or cannot be registered,
Murmur attempts to reset it to the default `ctrl+shift+space`; an unrecoverable
murmur attempts to reset it to the default `ctrl+shift+space`; an unrecoverable
registration failure stops startup.

## Record a transcript

1. Wait for the Murmur icon in the Windows system tray.
1. Wait for the murmur icon in the Windows system tray.
2. Press `Ctrl+Shift+Space` (or the configured hotkey) to start recording.
3. Speak normally. Murmur captures 100 ms recorder blocks and processes sealed
3. Speak normally. murmur captures 100 ms recorder blocks and processes sealed
speech segments in background workers.
4. Press the hotkey again to stop, or let the configured maximum duration stop
capture.
Expand All @@ -136,7 +175,7 @@ registration failure stops startup.

Silence closes VAD segments; it does not stop the overall recording. The stop
path uses the accumulated live text when healthy. If live processing is empty
or degraded, Murmur recomputes from the complete recorded audio.
or degraded, murmur recomputes from the complete recorded audio.

## Development checks

Expand Down
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Murmur Pipeline Docs
# murmur Pipeline Docs

This directory documents Murmur's setup, runtime behavior, audio processing
This directory documents murmur's setup, runtime behavior, audio processing
pipeline, and privacy boundaries in more detail than the root README. The
diagrams are written as Mermaid blocks inside Markdown so they are easy to edit,
review, and keep in sync with code changes.
Expand All @@ -19,7 +19,7 @@ review, and keep in sync with code changes.
WebRTC VAD frames and speech segments.
6. [Transcription And Cleanup](transcription-and-cleanup.md) covers Whisper,
transcript accumulation, local cleanup, and optional Ollama cleanup.
7. [Fallbacks And Failure Modes](failure-and-fallbacks.md) shows how Murmur
7. [Fallbacks And Failure Modes](failure-and-fallbacks.md) shows how murmur
recovers when live processing degrades.

## Diagram Editing
Expand Down
2 changes: 1 addition & 1 deletion docs/live-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ for the same local model and keeps output ordering predictable.
If live VAD cannot be initialized—for example, because the configured sample
rate is not supported by WebRTC—the recorder still starts without a live VAD
callback. The stop path then uses the offline fallback. A callback or worker
failure marks the live pipeline degraded; Murmur keeps capturing the full audio
failure marks the live pipeline degraded; murmur keeps capturing the full audio
but ignores partial live output during finalization.

Stopping is deliberately ordered: the recorder is stopped first, then live VAD
Expand Down
10 changes: 5 additions & 5 deletions docs/pipeline.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Pipeline Overview

Murmur has one user-visible workflow: press the hotkey, speak, press the hotkey
murmur has one user-visible workflow: press the hotkey, speak, press the hotkey
again, and paste the final transcript. Internally, that workflow is split into a
live path and a fallback path.

The live path starts VAD segmentation and Whisper transcription while recording
is still active. This lowers stop-time latency because many sealed speech
segments have already been transcribed before the user releases the hotkey.

The fallback path keeps the system reliable. Murmur still records the full audio
The fallback path keeps the system reliable. murmur still records the full audio
clip, so if live VAD or live transcription degrades—or produces no usable text—
finalization can recompute the transcript from the full recording.

Expand Down Expand Up @@ -82,16 +82,16 @@ segment order by `segment_id`.

### Finalization

When recording stops, Murmur stops capture, flushes pending VAD state, drains
When recording stops, murmur stops capture, flushes pending VAD state, drains
queued live transcription work, and first checks the per-recording degraded flag.
If the live path is healthy and its accumulator contains text,
`finalize_segment_texts()` joins the ordered chunks and performs the final
cleanup pass.

If the live path degraded or produced no text, Murmur falls back to the full
If the live path degraded or produced no text, murmur falls back to the full
recorded clip. The fallback path runs offline VAD segmentation and then Whisper
transcription over the resulting speech segments. If offline VAD is unavailable
or finds no speech, Murmur transcribes the full clip directly. Both the segmented
or finds no speech, murmur transcribes the full clip directly. Both the segmented
fallback and full-clip path perform final cleanup through
`transcribe_segments()`; a completed recording receives one final cleanup path,
not one cleanup call per live chunk.
Expand Down
Loading
Loading