Metadata-Version: 2.4
Name: cjm-fasthtml-web-audio
Version: 0.0.10
Summary: A reusable Web Audio API manager for FastHTML applications with multi-buffer support, parallel decode, and card-stack-compatible playback.
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
License: Apache-2.0
Project-URL: Repository, https://github.com/cj-mills/cjm-fasthtml-web-audio
Project-URL: Documentation, https://cj-mills.github.io/cjm-fasthtml-web-audio
Keywords: nbdev,jupyter,notebook,python
Classifier: Natural Language :: English
Classifier: Intended Audience :: Developers
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-fasthtml
Requires-Dist: cjm-fasthtml-tailwind
Requires-Dist: cjm-fasthtml-daisyui
Requires-Dist: cjm-fasthtml-card-stack
Requires-Dist: cjm-fasthtml-keyboard-navigation
Requires-Dist: cjm-fasthtml-app-core
Dynamic: license-file

# cjm-fasthtml-web-audio


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Install

``` bash
pip install cjm_fasthtml_web_audio
```

## Project Structure

    nbs/
    ├── components.ipynb # FastHTML component helpers for the Web Audio API manager
    ├── js.ipynb         # JavaScript generation for the Web Audio API manager
    └── models.ipynb     # Configuration and HTML ID types for the Web Audio API manager

Total: 3 notebooks

## Module Dependencies

``` mermaid
graph LR
    components[components<br/>components]
    js[js<br/>js]
    models[models<br/>models]

    components --> js
    components --> models
    js --> models
```

*3 cross-module dependencies detected*

## CLI Reference

No CLI commands found in this project.

## Module Overview

Detailed documentation for each module in the project:

### components (`components.ipynb`)

> FastHTML component helpers for the Web Audio API manager

#### Import

``` python
from cjm_fasthtml_web_audio.components import (
    DEFAULT_STATIC_MOUNT_PATH,
    render_audio_urls_input,
    render_web_audio_script,
    mount_web_audio_static,
    render_initial_speed_sync,
    render_speed_selector
)
```

#### Functions

``` python
def render_audio_urls_input(
    config: WebAudioConfig,    # Instance configuration
    audio_urls: List[str],     # Audio file URLs to load
    oob: bool = False,         # Whether to render as OOB swap
) -> Any:  # Hidden input element with JSON-encoded URLs
    "Render a hidden input storing audio URLs as JSON."
```

``` python
def render_web_audio_script(
    config: WebAudioConfig,        # Instance configuration
    focus_input_id: str,           # Hidden input ID for focused index
    card_stack_id: str,            # Card stack container ID
    nav_down_btn_id: str = "",     # Nav down button ID (for auto-navigate)
) -> Any:  # Script element with complete Web Audio JS
    "Render the complete Web Audio API script for a configured instance."
```

``` python
def mount_web_audio_static(
    app,                                            # FastHTML/Starlette app
    mount_path: str = DEFAULT_STATIC_MOUNT_PATH,    # URL prefix to mount static dir at
) -> str:                                           # URL of the SoundTouch worklet processor
    """
    Mount the library's vendored static assets (SoundTouch worklet, license).
    
    Inserts the Mount at `app.routes[0]` so it's matched BEFORE any catch-all
    routes FastHTML may have registered. Returns the URL where the SoundTouch
    worklet processor is served; when `mount_path` is the default the URL equals
    `DEFAULT_WORKLET_URL` — so `WebAudioConfig(enable_speed=True)` with no explicit
    `worklet_url` just works.
    """
```

``` python
def render_initial_speed_sync(
    config: WebAudioConfig,     # Instance configuration
    speed: float,               # Persisted playback speed (e.g. from step state)
) -> Any:                       # Script element (empty if speed is default or speed is disabled)
    """
    Render a <Script> that syncs the JS state's playbackSpeed to `speed` after insertion.
    
    Polls briefly (~1 second, 20ms intervals) for `window.set{Ns}Speed` so the helper
    works regardless of whether the web-audio script or the toolbar script runs first.
    Returns an empty Script when `speed == 1.0` (state_init already defaults to 1.0)
    or when `config.enable_speed` is False.
    """
```

