Files
looper/docs/8-tui.md

6.2 KiB
Raw Permalink Blame History

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 mainloop 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")   ◄──────────── (nonblocking 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

cd engine
make           # produces `looper`

Client

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:
    cd engine && ./looper
    
  3. In another terminal, start the client:
    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 toplevel make test they must be invoked manually or added to the toplevel 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.