pmc-whirlwind 0.3.0

whirlwind is a collaborative Reaper project sync tool for podcast co-editors. It uses Cloudflare R2 for storage and synchronization.
Documentation
# whirlwind

whirlwind is a collaborative Reaper project sync tool for podcast co-editors. It helps a small team keep project files in sync through Cloudflare R2-backed storage.

## Reaper Users

This project uses local paths for media. To make sure you are using local media paths in Reaper, see this recommendation from the docs:

> In **Options > Preferences > Project** and check **"Save project file references with relative pathnames"**. This ensures all media files are stored within the project folder.

## Command Reference

```sh
$ whirlwind help
Collaborative Reaper project sync for podcasters

Usage: whirlwind <COMMAND>

Commands:
  init     Initialize whirlwind config and test R2 connection
  list     List all projects and their lock/push status
  status   Show status of a project (lock info, last push)
  pull     Download a project from R2 to local working directory
  push     Upload local project changes to R2
  session  Pull project, launch Reaper, push on exit
  new      Create a new episode project from a Reaper template
  unlock   Break a stale lock on a project
  help     Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

```

## Creating a New Episode Project

`whirlwind new` automates Reaper project setup for a new episode. It downloads your team's shared template from R2, wires in the recorded audio files, sets the project end marker, and pushes the result back to R2 — ready to open in Reaper.

### Workflow

1. Copy the recorded WAV files into a new episode directory:
   ```sh
   mkdir -p ~/podcast/episodes/ep96-database-history
   cp /path/to/recordings/*.wav ~/podcast/episodes/ep96-database-history/
   ```

2. Run `whirlwind new <episode-directory-name>`:
   ```sh
   whirlwind new ep96-database-history
   ```

3. Open the session in Reaper:
   ```sh
   whirlwind session ep96-database-history
   ```

### How it works

- Downloads `templates/default.rpp` (or a named template) from R2
- Reads `[[new.tracks]]` entries from `~/.config/whirlwind/config.toml` — maps filename glob patterns to named tracks in the template (e.g. `*_erik_*.wav``erik`)
- Matched audio files are inserted into the corresponding template track, preserving its EQ and plugin chain
- Unmatched files (guests) are appended as plain tracks
- The outro track position is set to `project_end - 3s`
- The project end marker is set to `max(track_durations) - trim_seconds`
- The resulting `.rpp` is pushed to R2

### Options

```
whirlwind new <episode-name> [OPTIONS]

Arguments:
  <episode-name>  Name of the episode directory under your working_dir

Options:
  --template <name>         Template to use (default: from config, else "default")
  --trim-seconds <secs>     Seconds to trim from project end (default: from config, else 0)
  --assign <TRACK=FILE>     Assign a file to a named track (repeatable)
  --dry-run                 Show what would happen without writing or pushing anything
```

Use `--assign` to handle filenames that don't match your configured patterns:

```sh
whirlwind new ep96-database-history \
  --assign "erik=riverside_ERIKLONGNAME_raw-audio_ep96.wav" \
  --assign "mike=riverside_MIKELONGNAME_raw-audio_ep96.wav"
```

### Uploading your template

Before using `whirlwind new`, upload your Reaper template to R2:

```sh
aws s3 cp episode-base-template.rpp \
  s3://<bucket>/templates/default.rpp \
  --endpoint-url https://<account_id>.r2.cloudflarestorage.com
```

The template should have empty `<TRACK>` blocks (no `<ITEM>`) for host mic tracks, and fully configured items for intro/outro tracks.

### Config

Add an optional `[new]` section to `~/.config/whirlwind/config.toml` to set defaults and configure track matching:

```toml
[new]
default_template = "default"   # template name in R2 (without file extension)
trim_seconds = 2.0             # trim this many seconds from project end

[[new.tracks]]
track = "erik"             # track name in the Reaper template
pattern = "*_erik_*.wav"       # glob pattern matched against the audio filename

[[new.tracks]]
track = "mike"
pattern = "*_mike_*.wav"
```

`[[new.tracks]]` patterns are matched in order — the first match wins. Use `--assign` to override per-run when a filename doesn't match your usual patterns.

## Purpose

- Keep Reaper project state aligned across collaborators.
- Provide a reliable sync workflow for podcast editing sessions.
- Reduce manual file handoffs between co-editors.

## Common Workflows

This project uses just as the task runner for everyday development commands:

- `just test`: run all tests
- `just test <filter>`: run a filtered subset of tests
- `just fmt`: auto-format code
- `just check`: run formatting checks, clippy, and linting
- `just build`: create a release build

## Stack

- Rust for the CLI and core sync logic
- Cloudflare R2 for remote object storage
- Reaper project files as the collaboration target