``` python
def render_speed_selector(
    config: WebAudioConfig,          # Instance configuration (must have enable_speed=True for sync to fire)
    current_speed: float = 1.0,      # Current / persisted playback speed
    change_url: str = "",            # URL to POST speed changes to (empty → no server persist)
    label: Optional[str] = "Speed:", # Leading label text (None/"" → omit label span)
) -> Any:                            # Div(Span?, Select, sync Script)
    """
    Render the shared playback speed selector.
    
    Emits a DaisyUI `<select>` whose `onchange` calls `window.set{Ns}Speed(...)` for
    immediate JS state update. When `change_url` is provided, also POSTs to that URL
    via HTMX for server-side persistence (consumers typically return `Script(generate_speed_change_js(...))`).
    
    Also emits `render_initial_speed_sync(config, current_speed)` so the JS state
    reconciles to `current_speed` on initial render and OOB swaps — the `<option selected>`
    attribute only restores the dropdown visually.
    """
```

#### Variables

``` python
DEFAULT_STATIC_MOUNT_PATH = '/static/cjm-web-audio'
```

### js (`js.ipynb`)

> JavaScript generation for the Web Audio API manager

#### Import

``` python
from cjm_fasthtml_web_audio.js import (
    DEFAULT_WORKLET_URL,
    PLAYBACK_SPEEDS,
    generate_state_init,
    generate_init_audio,
    generate_stop_audio,
    generate_play_segment,
    generate_optional_features,
    generate_speed_change_js,
    generate_focus_change,
    generate_htmx_settle_handler,
    generate_web_audio_js
)
```

#### Functions

``` python
def generate_state_init(
    config: WebAudioConfig,  # Instance configuration
) -> str:  # JS code that initializes the state object
    "Generate JS code to initialize the namespaced state object."
```

``` python
def generate_init_audio(
    config: WebAudioConfig,  # Instance configuration
) -> str:  # JS init function
    """
    Generate JS function that loads and decodes audio files in parallel.
    
    When `config.enable_speed=True`, the SoundTouch worklet processor is registered
    on the AudioContext before decoding. On registration failure, `workletRegistered`
    remains false and the play path falls back to naive (pitch-shifting) playbackRate.
    """
```

``` python
def generate_stop_audio(
    config: WebAudioConfig,  # Instance configuration
) -> str:  # JS stop function
    "Generate JS function that stops current playback."
```

``` python
def generate_play_segment(
    config: WebAudioConfig,  # Instance configuration
    nav_down_btn_id: str = "",  # Nav down button ID (for auto-navigate)
) -> str:  # JS play function
    """
    Generate JS function that plays a segment from a specific buffer.
    
    When `config.enable_speed=True` and speed != 1.0 and the SoundTouch worklet is
    registered, audio flows BufferSource -> SoundTouchNode -> destination, with
    pitch auto-compensated by the worklet so tempo changes preserve pitch. Otherwise
    audio flows BufferSource -> destination (naive path; pitch shifts at non-1x).
    """
```

``` python
def _speed_values_js_array() -> str:  # JS array literal of speed values
    """Render PLAYBACK_SPEEDS values as a JS array literal (e.g., '[0.5, 0.75, ...]')."""
    return "[" + ", ".join(str(s) for s, _ in PLAYBACK_SPEEDS) + "]"


def generate_optional_features(
    config: WebAudioConfig,  # Instance configuration
) -> str:  # JS for optional feature functions (empty if none enabled)
    "Render PLAYBACK_SPEEDS values as a JS array literal (e.g., '[0.5, 0.75, ...]')."
```

``` python
def generate_optional_features(
    config: WebAudioConfig,  # Instance configuration
) -> str:  # JS for optional feature functions (empty if none enabled)
    """
    Generate JS for optional features based on config flags.
    
    When `config.enable_speed=True`, emits `set{Ns}Speed`, `cycle{Ns}SpeedTo`,
    `cycle{Ns}SpeedUp`, `cycle{Ns}SpeedDown`. The cycle helpers drive the shared
    speed `<select>` via a synthetic `change` event so the dropdown, JS state, and
    HTMX-backed server persistence stay in sync through a single code path.
    """
```

