# How FluidVoice's Audio History System Works with Budget Controls and ZIP Export

> Discover how FluidVoice's audio history system manages WAV files, enforces gigabyte budgets, and enables ZIP export with JSON manifests. Optimize your storage effortlessly.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: internals
- Published: 2026-07-03

---

**FluidVoice maintains separate WAV recordings for every transcription entry, automatically pruning old files to respect user-defined gigabyte budgets while supporting full ZIP export with JSON manifests.**

The audio history system in the [FluidVoice](https://github.com/altic-dev/FluidVoice) repository preserves raw microphone recordings alongside transcription entries, enabling replay and audit functionality. It implements a three-layer architecture that decouples audio storage from transcription data and user settings, ensuring reliable persistence while respecting storage constraints.

## Core Architecture

The system relies on three specialized stores that handle distinct responsibilities:

- **`DictationAudioHistoryStore`** – Manages WAV file persistence, usage tracking, and ZIP archive generation in [`Sources/Fluid/Persistence/DictationAudioHistoryStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/DictationAudioHistoryStore.swift)
- **`TranscriptionHistoryStore`** – Maintains the list of transcription entries and their associated `DictationAudioMetadata` references in [`Sources/Fluid/Persistence/TranscriptionHistoryStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/TranscriptionHistoryStore.swift)
- **`SettingsStore`** – Persists user budget preferences and converts gigabyte values to byte limits in [`Sources/Fluid/Persistence/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/SettingsStore.swift)

This separation ensures the UI never interacts directly with the file system, operating exclusively through these store interfaces.

## Storing Audio Recordings

When dictation completes, `TranscriptionHistoryStore.addEntry` receives optional `DictationAudioMetadata`. If present, the store invokes `DictationAudioHistoryStore.save(snapshot:entryID:timestamp:model:)` to persist the raw audio.

### WAV File Format and Storage Location

The method writes a **44-byte WAV header** followed by raw PCM samples to a file named `<timestamp>_<entryID>.wav` in the application support directory:

```

~/Library/Application Support/FluidVoice/DictationAudioHistory/

```

The resulting metadata—including file name, size, duration, and sample rate—is stored on the `TranscriptionHistoryEntry`, creating a persistent link between the transcription text and its corresponding audio file.

## Budget Controls and Storage Management

FluidVoice enforces storage limits through a configurable budget system that automatically prunes old recordings when limits are exceeded.

### User Configuration

The budget UI resides in `SettingsView` and persists values through `SettingsStore`:

- **Budget value**: Stored as a `Double` (gigabytes) in `UserDefaults` under the key `AudioHistoryBudgetGB`
- **Byte limit**: Computed on-demand via `SettingsStore.audioHistoryBudgetBytes`:

```swift
var audioHistoryBudgetBytes: Int64 {
    DictationAudioHistoryStore.bytes(forGigabytes: self.audioHistoryBudgetGB)
}

```

### Automatic Pruning Logic

Every time `addEntry` saves new audio, `TranscriptionHistoryStore.pruneAudioToBudget()` executes:

1. Reads current usage via `DictationAudioHistoryStore.audioUsageBytes()`
2. If usage exceeds the byte limit, iterates entries newest-to-oldest
3. Deletes audio files via `deleteAudio(fileName:)` until usage fits under budget
4. Removes orphaned files through `DictationAudioHistoryStore.deleteUnreferencedAudioFiles`

The UI displays current consumption using formatted strings:

```swift
Text("Audio history: \(DictationAudioHistoryStore.formattedGigabytes(self.audioHistoryUsageBytes)) / \(Self.audioBudgetText(for: SettingsStore.shared.audioHistoryBudgetGB)) GB Budget")

```

## ZIP Export Functionality

The system supports exporting audio archives either as complete backups or individual entry pairs, triggered from `SettingsView` (`exportAudioArchive`) or per-entry actions (`exportPair`).

### Export Process

The `DictationAudioHistoryStore.exportAudioArchive(entries:to:)` method implements the following workflow:

1. **Filter entries** – Retains only entries with existing audio files
2. **Create staging directory** – Generates temporary folder under the system temporary directory
3. **Copy audio files** – Copies WAV files to `staging/audio/` with deterministic names (`<timestamp>_<entryID>.wav`)
4. **Build manifest** – Writes `manifest.jsonl` containing JSON lines with relative paths, raw transcripts, final transcripts, and technical metadata
5. **Archive creation** – Executes `/usr/bin/zip` subprocess:

```swift
process.arguments = ["-qr", destinationURL.path, "manifest.jsonl", "audio"]

```

6. **Cleanup** – Removes temporary staging directory via `defer { try? self.fileManager.removeItem(at: staging) }`

### Manifest Structure

The internal `AudioManifestRow` struct encodes each entry with `JSONEncoder(sortedKeys:)`, producing records containing:
- Audio file path (relative to archive)
- `raw_transcript` and `final_transcript` text
- Recording duration, sample rate, channel count
- Application version and model name

## Practical Implementation Examples

### Checking Current Usage and Budget

```swift
let usedBytes = DictationAudioHistoryStore.shared.audioUsageBytes()
let usedGB = DictationAudioHistoryStore.formattedGigabytes(usedBytes)
let budgetGB = SettingsStore.shared.audioHistoryBudgetGB

print("Audio usage: \(usedGB) / \(budgetGB) GB")

```

### Updating Storage Budget

```swift
// Set budget to 2 GB (triggers pruning on next addEntry)
SettingsStore.shared.audioHistoryBudgetGB = 2.0

```

### Manual Pruning

```swift
// Force immediate pruning to respect current budget
let prunedCount = TranscriptionHistoryStore.shared.pruneAudioToBudget()
print("Pruned \(prunedCount) old audio files")

```

### Exporting Complete Archive

```swift
let entries = TranscriptionHistoryStore.shared.entries
let destination = FileManager.default.temporaryDirectory
    .appendingPathComponent("FluidVoice_Export.zip")

do {
    try DictationAudioHistoryStore.shared.exportAudioArchive(
        entries: entries,
        to: destination
    )
    print("Exported archive to \(destination.path)")
} catch {
    print("Export failed: \(error)")
}

```

### Exporting Single Entry Pair

```swift
if let entry = TranscriptionHistoryStore.shared.entries.first {
    let pairURL = FileManager.default.temporaryDirectory
        .appendingPathComponent("EntryPair.zip")
    
    try DictationAudioHistoryStore.shared.exportPair(
        entry: entry,
        to: pairURL
    )
}

```

## Summary

- **FluidVoice** stores raw dictation audio as WAV files in `~/Library/Application Support/FluidVoice/DictationAudioHistory/`, linked to transcription entries via `DictationAudioMetadata`
- **Budget controls** convert user-defined gigabyte limits to byte values via `SettingsStore.audioHistoryBudgetBytes`, automatically pruning oldest recordings when usage exceeds limits through `TranscriptionHistoryStore.pruneAudioToBudget()`
- **ZIP export** creates portable archives containing audio files and JSON line manifests via `/usr/bin/zip`, with automatic cleanup of temporary staging directories
- **Architecture** decouples concerns across three stores: `DictationAudioHistoryStore` handles files, `TranscriptionHistoryStore` manages entry relationships, and `SettingsStore` persists budget configuration

## Frequently Asked Questions

### How does FluidVoice handle storage limits when the audio history budget is exceeded?

When the budget is exceeded, `TranscriptionHistoryStore.pruneAudioToBudget()` automatically deletes the oldest audio files first until usage falls below the configured limit. This process runs every time a new entry is added, checking `DictationAudioHistoryStore.audioUsageBytes()` against the byte limit derived from `SettingsStore.audioHistoryBudgetGB`. Orphaned files without corresponding entries are also removed via `deleteUnreferencedAudioFiles()`.

### What format does FluidVoice use for exported audio archives?

Exported archives are standard ZIP files containing a `manifest.jsonl` file and an `audio/` directory. The manifest uses JSON Lines format with sorted keys, documenting each recording's relative path, raw transcript, final transcript, timestamp, duration, and technical metadata. Audio files maintain their original WAV format with PCM encoding and 44-byte headers.

### Can users export individual transcription entries with their audio?

Yes, the system supports single-entry export through `DictationAudioHistoryStore.exportPair(entry:to:)`. This method creates a ZIP archive containing both the individual WAV file and a corresponding manifest entry, useful for sharing specific recordings or debugging particular transcription sessions.

### Where does FluidVoice store the audio history budget preference?

The budget preference persists in `UserDefaults` under the key `AudioHistoryBudgetGB` as a `Double` value representing gigabytes. `SettingsStore` exposes this as `audioHistoryBudgetGB` and provides the computed `audioHistoryBudgetBytes` property that converts the gigabyte value to bytes for comparison against actual disk usage during pruning operations.