docs: add plan for refactoring TUI into standalone FIFO-client binary

This commit is contained in:
Loic Coenen
2026-05-13 19:27:07 +00:00
committed by Loic Coenen (aider)
parent f3dde6b668
commit 5ad831f50c

136
client/PLAN.md Normal file
View File

@@ -0,0 +1,136 @@
# Plan: Refactor TUI into Standalone FIFOClient Binary
## Goal
Extract the TUI from the existing monolithic codebase into a separate `looper-client` binary that communicates with the engine **only** via the FIFO pipe (`/tmp/looper_cmd`).
The TUI must **not** link to any engine source files (`engine.c`, `dispatcher.c`, `carla.c`, etc.) or use their headers beyond shared type definitions (e.g., `command.h`). All state is maintained by the engine; the TUI sends commands and assumes they succeed.
## Background
- The looper engine runs as a separate JACK client and listens for commands on:
- FIFO pipe (`/tmp/looper_cmd`) textbased commands
- `looper:control` MIDI noteon events
- The current TUI uses a local `Engine` / `AppState` / `dispatcher` that does **not** talk to the real looper. It was designed for unit testing.
## Task 1 Create a new `client/` directory structure
- Keep existing `client/src/tui.c` and `client/src/tui.h`.
- Remove all `#include` directives that reference engine internal headers:
- `engine.h`
- `dispatcher.h`
- `wav_io.h`
- `transport.h`
- `carla.h`
- Remove any `Engine*`, `AppState`, `DispatchFn`, `Clip`, `MidiClip` usage.
- **Keep** the `FuzzySearch` struct, `draw_rack_view()`, `handle_rack_view()`, `list_wav_files()`, `load_sample_callback`, mouse handling, etc. they **are not removed**.
However, every call to engine internals (e.g., `carla_get_available_plugins`, `dispatcher_get_state`, `g_dispatch`, `carla.h` functions) **must be replaced** with a stub that does nothing (or prints a debug message) until the engine implements the corresponding FIFO commands.
This keeps the UI code compilable and preserves the structure for future implementation.
## Task 2 Implement `send_command()` and open FIFO
- Add a function:
```c
int send_command(const char *cmd_line);
```
This function:
- Opens `/tmp/looper_cmd` with `O_WRONLY`.
- Writes the command string (e.g., `"record 0\n"`).
- Appends a newline if missing.
- Closes the file descriptor.
- Returns 0 on success, -1 on error (prints diagnostic to stderr).
- The TUI should call `send_command()` for every user action that should affect the engine.
## Task 3 Map TUI keys to FIFO commands
Replace each `g_dispatch(action)` with a direct FIFO command string.
**Proposed mapping (simplified can be refined later):**
| TUI key/action | FIFO command |
|--------------------------------------------|-----------------------------------------------------|
| `'t'` (trigger clip at selected cell) | `record <channel>\n` (channel = `selected_col`) |
| `'d'` (reset clip) | `stop\n` (global stop) |
| `'s'` (trigger scene current row) | `"scene_next\n"` (or `"scene_add\n"`? TBD) |
| `' '` (toggle transport play/pause) | No corresponding FIFO command yet. Omit for now. |
| `'S'` (stop transport) | `"stop\n"` |
| `'q'` (cycle quantize) | No FIFO equivalent ignore. |
| `'x'` (reset transport) | `"stop\n"` |
| `'N'` (play next scene) | `"scene_next\n"` |
| `'P'` (play previous scene) | `"scene_prev\n"` |
| `'u'` (undo) | No FIFO equivalent ignore. |
| `Ctrl+R` (redo) | Ignore. |
| `'v'`, `'V'` (visual mode) | Keep visual selection logic but send commands only for `'d'` and `'y'` actions. |
| `'y'` (yank) | Do nothing (local clipboard only). |
| `'p'` (paste) | For each pasted cell send `"record <ch>\n"`. |
| `'m'` (move mode) | No effect on engine local navigation. |
| `'z'` (zoom grid selector) | Local navigation only. |
| `'G'` (toggle audio/MIDI grid) | No FIFO command ignore. |
| `'-'` / `'='` (volume) | No FIFO command ignore. |
| `\t` (switch to rack view) | Remove entirely. |
| `':'` command mode | Keep for `:q` (quit) and `:rack` commands (the latter can be removed). |
| Escape / `'Q'` | Quit the TUI (no command sent). |
**Channel binding:**
- When the user moves selection to a new column, send `"bind <col>\n"` to ensure subsequent commands affect the correct channel. This can be done in the navigation switch cases.
## Task 4 Remove all references to `Engine` and `dispatcher`
- Delete the lines:
- `static Engine *g_engine = NULL;`
- `static DispatchFn g_dispatch = NULL;`
- Replace calls like `g_dispatch(action)` with `send_command(formatted_string)`.
- Remove `dispatcher_get_state()` calls the TUI will no longer query the engine state. Update `draw_cell()` to display only static info (clip index) or a fixed colour (e.g., all green). The statedependent colouring is not available without feedback from the engine. For now, show all cells as idle (white) or use a placeholder.
- Remove the line `AppState state; dispatcher_get_state(&state);` inside draw functions.
## Task 5 Simplify the `tui.h` header
- Replace the function signatures:
```c
void tui_init(void); // no Engine* argument
void tui_run(void); // no Engine* argument
void tui_cleanup(void);
```
- Remove `#include "engine.h"` and `#include "dispatcher.h"`.
- Remove the `Engine*` parameter from the init and run functions.
## Task 6 Create `client/main.c`
- Write a simple `main()` that:
- Optionally opens the FIFO for writing just to check it exists.
- Calls `tui_init()`.
- Calls `tui_run()`.
- Calls `tui_cleanup()`.
- Returns 0.
## Task 7 Write `client/makefile`
- Target `looper-client`:
- Compile `src/tui.c` and `src/main.c` (or `src/client.c` if split).
- **Do not** link to any engine `.o` files.
- Link only with `-lncurses` (and `-lm` if needed).
- Example:
```makefile
CC ?= gcc
CFLAGS ?= -Wall -Wextra -g -I../engine/src
LDFLAGS ?= -lncurses -lm
looper-client: src/tui.c src/main.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
```
## Task 8 Remove dead code and unnecessary helpers
- Delete `utils.c` / `utils.h` if they existed only for TUI but keep all WAVrelated, rackrelated, and fuzzysearch code (they are now stubs).
- Remove `clip_state_to_string()`, `transport_state_to_string()`, `quantize_mode_to_string()`, `clock_source_to_string()` they are no longer used for display because we have no `AppState`.
Replace them with static strings that show placeholder text (e.g., `"N/A"`).
- Remove `state_to_color()` instead use a fixed colour pair (e.g., all cells white) or remove colour entirely, because we have no clip state.
- Remove the `mouse` callback if it relied on `dispatcher_get_state` but keep the function body as a noop.
## Task 9 Test the new client
- Build `looper-client` and verify it compiles without engine object files.
- Start the engine (`./looper` in `engine/`).
- Run `./looper-client` and press keys that should generate FIFO commands. Use `cat /tmp/looper_cmd` in another terminal to verify output.
- Check that commands like `record 2`, `stop`, `bind 3` appear.
## Notes / Future Improvements
- **State feedback:** The TUI currently shows clip state colours. To restore that, a separate FIFO (or shared memory) for engine>client status could be added. Not part of this plan.
- **MIDI grid / rack view:** These depend on engine features not yet exposed via FIFO. They are removed; can be readded later.
- **Transport commands:** The engine does not have a dedicated transport play/pause command via FIFO; it relies on MIDI notes. Future FIFO extension needed.
This plan produces a clean, minimal client that interfaces only through the named pipe.
````