``` python
def generate_speed_change_js(
    config: WebAudioConfig,  # Instance configuration
    speed: float,            # New playback speed
) -> str:  # JS snippet: updates JS playbackSpeed via set{Ns}Speed
    """
    Generate the JS body returned by a consumer's `/speed_change` POST handler.
    
    Consumers wrap this in a `Script(...)` and return it so the client-side state
    updates after the server persists the new speed. Guarded with `if (window.set{Ns}Speed)`
    to tolerate the Script running before the web-audio script initializes.
    """
```

``` python
def generate_focus_change(
    config: WebAudioConfig,  # Instance configuration
    focus_input_id: str,  # Hidden input ID for focused index
) -> str:  # JS focus change callback
    "Generate JS focus change callback for card stack integration."
```

``` python
def generate_htmx_settle_handler(
    config: WebAudioConfig,  # Instance configuration
    card_stack_id: str,  # Card stack container ID
) -> str:  # JS HTMX afterSettle handler
    """
    Generate HTMX afterSettle handler for card stack navigation.
    
    When `config.should_play_fn` is set, delegates the play guard to a consumer-defined
    window function (e.g., `window.shouldAlignPlay()`). Otherwise uses the default
    inline guard based on zone active state and auto-navigate flag.
    """
```

``` python
def generate_web_audio_js(
    config: WebAudioConfig,  # Instance configuration
    focus_input_id: str,  # Hidden input ID for focused index
    card_stack_id: str,  # Card stack container ID
    nav_down_btn_id: str = "",  # Nav down button ID (for auto-navigate)
) -> str:  # Complete JS code for this instance
    "Generate the complete Web Audio API JS for a configured instance."
```

#### Variables

``` python
DEFAULT_WORKLET_URL = '/static/cjm-web-audio/soundtouch-processor.js'
PLAYBACK_SPEEDS = [8 items]
```

### models (`models.ipynb`)

> Configuration and HTML ID types for the Web Audio API manager

#### Import

``` python
from cjm_fasthtml_web_audio.models import (
    WebAudioConfig,
    WebAudioHtmlIds
)
```

#### Classes

``` python
@dataclass
class WebAudioConfig:
    "Configuration for a Web Audio API manager instance."
    
    namespace: str  # Unique prefix (e.g., "align", "review")
    indicator_selector: str  # CSS selector for playing indicators
    data_index_attr: str = 'audioFileIndex'  # Data attr name for buffer index
    data_start_attr: str = 'startTime'  # Data attr name for start time
    data_end_attr: str = 'endTime'  # Data attr name for end time
    enable_speed: bool = False  # Playback speed support (pitch-preserving via SoundTouch worklet)
    enable_replay: bool = False  # Replay current segment support
    enable_auto_nav: bool = False  # Auto-navigate on completion support
    should_play_fn: str = ''  # Named window function for custom play guard (replaces default zone guard)
    worklet_url: Optional[str]  # URL of vendored soundtouch-processor.js; None -> default mount path
    
    def ns(self) -> str:  # Capitalized namespace for JS function names
            """Capitalized namespace for JS function names (e.g., 'align' -> 'Align')."""
            return self.namespace.capitalize()
    
        @property
        def state_key(self) -> str:  # JS state object key
        "Capitalized namespace for JS function names (e.g., 'align' -> 'Align')."
    
    def state_key(self) -> str:  # JS state object key
        "JS global state object key (e.g., '_webAudio_align')."
```

``` python
class WebAudioHtmlIds:
    "HTML ID generators for Web Audio manager elements."
    
    def audio_urls_input(
            namespace: str  # Instance namespace
        ) -> str:  # HTML ID for audio URLs hidden input
        "ID for the hidden input storing audio file URLs as JSON."
    
    def speed_select(
            namespace: str  # Instance namespace
        ) -> str:  # HTML ID for the playback speed <select>
        "ID for the playback speed selector element."
```
