subsequence.display =================== .. py:module:: subsequence.display .. autoapi-nested-parse:: Live terminal dashboard for composition playback. Provides a persistent status line showing the current bar, section, chord, BPM, and key. Optionally renders an ASCII grid visualisation of all running patterns above the status line, showing which steps have notes and at what velocity. Log messages scroll above the dashboard without disruption. Enable it with a single call before ``play()``: .. code-block:: python composition.display() # status line only composition.display(grid=True) # status line + pattern grid composition.play() The status line updates every beat and looks like:: 125 BPM Key: E Bar: 17 [chorus 1/8] Chord: Em7 The grid (when enabled) updates every bar and looks like:: kick_2 |X . . . X . . . X . . . X . . .| snare_1 |. . . . X . . . . . . . X . . .| bass |X . . X . . X . X . . . X . . .| Module Contents --------------- .. py:class:: Display(composition: subsequence.composition.Composition, grid: bool = False, grid_scale: float = 1.0) Live-updating terminal dashboard showing composition state. Reads bar, section, chord, BPM, and key from the ``Composition`` and renders a persistent region to stderr. When ``grid=True`` an ASCII pattern grid is rendered above the status line. A custom ``DisplayLogHandler`` ensures log messages scroll cleanly above the dashboard. .. rubric:: Example .. code-block:: python composition.display(grid=True) composition.play() Store composition reference for reading playback state. :param composition: The ``Composition`` instance to read state from. :param grid: When True, render an ASCII grid of running patterns above the status line. :param grid_scale: Horizontal zoom factor for the grid (default ``1.0``). Snapped internally to the nearest integer ``cols_per_step`` for uniform marker spacing. .. py:method:: clear_line() -> None Erase the entire dashboard region from the terminal. .. py:method:: draw() -> None Write the current dashboard to the terminal. .. py:method:: start() -> None Install the log handler and activate the display. Saves existing root logger handlers and replaces them with a ``DisplayLogHandler`` that clears/redraws the status line around each log message. Original handlers are restored by ``stop()``. .. py:method:: stop() -> None Clear the status line and restore original log handlers. .. py:method:: update(_: int = 0) -> None Rebuild and redraw the dashboard; called on ``"bar"`` and ``"beat"`` events. The integer argument (bar or beat number) is ignored — state is read directly from the composition. Note: "bar" and "beat" events are emitted as ``asyncio.create_task`` at the start of each pulse, but the tasks only execute *after* ``_advance_pulse()`` completes (which includes sending MIDI via ``_process_pulse()``). The display therefore always trails the audio slightly — this is inherent to the architecture and cannot be avoided without restructuring the sequencer loop. .. py:class:: DisplayLogHandler(display: Display) Bases: :py:obj:`logging.Handler` Logging handler that clears and redraws the status line around log output. Installed by ``Display.start()`` and removed by ``Display.stop()``. Ensures log messages do not overwrite or corrupt the persistent status line. Store reference to the display for clear/redraw calls. .. py:method:: emit(record: logging.LogRecord) -> None Clear the status line, write the log message, then redraw. .. py:class:: GridDisplay(composition: subsequence.composition.Composition, scale: float = 1.0) Multi-line ASCII grid visualisation of running pattern steps. Renders one block per pattern showing which grid steps have notes and at what velocity. Drum patterns (those with a ``drum_note_map``) show one row per drum sound; pitched patterns show a single summary row. Not used directly — instantiated by ``Display`` when ``grid=True``. Store composition reference for reading pattern state. :param composition: The ``Composition`` instance to read running patterns from. :param scale: Horizontal zoom factor. Snapped to the nearest integer ``cols_per_step`` so that all on-grid markers are uniformly spaced. Default ``1.0`` = one visual column per grid step (current behaviour). .. py:method:: build() -> None Rebuild grid lines from the current state of all running patterns. .. py:property:: line_count :type: int Number of terminal lines the grid currently occupies.