149 lines
6.2 KiB
Markdown
149 lines
6.2 KiB
Markdown
# TUI Client – Architecture and Usage
|
||
|
||
## Overview
|
||
|
||
The TUI client (`looper-client`) is a standalone ncurses application that communicates with the looper engine **only** via two named pipes:
|
||
|
||
- `/tmp/looper_cmd` – the client writes text commands to the engine.
|
||
- `/tmp/looper_status` – the engine writes one line per active channel after each main‑loop iteration, reporting the current scene state.
|
||
|
||
The client never links against engine source code. It is built from files in `client/src/` and linked only with `-lncurses`.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
User keypress
|
||
│
|
||
▼
|
||
tui_run() ──► getch() ──► switch(ch)
|
||
│ │
|
||
│ ▼
|
||
│ send_command(cmd)
|
||
│ │
|
||
│ ▼
|
||
│ write("/tmp/looper_cmd")
|
||
│
|
||
│ ┌──────────────────┐
|
||
│ │ Engine main loop │
|
||
│ │ (looper.c) │
|
||
│ │ │
|
||
│ │ looper_process_ │
|
||
│ │ commands() │
|
||
│ │ │ │
|
||
│ │ ▼ │
|
||
│ │ looper_write_ │
|
||
│ │ status() │
|
||
│ │ │ │
|
||
│ └─────────┼────────┘
|
||
│ │
|
||
│ ▼
|
||
│ write("/tmp/looper_status")
|
||
│
|
||
│ read("/tmp/looper_status") ◄──────────── (non‑blocking open)
|
||
│ │
|
||
│ ▼
|
||
▼
|
||
parse_status_line(...)
|
||
│
|
||
▼
|
||
cell_state[ch] = state
|
||
│
|
||
▼
|
||
draw_grid() ──► state_to_color(state) returns colour pair
|
||
apply colour to cell
|
||
```
|
||
|
||
## Key Bindings
|
||
|
||
| Key | Action | FIFO command sent |
|
||
|------------------|---------------------------------------------|------------------------------|
|
||
| `h` / `←` | Move selection left | (none) |
|
||
| `j` / `↓` | Move selection down | (none) |
|
||
| `k` / `↑` | Move selection up | (none) |
|
||
| `l` / `→` | Move selection right | (none) |
|
||
| `t` | Record / toggle on selected column | `record <col>\n` |
|
||
| `s` | Next scene | `scene_next\n` |
|
||
| `S` | Previous scene | `scene_prev\n` |
|
||
| `d` / `D` | Stop all channels | `stop\n` |
|
||
| `a` | Add audio channel | `add\n` |
|
||
| `A` | Add MIDI channel | `add_midi\n` |
|
||
| `r` | Remove last dynamic channel | `remove\n` |
|
||
| `b` | Bind to selected column | `bind <col>\n` |
|
||
| `u` | Unbind (reset to channel 0) | `unbind\n` |
|
||
| `?` | Toggle help text | (none) |
|
||
| `Esc` / `Q` | Quit | (none) |
|
||
|
||
## Status Line Format
|
||
|
||
Each line written by the engine to `/tmp/looper_status` follows this pattern:
|
||
|
||
```
|
||
CH=<channel_number> SC=<scene_index> STATE=<state_string>
|
||
```
|
||
|
||
`<state_string>` is one of `IDLE`, `RECORD`, `LOOPING`, `PAUSED`.
|
||
|
||
Example:
|
||
```
|
||
CH=0 SC=0 STATE=RECORD
|
||
CH=1 SC=0 STATE=LOOPING
|
||
```
|
||
|
||
The client parses these lines and updates the colour of the corresponding cell:
|
||
|
||
- `IDLE` → white (`COLOR_EMPTY`)
|
||
- `RECORD` → red (`COLOR_RECORDING`)
|
||
- `LOOPING` → green (`COLOR_LOOPING`)
|
||
- `PAUSED` → blue (`COLOR_STOPPED`)
|
||
|
||
## Building and Running
|
||
|
||
### Engine
|
||
|
||
```sh
|
||
cd engine
|
||
make # produces `looper`
|
||
```
|
||
|
||
### Client
|
||
|
||
```sh
|
||
cd client
|
||
make # produces `looper-client`
|
||
```
|
||
|
||
### Running Together
|
||
|
||
1. Start the JACK server (e.g., `jackd -d alsa` or `pipewire`).
|
||
2. In a terminal, start the engine:
|
||
```sh
|
||
cd engine && ./looper
|
||
```
|
||
3. In another terminal, start the client:
|
||
```sh
|
||
cd client && ./looper-client
|
||
```
|
||
4. Use the TUI keys described above.
|
||
|
||
## Cleanup
|
||
|
||
When the client exits, it deletes both FIFOs (`/tmp/looper_cmd` and `/tmp/looper_status`).
|
||
If the engine is still running, it will continue to try to write to the status FIFO; that write will fail silently (the engine uses `O_NONBLOCK` and ignores errors).
|
||
The engine creates the status FIFO on startup and does not delete it.
|
||
|
||
## Testing
|
||
|
||
- **Unit test for status line parser**: `make test` in `client/` runs `test_status_parse`.
|
||
- **Integration test for status FIFO** (engine side): `make test` in `engine/tests/` runs `test_status_fifo`.
|
||
|
||
These are **not** executed automatically from the top‑level `make test` – they must be invoked manually or added to the top‑level Makefile.
|
||
|
||
The engine status FIFO test (`test_status_fifo`) uses `select()` with a timeout and retry loop to wait for a status line showing `STATE=RECORD`. It is reliable and does not hang.
|
||
|
||
## Future Work
|
||
|
||
- Replace dead stubs (`FuzzySearch`, `marks`, `yank_buffer`, visual mode) with real implementations or remove them.
|
||
- Support transport play/pause via a dedicated FIFO command.
|
||
- Allow the client to display multiple scenes per channel (e.g., via a tab or side panel).
|
||
- Graceful error recovery when the engine or FIFO is not available.
|