diff --git a/.env.example b/.env.example index 6e0033c..0f6f81d 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,32 @@ -# Docker Compose defaults. Copy this file to .env and set a long, private API -# token. The supplied Compose file keeps the API inside its Docker network. +# Docker Compose defaults. Copy this file to .env, then set a long, private API +# token. Home Assistant app users set the equivalent values in the app's +# Configuration tab instead. The supplied Compose file keeps the API inside +# its Docker network; the integration must be able to reach this address. +# +# Listen address and port. Keep HOST as 0.0.0.0 in the container so Home +# Assistant can connect. PORT is also the port used in the integration URL. VIRTUAL_CARILLON_HOST=0.0.0.0 VIRTUAL_CARILLON_PORT=9876 + +# Persistent runtime data: the SQLite schedule/history, imported recordings, +# LitCal cache, and rendered audio cache. Do not point this at source control. VIRTUAL_CARILLON_DATA_DIR=/app/.data + +# Bearer token for every /api/* request. /health remains public for health +# checks. Set this before allowing anything beyond a trusted local network. VIRTUAL_CARILLON_API_TOKEN= + +# Default acoustic character for generated bells and hymns. Valid profiles are +# near, church-grounds, quarter-mile, half-mile, and one-mile. Home Assistant +# schedules can choose the same profiles in the integration settings. VIRTUAL_CARILLON_DISTANCE_PROFILE=half-mile + +# Generated WAV sample rate. Valid values are 44100 and 48000 Hz. VIRTUAL_CARILLON_SAMPLE_RATE=44100 -# LitCal connection defaults. Automatic Home Assistant routines always use -# LitCal; each Home Assistant schedule stores its selected calendar. +# LitCal connection. The URL should point to a compatible Liturgical Calendar +# API. The calendar here is the default for CLI/API requests; each Home +# Assistant schedule stores its own calendar choice in the integration. VIRTUAL_CARILLON_LITCAL_URL=https://litcal.johnromanodorazio.com/api/v5 +# Valid calendars: general, US, IT, NL, VA, or CA. VIRTUAL_CARILLON_LITCAL_CALENDAR=general diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30e9505..15c4d5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,11 +43,11 @@ jobs: run: pnpm release:check - name: Validate Home Assistant integration syntax - run: python3 -m compileall -q custom_components/virtual_carillon + run: python3 -m compileall -q homeassistant/integration - name: Validate JSON metadata run: | - python3 -m json.tool custom_components/virtual_carillon/manifest.json >/dev/null + python3 -m json.tool homeassistant/integration/manifest.json >/dev/null python3 -m json.tool hacs.json >/dev/null container: diff --git a/.github/workflows/publish-app.yml b/.github/workflows/publish-app.yml index d747728..191d21e 100644 --- a/.github/workflows/publish-app.yml +++ b/.github/workflows/publish-app.yml @@ -61,7 +61,7 @@ jobs: run: pnpm release:check -- "${RELEASE_VERSION#v}" - name: Validate Home Assistant integration syntax - run: python3 -m compileall -q custom_components/virtual_carillon + run: python3 -m compileall -q homeassistant/integration - name: Log in to GHCR uses: docker/login-action@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a5bce5..3fe88d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.1.0-beta.2 + +Follow-up beta release containing the polish and cleanup that missed the initial beta. + +- Updated the Virtual Carillon logo and Home Assistant app branding. +- Reorganized the engine, Home Assistant integration, and documentation for clearer ownership and maintenance. +- Cleaned up and expanded project documentation and contributor guidance. +- Applied minor code-quality, build, CI, and release-process improvements. + ## 0.1.0-beta.1 First public beta release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a29c3ee..e7ad9a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thank you for helping improve Virtual Carillon. Contributions should preserve a ## Before you begin -Read the [development guide](doc/development.md) for setup, checks, deployment helpers, and releases. The [architecture guide](doc/architecture.md) explains the project boundaries, and the [testing guide](doc/testing.md) describes the expected smoke tests. +Read the [development guide](docs/development.md) for setup, architecture, checks, testing, deployment helpers, and releases. For a bug, include the deployment method, the relevant date and calendar when liturgical selection is involved, steps to reproduce, and the useful portion of the logs. Remove API tokens, private URLs, personal information, and private recordings before posting. @@ -57,7 +57,7 @@ Run the same combined checks locally with `pnpm ci:check` before requesting revi ## Development deployment helper -`scripts/push-live.sh` is available for testing Home Assistant integration changes on a configured remote Docker installation. It type-checks the project, copies the current working tree’s `custom_components/virtual_carillon` directory over SSH, restarts the target Home Assistant container, and waits for it to become healthy. +`scripts/push-live.sh` is available for testing Home Assistant integration changes on a configured remote Docker installation. It type-checks the project, copies the current working tree’s `homeassistant/integration` directory into the target Home Assistant container’s `/config/custom_components/virtual_carillon` location over SSH, restarts the container, and waits for it to become healthy. It reads `REMOTE_HOST` and `HA_CONTAINER` from a local ignored `dev.env` file. This is a development convenience, not a substitute for CI or the release process. Check the current branch, working tree, remote host, and target container before running it. It does not deploy the engine container or publish a release. @@ -65,7 +65,7 @@ It reads `REMOTE_HOST` and `HA_CONTAINER` from a local ignored `dev.env` file. T ## Releases -Release versions must agree in `package.json`, `custom_components/virtual_carillon/manifest.json`, `homeassistant/app/config.yaml`, and `Dockerfile`. Run: +Release versions must agree in `package.json`, `homeassistant/integration/manifest.json`, `homeassistant/app/config.yaml`, and `Dockerfile`. Run: ```bash pnpm release:check @@ -91,4 +91,4 @@ git status git diff --check ``` -Do not commit `.env` or `dev.env` files, credentials, runtime data, private recordings, generated audio, `dist/`, `node_modules/`, Python bytecode, coverage output, or editor files. Keep test data disposable and avoid placing personal deployment details in examples. +Do not commit `.env` or `dev.env` files, credentials, runtime data, private recordings, generated audio, `engine/dist/`, `node_modules/`, Python bytecode, coverage output, or editor files. Keep test data disposable and avoid placing personal deployment details in examples. diff --git a/Dockerfile b/Dockerfile index c7be1ea..c4b4f62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,19 +6,10 @@ COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY tsconfig.json eslint.config.mjs .prettierrc.json ./ COPY scripts ./scripts -COPY src ./src +COPY engine/src ./engine/src RUN pnpm build -FROM node:24-bookworm-slim AS runtime -ARG BUILD_VERSION=0.1.0-beta.1 -ARG BUILD_ARCH=amd64 -LABEL \ - io.hass.version="${BUILD_VERSION}" \ - io.hass.type="app" \ - io.hass.arch="aarch64|amd64" \ - org.opencontainers.image.title="Virtual Carillon" \ - org.opencontainers.image.source="https://github.com/HPaulson/virtual-carillon" \ - org.opencontainers.image.licenses="MIT" +FROM node:24-bookworm-slim AS runtime-deps ENV NODE_ENV=production \ VIRTUAL_CARILLON_HOST=0.0.0.0 \ VIRTUAL_CARILLON_PORT=9876 \ @@ -30,13 +21,24 @@ RUN apt-get update \ RUN corepack enable COPY package.json pnpm-lock.yaml ./ RUN pnpm install --prod --frozen-lockfile -COPY --from=build /app/dist ./dist -COPY bin ./bin + +FROM runtime-deps AS runtime +ARG BUILD_VERSION=0.1.0-beta.2 +ARG BUILD_ARCH=amd64 +LABEL \ + io.hass.version="${BUILD_VERSION}" \ + io.hass.type="app" \ + io.hass.arch="aarch64|amd64" \ + org.opencontainers.image.title="Virtual Carillon" \ + org.opencontainers.image.source="https://github.com/HPaulson/virtual-carillon" \ + org.opencontainers.image.licenses="MIT" +COPY --from=build /app/engine/dist ./engine/dist +COPY engine/bin ./engine/bin COPY homeassistant ./homeassistant COPY README.md LICENSE ./ -COPY homeassistant/app/entrypoint.mjs ./bin/virtual-carillon-app-entrypoint.mjs +COPY homeassistant/app/entrypoint.mjs ./engine/bin/virtual-carillon-app-entrypoint.mjs RUN mkdir -p /app/.data/cache EXPOSE 9876 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:9876/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" -CMD ["node", "bin/virtual-carillon-app-entrypoint.mjs"] +CMD ["node", "engine/bin/virtual-carillon-app-entrypoint.mjs"] diff --git a/README.md b/README.md index 3573ec4..4e2e211 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Virtual Carillon

- Virtual Carillon + Virtual Carillon

-Virtual Carillon turns your speakers into a programmable carillon. Play a bell, signal, or liturgically-appropriate Catholic hymn whenever you like. +Virtual Carillon turns your speakers into a programmable Church carillon. Use it with Home Assistant to build routines such as: @@ -20,7 +20,7 @@ You can also run the carillon on its own from the command line or connect to it Most people will use Virtual Carillon with [Home Assistant](https://www.home-assistant.io/getting-started/), a free, self-hosted home-automation system. The included Home Assistant Integration provides a user-friendly GUI to interact with Virtual Carillon. We also provide Virtual Carillon as a standalone app for advanced users who are comfortable working from the command line and don't want to use Home Assistant. -[HACS](https://hacs.xyz/docs/use/) (Home Assistant Community Store) is an optional community store inside Home Assistant that we recommend for most users. It makes the Virtual Carillon integration easy to install, but it is not required if you prefer to copy the integration files manually. +[HACS](https://hacs.xyz/docs/use/) (Home Assistant Community Store) is an optional community store inside Home Assistant. This repository keeps all Home Assistant sources under `homeassistant/`; use the manual installation path below unless a HACS release package is provided. For either Home Assistant path, you must have at least one working Home Assistant `media_player` (speaker) before setting up Virtual Carillon. @@ -42,8 +42,7 @@ Use this path when your Home Assistant installation has **Settings → Apps**. I 3. Open the app’s **Configuration** tab. Set **API token** to a long, unique private value, choose **Save**, and restart the app if Home Assistant asks. 4. Install the integration: - - **With HACS (Recommended):** open **HACS → Integrations**. Open the three-dot menu, choose **Custom repositories**, add `https://github.com/HPaulson/virtual-carillon` with category **Integration**, then choose **Add**. Search HACS for **Virtual Carillon**, open it, and choose **Download**. Restart Home Assistant when the download finishes. - - **Without HACS:** copy this repository’s `custom_components/virtual_carillon` directory to `/config/custom_components/virtual_carillon/`, then restart Home Assistant. + - **Manual installation:** copy this repository’s `homeassistant/integration` directory to `/config/custom_components/virtual_carillon/`, then restart Home Assistant. 5. Open **Settings → Devices & services → Add integration**, search for **Virtual Carillon**, and enter: @@ -81,7 +80,7 @@ Use this path when Home Assistant itself runs in a Docker container. Virtual Car The `virtual-carillon_default` network is created by the command in step 3. Once both containers share it, Home Assistant can reach the engine as `http://virtual-carillon:9876`. Keep this connection in your own Compose configuration if you recreate the Home Assistant container. -5. In the Home Assistant web interface, install the integration through HACS: **HACS → Integrations → three-dot menu → Custom repositories**. Add `https://github.com/HPaulson/virtual-carillon` as an **Integration**, download **Virtual Carillon**, and restart Home Assistant. Without HACS, copy `custom_components/virtual_carillon` to `/config/custom_components/virtual_carillon/` and restart instead. +5. Copy this repository’s `homeassistant/integration` directory to `/config/custom_components/virtual_carillon/`, then restart Home Assistant. HACS custom repositories currently require the integration under a repository-root `custom_components/` directory, so this source layout is intentionally installed manually. 6. Open **Settings → Devices & services → Add integration**, search for **Virtual Carillon**, and enter: - **Engine URL:** `http://virtual-carillon:9876` @@ -94,7 +93,7 @@ Do not run the Home Assistant app and the Compose service against the same port ### 3. Standalone engine (advanced) -Use this path only when you are comfortable working with the command line without a GUI to interact with the Virtual Carillon. It can play the host’s default audio output, but local speakers, Bluetooth, and unusual audio setups are outside the project’s supported deployment paths. The CLI and [HTTP API](doc/api.md) are provided for experienced users to integrate as they see fit. +Use this path only when you are comfortable working with the command line without a GUI to interact with the Virtual Carillon. It can play the host’s default audio output, but local speakers, Bluetooth, and unusual audio setups are outside the project’s supported deployment paths. The CLI and [HTTP API](docs/api.md) are provided for experienced users to integrate as they see fit. Install Node.js 24 or later and the pnpm version declared in `package.json`, then run: @@ -103,18 +102,18 @@ pnpm install --frozen-lockfile pnpm build # List the available bells, signals, and hymns. -node dist/cli/index.js assets +node engine/dist/cli/index.js assets # Check whether this host has an available local audio output. -node dist/cli/index.js doctor -node dist/cli/index.js devices +node engine/dist/cli/index.js doctor +node engine/dist/cli/index.js devices # Play through the default local output. -node dist/cli/index.js play test-bell -node dist/cli/index.js play angelus +node engine/dist/cli/index.js play test-bell +node engine/dist/cli/index.js play angelus ``` -To expose the API on the local machine, run `node dist/cli/index.js server`. Set `VIRTUAL_CARILLON_API_TOKEN` before binding it to any network address. Run `node dist/cli/index.js --help` for the complete command reference. +To expose the API on the local machine, run `node engine/dist/cli/index.js server`. Set `VIRTUAL_CARILLON_API_TOKEN` before binding it to any network address. Run `node engine/dist/cli/index.js --help` for the complete command reference. ## Scheduling in Home Assistant @@ -129,11 +128,11 @@ The editor has one Westminster schedule and three routine modes: | **Category — Select from a hymn category** | Variety within a chosen category, such as Marian or Eucharistic. | | **Automatic — Hymn selected based on liturgical calendar** | A hymn chosen from the current LitCal context, with an optional Liturgy of the Hours preference. | -Automatic mode uses the selected LitCal calendar, favoring the day’s feast, saint, category, and season while avoiding a suitable hymn already used that day. See the [automatic mode guide](doc/automatic-mode.md) for details on the hymn selection behavior. +Automatic mode uses the selected LitCal calendar, favoring the day’s feast, saint, category, and season while avoiding a suitable hymn already used that day. See the [automatic mode guide](docs/automatic-mode.md) for details on the hymn selection behavior. Use the built-in editor for simple, repetitive schedules with fixed times, days, and media players. When a schedule depends on other Home Assistant entities, use a regular automation and select a Virtual Carillon item from the Media browser. The engine renders and serves the audio and keeps it available in Home Assistant’s media library; Home Assistant chooses the media players and handles those additional conditions. -The [Home Assistant guide](doc/home-assistant.md#create-schedules) documents every schedule field, cadence, time-window rule, category, canonical-hour preference, and volume behavior in the GUI. +The [Home Assistant guide](docs/home-assistant.md#create-schedules) documents every schedule field, cadence, time-window rule, category, canonical-hour preference, and volume behavior in the GUI. ## A few good starting points @@ -148,11 +147,14 @@ Virtual Carillon does not set up speakers, Bluetooth pairing, or media-player in ## Further reading -- [Home Assistant setup, actions, and schedule details](doc/home-assistant.md) -- [Docker deployment](docs/docker.md) -- [Configuration reference](docs/configuration.md) -- [Adding your own recordings](doc/content.md) -- [Documentation index](doc/README.md) +The guides below expand on the relevant parts of this README: + +- [Home Assistant setup, actions, and schedule details](docs/home-assistant.md) +- [Automatic hymn selection](docs/automatic-mode.md) +- [Development, architecture, and testing](docs/development.md) +- [HTTP API reference](docs/api.md) +- [Contributing guide](CONTRIBUTING.md) +- [Security policy](SECURITY.md) ## License diff --git a/assets/virtual-carillon-icon.svg b/assets/virtual-carillon-icon.svg deleted file mode 100644 index 8a0b545..0000000 --- a/assets/virtual-carillon-icon.svg +++ /dev/null @@ -1 +0,0 @@ -Virtual CarillonA golden bell inside a deep blue roundel with sound waves. \ No newline at end of file diff --git a/assets/virtual-carillon-logo.svg b/assets/virtual-carillon-logo.svg deleted file mode 100644 index 7d62f10..0000000 --- a/assets/virtual-carillon-logo.svg +++ /dev/null @@ -1 +0,0 @@ -Virtual CarillonA golden bell inside a deep blue roundel beside the Virtual Carillon wordmark.Virtual Carillon \ No newline at end of file diff --git a/custom_components/virtual_carillon/README.md b/custom_components/virtual_carillon/README.md deleted file mode 100644 index 292c59f..0000000 --- a/custom_components/virtual_carillon/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Virtual Carillon integration - -This directory contains the Home Assistant integration only. For installation, follow the [main README](../../README.md), which explains the two Home Assistant engine paths, the optional [HACS](https://hacs.xyz/docs/use/) install, and the manual alternative. The integration needs a separately running Virtual Carillon engine: - -- use the Virtual Carillon Home Assistant app on Home Assistant OS or Home Assistant Supervised; or -- run the engine with Docker Compose for Home Assistant Container and other Docker installations. - -After the engine is running, add **Virtual Carillon** in **Settings → Devices & services**. The integration provides the media browser, `virtual_carillon.play`, `virtual_carillon.select_hymn`, and `virtual_carillon.stop` actions; a status sensor; and a saved schedule editor for Westminster, Manual, Category, and Automatic hymn routines. - -Home Assistant controls the selected speakers. The engine generates and serves the audio, so its container does not need direct access to ALSA, PipeWire, Bluetooth, or speaker hardware. - -See the [Home Assistant guide](../../doc/home-assistant.md) for media browsing, actions, exact settings, routine modes, and examples. diff --git a/custom_components/virtual_carillon/brand/icon.png b/custom_components/virtual_carillon/brand/icon.png deleted file mode 100644 index 969fcf2..0000000 Binary files a/custom_components/virtual_carillon/brand/icon.png and /dev/null differ diff --git a/custom_components/virtual_carillon/brand/icon@2x.png b/custom_components/virtual_carillon/brand/icon@2x.png deleted file mode 100644 index 3cc9c52..0000000 Binary files a/custom_components/virtual_carillon/brand/icon@2x.png and /dev/null differ diff --git a/custom_components/virtual_carillon/brand/logo.png b/custom_components/virtual_carillon/brand/logo.png deleted file mode 100644 index 87a56a6..0000000 Binary files a/custom_components/virtual_carillon/brand/logo.png and /dev/null differ diff --git a/custom_components/virtual_carillon/brand/logo@2x.png b/custom_components/virtual_carillon/brand/logo@2x.png deleted file mode 100644 index 3f06faf..0000000 Binary files a/custom_components/virtual_carillon/brand/logo@2x.png and /dev/null differ diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index cbaf83b..0000000 --- a/doc/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Documentation - -The [main README](../README.md) is the canonical guide for installation choices, prerequisites, routine modes, and everyday use. The pages below each cover one subject in more detail; they do not repeat the installation instructions. - -## Using Virtual Carillon - -| Guide | When to use it | -| --------------------------------------------------- | --------------------------------------------------------------------------- | -| [Home Assistant](home-assistant.md) | Media browser, actions, status, and schedule details after installation. | -| [Automatic mode](automatic-mode.md) | How LitCal-based hymn scoring and selection work. | -| [Docker deployment](../docs/docker.md) | Docker networking, persistent data, and troubleshooting after installation. | -| [Configuration reference](../docs/configuration.md) | Set environment variables, LitCal, and audio-distance defaults. | -| [Content and recordings](content.md) | Understand bundled material and add or remove local recordings. | -| [Operations and troubleshooting](operations.md) | Maintain a deployment and diagnose common problems. | - -## For developers and contributors - -| Guide | Contents | -| ---------------------------------- | ---------------------------------------------------------------- | -| [Development](development.md) | Requirements, local commands, checks, and release preparation. | -| [Testing](testing.md) | Automated checks and manual smoke tests. | -| [HTTP API](api.md) | Advanced integration and API reference. | -| [Architecture](architecture.md) | Runtime boundaries, persistence, scheduling, and selection flow. | -| [Contributing](../CONTRIBUTING.md) | Contribution expectations and pull-request guidance. | -| [Security](../SECURITY.md) | Vulnerability reporting and deployment boundary. | -| [Roadmap](roadmap.md) | Current project direction. | diff --git a/doc/architecture.md b/doc/architecture.md deleted file mode 100644 index 83c80f0..0000000 --- a/doc/architecture.md +++ /dev/null @@ -1,69 +0,0 @@ -# Architecture - -This page is for contributors who need to understand the project boundary. User setup belongs in the [main guide](../README.md) and [Home Assistant guide](home-assistant.md). - -## Runtime roles - -The Node engine owns bell synthesis, asset rendering, the HTTP API, persistent schedules, schedule claims, event history, user recordings, and LitCal cache files. Home Assistant is optional: the integration supplies forms, media browsing, and speaker delivery, but it does not render the audio. - -```text -Home Assistant integration ── schedule/settings ──► Node API + SQLite - │ │ - │◄────────── due Home Assistant actions ────────┤ - ▼ ▼ - Home Assistant media players native local outputs -``` - -The standard Home Assistant path is deliberately speaker-agnostic. The integration turns a rendered asset into a Home Assistant media-source URL and calls `media_player.play_media`; the engine does not pair, discover, or configure those speakers. - -## Main modules - -| Path | Responsibility | -| ------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| `src/cli/` | Command-line entry point, hymn-cycle commands, and automatic-selection preview. | -| `src/configuration/` | Environment settings and default data paths. | -| `src/api/` | Fastify HTTP API, authentication, direct playback, and schedule handoff. | -| `src/scheduling/` | Schedule validation, normalization, time windows, Westminster matching, and target rules. | -| `src/database/` | SQLite storage for schedules, claims, events, and completed-hymn history. | -| `src/audio/` and `src/bells/` | Bell synthesis, WAV rendering, distance profiles, local-output discovery, and native playback. | -| `src/library/` | Bundled assets, imported recordings, hymn catalog, metadata, rendering, and playback. | -| `src/liturgical/` | LitCal retrieval and caching, calendar normalization, taxonomy, conditions, and hymn-query conversion. | -| `custom_components/virtual_carillon/` | Home Assistant config flow, coordinator, schedule runner, media source, services, and status sensor. | -| `homeassistant/app/` | Supervisor app definition and entrypoint. | - -## Data directory - -The configured data directory contains the persistent state: - -| Path | Contents | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `carillon.sqlite` | Saved schedules, claimed runs, event history, and completed-hymn history. | -| `cache/` | Lazily rendered WAV files. Safe to regenerate. | -| `assets/` | Imported recordings and their `index.json` library manifest. | -| `litcal/` | Cached normalized LitCal calendar years. Stale cached data is preferred to blocking playback when the calendar service is unavailable. | - -Back up the database and `assets/` when preserving saved configuration and imported content matters. Rendered audio can be recreated. - -## Schedule delivery - -The public schedule format uses Westminster, assets, and hymn modes. The internal action graph supports the execution details and legacy migration, but new integrations should use `GET` and `PUT /api/schedule`. - -- A routine with `mediaPlayers` is claimed by the Home Assistant integration and delivered through `media_player` entities. -- A routine with native `outputs`, or with neither target list, is evaluated by the server’s native schedule timer. -- A routine that names both can use both delivery paths. This is intentional API-level behavior, but normal Home Assistant routines supply media players only. -- Schedule claims use the local date and minute plus the schedule revision, so the same Home Assistant event is not claimed twice after a restart or repeated tick. - -## Hymn selection - -The hymn catalog is independent of rendering. Hymn definitions declare metadata; date and calendar logic live in the LitCal and catalog layers. - -```text -date + calendar - │ - ▼ -LitCal day ──► celebration / season tags ──► hymn catalog ──► selected asset - │ - daily completed-hymn history -``` - -Automatic selection scores feast, saint, category, season, and optional canonical-hour preference. Explicit category, season, and feast queries use a tiered match instead. The full external contract is in the [API reference](api.md#litcal-and-hymn-selection). diff --git a/doc/content.md b/doc/content.md deleted file mode 100644 index 4880601..0000000 --- a/doc/content.md +++ /dev/null @@ -1,46 +0,0 @@ -# Content and recordings - -Virtual Carillon keeps its bundled material and your own recordings separate. - -## Bundled material - -- **Bells, Westminster Chimes, Angelus signals, and Office signals** are generated by the engine. No sampled church-bell recordings are included. -- **Hymns** are stored as symbolic melody and timing data, then arranged and rendered for the virtual carillon when needed. -- Built-in hymn metadata identifies relevant seasons, feasts, categories, Offices, or canonical-hour preferences. The selector uses those tags when choosing hymns. - -## Add a recording - -For an advanced standalone engine, use the command line to copy a local recording into the persistent library: - -```bash -node dist/cli/index.js import \ - --name "Custom Angelus" \ - --file /path/to/custom-angelus.wav \ - --id custom-angelus -``` - -`--name` and `--file` are required. `--id` is optional; when you omit it, Virtual Carillon derives a stable ID from the source file name. IDs may contain letters, numbers, hyphens, and underscores after normalization. - -The supported formats are WAV, FLAC, MP3, OGG, M4A, and AAC. The file is copied into the data directory, usually `.data/assets` for a local install or the `virtual-carillon-data` Docker volume for Compose. The original file is left untouched. - -The command-line importer creates a general recording. For an imported hymn with liturgical metadata, use `POST /api/assets/import`; see the [API reference](api.md#import-a-recording). - -Imported recordings appear in **All assets** in the Home Assistant media browser and can be selected in a Manual routine. Imported assets whose `type` is `hymn` also appear in the hymn browser and can participate in category, season, and automatic selection when their metadata supports it. - -## Rights and local customs - -Only import recordings, hymn performances, arrangements, or other material that you have permission to copy and use. Virtual Carillon does not grant permission to redistribute imported material. - -Bell customs vary by place. The bundled Angelus and Office signals are usable starting points, not claims that one pattern is universal. A local recording can be a fitting way to preserve a local practice. - -## Remove an imported recording - -Use the API to remove an imported asset: - -```bash -curl -X DELETE \ - -H 'Authorization: Bearer YOUR_TOKEN' \ - http://127.0.0.1:9876/api/assets/custom-angelus -``` - -This deletes the copied file from the engine’s data directory and removes it from the persistent library. It cannot remove bundled assets and does not delete the original source file you imported. diff --git a/doc/operations.md b/doc/operations.md deleted file mode 100644 index 18166f2..0000000 --- a/doc/operations.md +++ /dev/null @@ -1,77 +0,0 @@ -# Operations and troubleshooting - -Use this page after initial installation. The [main README](../README.md) is the only installation guide; this page covers maintenance and troubleshooting. - -## Routine maintenance - -For a Docker Compose deployment: - -```bash -docker compose ps -docker compose logs -f virtual-carillon -``` - -The `virtual-carillon-data` volume contains the saved schedule, playback history, imported recordings, LitCal cache, and rendered audio. Back it up before removing the volume or moving to a new host. - -For the Home Assistant app, the same information is held in Supervisor-managed app storage. Include the app data in ordinary Home Assistant backups. - -You may remove rendered WAV cache files while the engine is stopped; it will render them again when needed. Do not delete `carillon.sqlite`, the `assets/` directory, or SQLite `-wal` and `-shm` files while the engine is running unless you intentionally want to lose or reset that data. - -## Check the engine - -From the engine’s network: - -```bash -curl http://virtual-carillon:9876/health -curl -H 'Authorization: Bearer YOUR_TOKEN' http://virtual-carillon:9876/api/status -``` - -For a local native installation, replace `virtual-carillon` with `127.0.0.1` when the server uses its default address. - -`/health` confirms that the engine is running. `/api/status` also reports the current distance profile, discovered outputs, Bluetooth diagnostics, and recent playback events. - -## Common problems - -### The integration cannot connect - -Confirm all of the following: - -1. The engine is running and `/health` responds. -2. The API URL is reachable from Home Assistant. In the supplied Compose deployment, use `http://virtual-carillon:9876` only when both containers share a network. -3. The API token exactly matches the engine or app token. -4. The engine API is not being blocked by a reverse proxy or firewall. - -The default Compose file does not publish port `9876` to the Docker host. The host’s `localhost:9876` is therefore not an integration address unless you deliberately add a port mapping. - -### A schedule is saved but nothing plays - -Check the master **Enable schedules** switch in **Configure → Settings**, then check the individual **Enable Westminster** or **Enable schedule** switch. Confirm that the routine includes the current weekday, an exact `HH:MM` time, and at least one media player. - -For routines with **Never before** and **Never after**, remember that both boundaries are inclusive and overnight ranges continue into the next morning. Check the Home Assistant log for a claimed schedule event and the Virtual Carillon Status sensor’s recent events. - -### A speaker does not play an asset - -First play a normal Home Assistant media item on that speaker. Then confirm that it can reach Home Assistant’s configured internal or external URL and supports `media_player.play_media`. Virtual Carillon does not manage pairing, speaker groups, or transport details for another integration. - -When a routine specifies **Volume (%)**, Home Assistant sets the player’s volume before playback and leaves it there. An unexpectedly quiet or loud later playback may be the result of that setting. - -### Automatic mode cannot find a suitable hymn - -Check the selected **LitCal calendar** in **Configure → Settings** and the Home Assistant log for the exact selection message. The engine uses a cached calendar year when it has one. If it cannot obtain a LitCal day, the routine does not play and the Home Assistant log identifies the date and calendar involved. - -If no suitable hymn is available, the log identifies the routine and date. Check that the engine can reach LitCal and that the hymn library contains eligible hymns. - -### Native playback fails - -Native playback is an advanced standalone path, not the supported Home Assistant speaker path. Run: - -```bash -node dist/cli/index.js doctor -node dist/cli/index.js devices -``` - -Use the returned device information with the CLI or API if your setup requires it. Virtual Carillon does not provide configuration guidance for audio drivers, Bluetooth pairing, or particular speaker hardware. For ordinary use, send the audio through a working Home Assistant `media_player`. The standard Docker/Home Assistant deployment does not provide native audio devices to the container. - -### The database is locked - -Do not run two engine processes against the same data directory. The database uses WAL mode and a busy timeout, but simultaneous independent servers are not a supported arrangement. diff --git a/doc/roadmap.md b/doc/roadmap.md deleted file mode 100644 index e31710f..0000000 --- a/doc/roadmap.md +++ /dev/null @@ -1,9 +0,0 @@ -# Roadmap - -Virtual Carillon’s next work is guided by real-world behavior across its supported interfaces and deployments. The current priorities are: - -- verify the Home Assistant app across its supported architectures; -- broaden the hymn catalog and its careful liturgical metadata; and -- refine scheduling, deployment, and accessibility from reproducible user reports. - -Reliability, clear behavior, and respectful handling of liturgical material take precedence over adding features quickly. Please use the issue tracker for a reproducible bug report or a concrete feature request. diff --git a/doc/testing.md b/doc/testing.md deleted file mode 100644 index 94f4c21..0000000 --- a/doc/testing.md +++ /dev/null @@ -1,66 +0,0 @@ -# Testing - -## Automated checks - -Run the normal project checks while developing: - -```bash -pnpm check -``` - -Before review or release preparation, run the local CI preflight: - -```bash -pnpm ci:check -``` - -`pnpm ci:check` runs `pnpm check`, validates release metadata, compiles the Home Assistant integration, validates JSON metadata, builds the Docker image, and performs an authenticated API smoke test. It requires Docker and Python 3. - -The test suite covers bell synthesis and distance rendering, hymn notation and arrangements, LitCal normalization and caching, liturgical metadata and selection, imported recordings, schedule matching and claims, database history, and API validation. - -## Local command-line smoke test - -Build first, then run: - -```bash -node dist/cli/index.js doctor -node dist/cli/index.js assets -node dist/cli/index.js test -node dist/cli/index.js shuffle-hymns --count 1 -node dist/cli/index.js hymn-order --count 3 -``` - -`test` renders representative bells, signals, chant, and hymns. Confirm that WAV files appear under the configured data directory’s `cache/` folder. Native playback needs a working local audio backend and is not part of the Docker/Home Assistant smoke path. - -## API smoke test - -Start the server with an API token, then query it: - -```bash -VIRTUAL_CARILLON_API_TOKEN=test-token node dist/cli/index.js server -``` - -In another terminal: - -```bash -curl http://127.0.0.1:9876/health -curl -H 'Authorization: Bearer test-token' http://127.0.0.1:9876/api/status -curl -H 'Authorization: Bearer test-token' http://127.0.0.1:9876/api/assets -curl -H 'Authorization: Bearer test-token' http://127.0.0.1:9876/api/hymns -curl -H 'Authorization: Bearer test-token' \ - 'http://127.0.0.1:9876/api/liturgical/2026-08-15/hymn?calendar=general' -curl -H 'Authorization: Bearer test-token' \ - http://127.0.0.1:9876/api/assets/test-bell/audio -o /tmp/test-bell.wav -``` - -For native schedule testing, save an output-targeted schedule and call `POST /api/schedule/run` at a due time. See the [API reference](api.md#schedules) for the public schedule format. - -## Home Assistant smoke test - -1. Add the integration and confirm that the **Virtual Carillon Status** sensor is `online`. -2. In the media browser, play `test-bell` on one known-working media player. -3. Use **Configure** to turn on schedules and create a Manual test routine a few minutes ahead. -4. Confirm that it plays once, then test Westminster and an Automatic hymn routine. -5. Remove or disable the test routine afterward. - -This validates the intended path from engine to Home Assistant media player. Speaker-specific behavior belongs to the media-player integration being used. diff --git a/doc/api.md b/docs/api.md similarity index 100% rename from doc/api.md rename to docs/api.md diff --git a/doc/automatic-mode.md b/docs/automatic-mode.md similarity index 98% rename from doc/automatic-mode.md rename to docs/automatic-mode.md index a81925d..358cd0c 100644 --- a/doc/automatic-mode.md +++ b/docs/automatic-mode.md @@ -88,7 +88,7 @@ The API returns `scoring`, `selectedScore`, `selectedRank`, and `selectedScoreBr To preview the automatic order without playing audio: ```bash -node dist/cli/index.js hymn-order --date 2026-08-15 --calendar general --count 5 +node engine/dist/cli/index.js hymn-order --date 2026-08-15 --calendar general --count 5 ``` The HTTP API provides the same selection information: diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 97b330d..0000000 --- a/docs/configuration.md +++ /dev/null @@ -1,32 +0,0 @@ -# Configuration reference - -The engine reads its deployment settings from environment variables when it starts. Copy `.env.example` to `.env` for Docker Compose, or set the same variables in the environment that starts the server. - -Home Assistant schedules, speaker targets, and the schedule’s LitCal choice are configured in the integration’s **Configure** flow, not with environment variables. See [Home Assistant setup](../doc/home-assistant.md). - -| Variable | Default | Use | -| ----------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `VIRTUAL_CARILLON_DATA_DIR` | `.data` | Stores `carillon.sqlite`, rendered audio, imported recordings, and LitCal cache files. Back up this directory if you want to retain schedules, history, and imported recordings. | -| `VIRTUAL_CARILLON_HOST` | `127.0.0.1` | HTTP address on which the engine listens. Docker Compose uses `0.0.0.0` inside the container. | -| `VIRTUAL_CARILLON_PORT` | `9876` | HTTP port for `/health` and the authenticated `/api/*` endpoints. | -| `VIRTUAL_CARILLON_API_TOKEN` | unset | Bearer token for every `/api/*` endpoint. Set a long, unique value for every networked deployment. `/health` remains unauthenticated for health checks. | -| `VIRTUAL_CARILLON_DISTANCE_PROFILE` | `half-mile` | Default acoustic distance used by native playback and API audio requests that do not specify a profile. | -| `VIRTUAL_CARILLON_SAMPLE_RATE` | `44100` | WAV sample rate. The only accepted values are `44100` and `48000`. | -| `VIRTUAL_CARILLON_LITCAL_URL` | `https://litcal.johnromanodorazio.com/api/v5` | Base URL for the Liturgical Calendar API. Change this only when using a compatible LitCal endpoint. | -| `VIRTUAL_CARILLON_LITCAL_CALENDAR` | `general` | Default calendar used by the command line and API: `general`, `US`, `IT`, `NL`, `VA`, or `CA`. A Home Assistant schedule stores its own calendar choice. | - -## Distance profiles - -The following names are accepted by `VIRTUAL_CARILLON_DISTANCE_PROFILE`, the `play --distance` command option, and the API’s `distance` field. The profile changes the generated audio: farther settings are quieter, less bright, and have more distant reflections. - -| Profile | Intended character | -| ---------------- | ----------------------------------------------- | -| `near` | Close, clear bells. | -| `church-grounds` | Bells heard from nearby grounds. | -| `quarter-mile` | Bells heard from a short distance away. | -| `half-mile` | The default: a more distant outdoor sound. | -| `one-mile` | The quietest and most filtered bundled profile. | - -The HTTP `POST /api/play` endpoint also accepts `custom` with individual distance values. That is an API-only advanced option; the environment variable and Home Assistant integration accept the five bundled profiles only. - -Home Assistant’s schedule controls, LitCal calendar, playback distance, routine modes, Westminster cadence, volume handling, time windows, and canonical-hour preference are documented in the [Home Assistant guide](../doc/home-assistant.md). diff --git a/doc/development.md b/docs/development.md similarity index 66% rename from doc/development.md rename to docs/development.md index 9b8ffa3..f58a5cf 100644 --- a/doc/development.md +++ b/docs/development.md @@ -1,6 +1,6 @@ # Development -This guide is for contributors working on the Virtual Carillon engine, Home Assistant integration, Home Assistant app, documentation, and hymn catalog. +This guide is for contributors working on the Virtual Carillon engine, Home Assistant integration, Home Assistant app, documentation, and hymn catalog. It also owns the project architecture and test workflow. ## Requirements @@ -30,7 +30,7 @@ Keep runtime data in the ignored `.data/` directory. Do not use a production dat ## Local checks -The usual check is: +The engine tests live beside the engine in `engine/tests/`; the Home Assistant integration currently has syntax and metadata validation but no Python test suite. The usual check is: ```bash pnpm check @@ -62,19 +62,19 @@ Start the development server through `tsx`: pnpm dev ``` -The default address is `http://127.0.0.1:9876`. Set `VIRTUAL_CARILLON_API_TOKEN` when testing authenticated requests. Environment variables and their defaults are listed in the [configuration reference](../docs/configuration.md). +The default address is `http://127.0.0.1:9876`. Set `VIRTUAL_CARILLON_API_TOKEN` when testing authenticated requests. Docker defaults are available in `.env.example`. After a build, useful command-line checks include: ```bash pnpm build -node dist/cli/index.js assets -node dist/cli/index.js doctor -node dist/cli/index.js devices -node dist/cli/index.js test -node dist/cli/index.js hymn-order --count 3 -node dist/cli/index.js diagnose salve-regina -node dist/cli/index.js schedule show +node engine/dist/cli/index.js assets +node engine/dist/cli/index.js doctor +node engine/dist/cli/index.js devices +node engine/dist/cli/index.js test +node engine/dist/cli/index.js hymn-order --count 3 +node engine/dist/cli/index.js diagnose salve-regina +node engine/dist/cli/index.js schedule show ``` `hymn-order` previews automatic selection without playing audio. `test` renders representative bells, signals, chants, and hymns. Native playback requires a working local audio backend and is separate from the Home Assistant and Docker playback path. @@ -86,7 +86,7 @@ node dist/cli/index.js schedule show | Command or script | Purpose | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `pnpm dev` | Run the TypeScript server directly through `tsx`. | -| `pnpm build` | Clear the render cache and build `dist/`. | +| `pnpm build` | Clear the render cache and build `engine/dist/`, failing if the compiler fails. | | `pnpm test` | Run the automated tests once. | | `pnpm test:watch` | Re-run tests while developing. | | `pnpm typecheck` | Run TypeScript without emitting files. | @@ -100,7 +100,7 @@ node dist/cli/index.js schedule show ### `scripts/push-live.sh` -This script deploys the current working tree’s engine and Home Assistant integration to a remote Docker-based installation. It runs pnpm typecheck, archives the integration, builds the engine image on the remote host for its architecture, and force-recreates the engine through its existing Compose project. It then restarts Home Assistant and waits for the Home Assistant health check. The engine’s persistent data volume is preserved and not modified. This is useful for live-testing the integration, though a local Home Assistant instance may also be used. The first run may take several minutes because the image installs FFmpeg and its dependencies. +This script deploys the current working tree’s engine and Home Assistant integration to a remote Docker-based installation. It runs pnpm typecheck, archives `homeassistant/integration`, installs it into Home Assistant’s runtime `/config/custom_components/virtual_carillon` location, builds the engine image on the remote host for its architecture, and force-recreates the engine through its existing Compose project. It then restarts Home Assistant and waits for the Home Assistant health check. The engine’s persistent data volume is preserved and not modified. This is useful for live-testing the integration, though a local Home Assistant instance may also be used. The first run may take several minutes because the image installs FFmpeg and its dependencies. Create a local, ignored `dev.env` with at least: @@ -146,7 +146,7 @@ For a bundled hymn or melody: 4. Add or update tests for notation, arrangement, metadata, or selection behavior. 5. Confirm that the project has the right to distribute the melody, arrangement, and any included text or recording. -For imported user recordings, follow [Content and recordings](content.md). User recordings and generated audio belong in runtime storage, not in the source tree. +Imported user recordings and generated audio belong in runtime storage, not in the source tree. The API guide covers importing recordings and their metadata. ## Public contracts @@ -168,7 +168,7 @@ Keep the major boundaries intact: audio synthesis, the asset library, persistenc The project version is duplicated intentionally for the package, Home Assistant integration, Home Assistant app, and container metadata. These values must agree in: - `package.json` (`version`); -- `custom_components/virtual_carillon/manifest.json` (`version`); +- `homeassistant/integration/manifest.json` (`version`); - `homeassistant/app/config.yaml` (`version`); and - `Dockerfile` (`ARG BUILD_VERSION`). @@ -199,7 +199,41 @@ Do not commit: - `.env`, `dev.env`, credentials, API tokens, or private URLs; - `.data/`, SQLite databases, WAL files, or runtime caches; - imported or private recordings; -- `dist/`, `node_modules/`, Python bytecode, coverage output, or editor files; or +- `engine/dist/`, `node_modules/`, Python bytecode, coverage output, or editor files; or - generated WAV files. The repository’s ignore rules cover the usual cases, but check `git status` before committing. + +## Architecture + +The Node engine owns bell synthesis, asset rendering, the HTTP API, persistent schedules, schedule claims, event history, user recordings, and LitCal cache files. Home Assistant is optional: the integration supplies forms, media browsing, and speaker delivery, but it does not render audio. + +The standard Home Assistant path is speaker-agnostic. The integration turns a rendered asset into a Home Assistant media-source URL and calls `media_player.play_media`; the engine does not pair, discover, or configure speakers. + +| Path | Responsibility | +| --------------------------------------------------- | --------------------------------------------------------------------------- | +| `engine/src/cli/` | Command-line entry point and hymn-selection preview. | +| `engine/src/api/` | Fastify API, authentication, direct playback, and schedule handoff. | +| `engine/src/audio/` and `engine/src/bells/` | Bell synthesis, WAV rendering, distance profiles, and native playback. | +| `engine/src/library/` | Bundled assets, imported recordings, hymn catalog, metadata, and rendering. | +| `engine/src/liturgical/` | LitCal retrieval and caching, taxonomy, conditions, and hymn queries. | +| `engine/src/scheduling/` and `engine/src/database/` | Schedule validation, matching, claims, events, and completed-hymn history. | +| `homeassistant/integration/` | Config flow, schedule runner, media source, services, sensor, and branding. | +| `homeassistant/app/` | Home Assistant Supervisor app definition and entrypoint. | + +Schedules with Home Assistant `mediaPlayers` are claimed by the integration. Schedules with native `outputs`, or no target list, are evaluated by the engine’s native timer. Claims use the local date, minute, and schedule revision to avoid duplicate delivery after restarts. + +## Testing + +Run the normal checks with `pnpm check` and the complete local CI preflight with `pnpm ci:check`. The preflight also validates release metadata, compiles the Home Assistant integration, validates JSON, builds the Docker image, and performs an authenticated API smoke test. It requires Docker and Python 3. + +After a build, useful CLI checks are: + +```bash +node engine/dist/cli/index.js doctor +node engine/dist/cli/index.js assets +node engine/dist/cli/index.js test +node engine/dist/cli/index.js hymn-order --count 3 +``` + +`test` renders representative bells, signals, chant, and hymns under the configured data directory’s `cache/` folder. Native playback needs a local audio backend and is separate from the Docker/Home Assistant path. For a Home Assistant smoke test, add the integration, confirm the status sensor is `online`, play `test-bell` from the media browser, create a Manual test routine a few minutes ahead, then test Westminster and Automatic mode. Remove or disable the test routine afterward. diff --git a/docs/docker.md b/docs/docker.md deleted file mode 100644 index 76ec354..0000000 --- a/docs/docker.md +++ /dev/null @@ -1,50 +0,0 @@ -# Home Assistant Container with Docker - -This is installation path 2 from the [main guide](../README.md#2-home-assistant-container-with-docker). Use it when Home Assistant runs in a Docker container. It is not the Home Assistant OS or Supervised app installation. - -Virtual Carillon’s container renders and serves audio. Home Assistant’s integration sends that audio to the media players you select, so the engine container does not need access to ALSA, PipeWire, PulseAudio, Bluetooth, or the speaker hardware. - -## Installation - -Use the [main README](../README.md#2-home-assistant-container-with-docker) for the complete Docker installation: creating `.env`, starting the container, connecting Home Assistant to its network, installing the integration, and adding the device. This page is deliberately limited to Docker-specific storage and troubleshooting details. - -The supplied Compose file creates the `virtual-carillon_default` network and keeps port `9876` inside that network. It does not publish the API to the Docker host. The integration address is `http://virtual-carillon:9876` when Home Assistant is attached to that network. - -Do not expose the API to the public internet. If you need remote access, place it behind a properly configured reverse proxy and keep the bearer token secret. - -## Persistent data and backups - -The `virtual-carillon-data` Docker volume holds the engine’s persistent state: - -- `carillon.sqlite` — schedules, schedule claims, and playback history; -- rendered WAV files; -- LitCal cache files; and -- imported recordings and their library index. - -Back up the volume before removing or recreating it. You may replace the container image without losing this data as long as the volume remains in place. - -## Troubleshooting - -### Home Assistant cannot connect - -Check the following in order: - -1. The `virtual-carillon` container is running: `docker compose ps`. -2. Home Assistant and the engine share a Docker network. -3. The integration uses `http://virtual-carillon:9876`, not the Docker host’s `localhost:9876`. -4. The API token matches exactly. - -From a container on the shared network, these requests should succeed: - -```bash -curl http://virtual-carillon:9876/health -curl -H 'Authorization: Bearer YOUR_TOKEN' http://virtual-carillon:9876/api/status -``` - -### A media player does not play an asset - -The engine can be healthy while a particular speaker cannot retrieve the audio. Confirm that the speaker can reach Home Assistant’s configured internal or external URL and that its integration supports `media_player.play_media`. Pairing, network access, and speaker-specific playback remain the responsibility of that media-player integration. - -### I want direct audio from the container - -The standard Compose setup is deliberately Home Assistant–first and does not mount host audio devices. The CLI and `POST /api/play` support direct host-output playback in a separate native setup, but that is not part of this Docker configuration. diff --git a/doc/home-assistant.md b/docs/home-assistant.md similarity index 100% rename from doc/home-assistant.md rename to docs/home-assistant.md diff --git a/bin/virtual-carillon.mjs b/engine/bin/virtual-carillon.mjs similarity index 100% rename from bin/virtual-carillon.mjs rename to engine/bin/virtual-carillon.mjs diff --git a/src/api/server.ts b/engine/src/api/server.ts similarity index 100% rename from src/api/server.ts rename to engine/src/api/server.ts diff --git a/src/audio/analysis.ts b/engine/src/audio/analysis.ts similarity index 100% rename from src/audio/analysis.ts rename to engine/src/audio/analysis.ts diff --git a/src/audio/engine.ts b/engine/src/audio/engine.ts similarity index 100% rename from src/audio/engine.ts rename to engine/src/audio/engine.ts diff --git a/src/audio/outputs.ts b/engine/src/audio/outputs.ts similarity index 100% rename from src/audio/outputs.ts rename to engine/src/audio/outputs.ts diff --git a/src/audio/wav.ts b/engine/src/audio/wav.ts similarity index 100% rename from src/audio/wav.ts rename to engine/src/audio/wav.ts diff --git a/src/bells/instrument.ts b/engine/src/bells/instrument.ts similarity index 100% rename from src/bells/instrument.ts rename to engine/src/bells/instrument.ts diff --git a/src/bells/synth.ts b/engine/src/bells/synth.ts similarity index 100% rename from src/bells/synth.ts rename to engine/src/bells/synth.ts diff --git a/src/bells/types.ts b/engine/src/bells/types.ts similarity index 100% rename from src/bells/types.ts rename to engine/src/bells/types.ts diff --git a/src/cli/hymn-cycle.ts b/engine/src/cli/hymn-cycle.ts similarity index 100% rename from src/cli/hymn-cycle.ts rename to engine/src/cli/hymn-cycle.ts diff --git a/src/cli/hymn-order.ts b/engine/src/cli/hymn-order.ts similarity index 100% rename from src/cli/hymn-order.ts rename to engine/src/cli/hymn-order.ts diff --git a/src/cli/index.ts b/engine/src/cli/index.ts similarity index 100% rename from src/cli/index.ts rename to engine/src/cli/index.ts diff --git a/src/configuration/config.ts b/engine/src/configuration/config.ts similarity index 100% rename from src/configuration/config.ts rename to engine/src/configuration/config.ts diff --git a/src/database/db.ts b/engine/src/database/db.ts similarity index 100% rename from src/database/db.ts rename to engine/src/database/db.ts diff --git a/src/index.ts b/engine/src/index.ts similarity index 100% rename from src/index.ts rename to engine/src/index.ts diff --git a/src/library/catalog.ts b/engine/src/library/catalog.ts similarity index 100% rename from src/library/catalog.ts rename to engine/src/library/catalog.ts diff --git a/src/library/hymns.ts b/engine/src/library/hymns.ts similarity index 100% rename from src/library/hymns.ts rename to engine/src/library/hymns.ts diff --git a/src/library/hymns/defineHymn.ts b/engine/src/library/hymns/defineHymn.ts similarity index 100% rename from src/library/hymns/defineHymn.ts rename to engine/src/library/hymns/defineHymn.ts diff --git a/src/library/hymns/index.ts b/engine/src/library/hymns/index.ts similarity index 100% rename from src/library/hymns/index.ts rename to engine/src/library/hymns/index.ts diff --git a/src/library/hymns/music/abide-with-me.ts b/engine/src/library/hymns/music/abide-with-me.ts similarity index 100% rename from src/library/hymns/music/abide-with-me.ts rename to engine/src/library/hymns/music/abide-with-me.ts diff --git a/src/library/hymns/music/adoramus-te-christe.ts b/engine/src/library/hymns/music/adoramus-te-christe.ts similarity index 100% rename from src/library/hymns/music/adoramus-te-christe.ts rename to engine/src/library/hymns/music/adoramus-te-christe.ts diff --git a/src/library/hymns/music/adoro-te-devote.ts b/engine/src/library/hymns/music/adoro-te-devote.ts similarity index 100% rename from src/library/hymns/music/adoro-te-devote.ts rename to engine/src/library/hymns/music/adoro-te-devote.ts diff --git a/src/library/hymns/music/aeterna-christi-munera.ts b/engine/src/library/hymns/music/aeterna-christi-munera.ts similarity index 100% rename from src/library/hymns/music/aeterna-christi-munera.ts rename to engine/src/library/hymns/music/aeterna-christi-munera.ts diff --git a/src/library/hymns/music/alleluia-sing-to-jesus.ts b/engine/src/library/hymns/music/alleluia-sing-to-jesus.ts similarity index 100% rename from src/library/hymns/music/alleluia-sing-to-jesus.ts rename to engine/src/library/hymns/music/alleluia-sing-to-jesus.ts diff --git a/src/library/hymns/music/alma-redemptoris-mater-simple-tone.ts b/engine/src/library/hymns/music/alma-redemptoris-mater-simple-tone.ts similarity index 100% rename from src/library/hymns/music/alma-redemptoris-mater-simple-tone.ts rename to engine/src/library/hymns/music/alma-redemptoris-mater-simple-tone.ts diff --git a/src/library/hymns/music/alma-redemptoris-mater.ts b/engine/src/library/hymns/music/alma-redemptoris-mater.ts similarity index 100% rename from src/library/hymns/music/alma-redemptoris-mater.ts rename to engine/src/library/hymns/music/alma-redemptoris-mater.ts diff --git a/src/library/hymns/music/amazing-grace.ts b/engine/src/library/hymns/music/amazing-grace.ts similarity index 100% rename from src/library/hymns/music/amazing-grace.ts rename to engine/src/library/hymns/music/amazing-grace.ts diff --git a/src/library/hymns/music/angels-from-the-realms-of-glory.ts b/engine/src/library/hymns/music/angels-from-the-realms-of-glory.ts similarity index 100% rename from src/library/hymns/music/angels-from-the-realms-of-glory.ts rename to engine/src/library/hymns/music/angels-from-the-realms-of-glory.ts diff --git a/src/library/hymns/music/anima-christi.ts b/engine/src/library/hymns/music/anima-christi.ts similarity index 100% rename from src/library/hymns/music/anima-christi.ts rename to engine/src/library/hymns/music/anima-christi.ts diff --git a/src/library/hymns/music/as-with-gladness-men-of-old.ts b/engine/src/library/hymns/music/as-with-gladness-men-of-old.ts similarity index 100% rename from src/library/hymns/music/as-with-gladness-men-of-old.ts rename to engine/src/library/hymns/music/as-with-gladness-men-of-old.ts diff --git a/src/library/hymns/music/at-the-cross-her-station-keeping.ts b/engine/src/library/hymns/music/at-the-cross-her-station-keeping.ts similarity index 100% rename from src/library/hymns/music/at-the-cross-her-station-keeping.ts rename to engine/src/library/hymns/music/at-the-cross-her-station-keeping.ts diff --git a/src/library/hymns/music/ave-de-lourdes.ts b/engine/src/library/hymns/music/ave-de-lourdes.ts similarity index 100% rename from src/library/hymns/music/ave-de-lourdes.ts rename to engine/src/library/hymns/music/ave-de-lourdes.ts diff --git a/src/library/hymns/music/ave-maris-stella.ts b/engine/src/library/hymns/music/ave-maris-stella.ts similarity index 100% rename from src/library/hymns/music/ave-maris-stella.ts rename to engine/src/library/hymns/music/ave-maris-stella.ts diff --git a/src/library/hymns/music/ave-regina-caelorum-simple-tone.ts b/engine/src/library/hymns/music/ave-regina-caelorum-simple-tone.ts similarity index 100% rename from src/library/hymns/music/ave-regina-caelorum-simple-tone.ts rename to engine/src/library/hymns/music/ave-regina-caelorum-simple-tone.ts diff --git a/src/library/hymns/music/ave-regina-caelorum.ts b/engine/src/library/hymns/music/ave-regina-caelorum.ts similarity index 100% rename from src/library/hymns/music/ave-regina-caelorum.ts rename to engine/src/library/hymns/music/ave-regina-caelorum.ts diff --git a/src/library/hymns/music/away-in-a-manger.ts b/engine/src/library/hymns/music/away-in-a-manger.ts similarity index 100% rename from src/library/hymns/music/away-in-a-manger.ts rename to engine/src/library/hymns/music/away-in-a-manger.ts diff --git a/src/library/hymns/music/christ-is-made-the-sure-foundation.ts b/engine/src/library/hymns/music/christ-is-made-the-sure-foundation.ts similarity index 100% rename from src/library/hymns/music/christ-is-made-the-sure-foundation.ts rename to engine/src/library/hymns/music/christ-is-made-the-sure-foundation.ts diff --git a/src/library/hymns/music/christe-redemptor-omnium.ts b/engine/src/library/hymns/music/christe-redemptor-omnium.ts similarity index 100% rename from src/library/hymns/music/christe-redemptor-omnium.ts rename to engine/src/library/hymns/music/christe-redemptor-omnium.ts diff --git a/src/library/hymns/music/christus-vincit.ts b/engine/src/library/hymns/music/christus-vincit.ts similarity index 100% rename from src/library/hymns/music/christus-vincit.ts rename to engine/src/library/hymns/music/christus-vincit.ts diff --git a/src/library/hymns/music/come-holy-ghost-creator-blest.ts b/engine/src/library/hymns/music/come-holy-ghost-creator-blest.ts similarity index 100% rename from src/library/hymns/music/come-holy-ghost-creator-blest.ts rename to engine/src/library/hymns/music/come-holy-ghost-creator-blest.ts diff --git a/src/library/hymns/music/come-holy-spirit.ts b/engine/src/library/hymns/music/come-holy-spirit.ts similarity index 100% rename from src/library/hymns/music/come-holy-spirit.ts rename to engine/src/library/hymns/music/come-holy-spirit.ts diff --git a/src/library/hymns/music/come-thou-fount.ts b/engine/src/library/hymns/music/come-thou-fount.ts similarity index 100% rename from src/library/hymns/music/come-thou-fount.ts rename to engine/src/library/hymns/music/come-thou-fount.ts diff --git a/src/library/hymns/music/come-thou-long-expected-jesus.ts b/engine/src/library/hymns/music/come-thou-long-expected-jesus.ts similarity index 100% rename from src/library/hymns/music/come-thou-long-expected-jesus.ts rename to engine/src/library/hymns/music/come-thou-long-expected-jesus.ts diff --git a/src/library/hymns/music/creator-alme-siderum.ts b/engine/src/library/hymns/music/creator-alme-siderum.ts similarity index 100% rename from src/library/hymns/music/creator-alme-siderum.ts rename to engine/src/library/hymns/music/creator-alme-siderum.ts diff --git a/src/library/hymns/music/exsultet-caelum-laudibus.ts b/engine/src/library/hymns/music/exsultet-caelum-laudibus.ts similarity index 100% rename from src/library/hymns/music/exsultet-caelum-laudibus.ts rename to engine/src/library/hymns/music/exsultet-caelum-laudibus.ts diff --git a/src/library/hymns/music/exsultet-orbis-gaudiis.ts b/engine/src/library/hymns/music/exsultet-orbis-gaudiis.ts similarity index 100% rename from src/library/hymns/music/exsultet-orbis-gaudiis.ts rename to engine/src/library/hymns/music/exsultet-orbis-gaudiis.ts diff --git a/src/library/hymns/music/first-noel.ts b/engine/src/library/hymns/music/first-noel.ts similarity index 100% rename from src/library/hymns/music/first-noel.ts rename to engine/src/library/hymns/music/first-noel.ts diff --git a/src/library/hymns/music/for-all-the-saints.ts b/engine/src/library/hymns/music/for-all-the-saints.ts similarity index 100% rename from src/library/hymns/music/for-all-the-saints.ts rename to engine/src/library/hymns/music/for-all-the-saints.ts diff --git a/src/library/hymns/music/gabriels-message.ts b/engine/src/library/hymns/music/gabriels-message.ts similarity index 100% rename from src/library/hymns/music/gabriels-message.ts rename to engine/src/library/hymns/music/gabriels-message.ts diff --git a/src/library/hymns/music/gloria-in-excelsis-deo.ts b/engine/src/library/hymns/music/gloria-in-excelsis-deo.ts similarity index 100% rename from src/library/hymns/music/gloria-in-excelsis-deo.ts rename to engine/src/library/hymns/music/gloria-in-excelsis-deo.ts diff --git a/src/library/hymns/music/god-rest-ye-merry-gentlemen.ts b/engine/src/library/hymns/music/god-rest-ye-merry-gentlemen.ts similarity index 100% rename from src/library/hymns/music/god-rest-ye-merry-gentlemen.ts rename to engine/src/library/hymns/music/god-rest-ye-merry-gentlemen.ts diff --git a/src/library/hymns/music/hail-the-day-that-sees-him-rise.ts b/engine/src/library/hymns/music/hail-the-day-that-sees-him-rise.ts similarity index 100% rename from src/library/hymns/music/hail-the-day-that-sees-him-rise.ts rename to engine/src/library/hymns/music/hail-the-day-that-sees-him-rise.ts diff --git a/src/library/hymns/music/hark-the-herald-angels-sing.ts b/engine/src/library/hymns/music/hark-the-herald-angels-sing.ts similarity index 100% rename from src/library/hymns/music/hark-the-herald-angels-sing.ts rename to engine/src/library/hymns/music/hark-the-herald-angels-sing.ts diff --git a/src/library/hymns/music/holy-holy-holy.ts b/engine/src/library/hymns/music/holy-holy-holy.ts similarity index 100% rename from src/library/hymns/music/holy-holy-holy.ts rename to engine/src/library/hymns/music/holy-holy-holy.ts diff --git a/src/library/hymns/music/hyfrydol.ts b/engine/src/library/hymns/music/hyfrydol.ts similarity index 100% rename from src/library/hymns/music/hyfrydol.ts rename to engine/src/library/hymns/music/hyfrydol.ts diff --git a/src/library/hymns/music/hymn-to-joy.ts b/engine/src/library/hymns/music/hymn-to-joy.ts similarity index 100% rename from src/library/hymns/music/hymn-to-joy.ts rename to engine/src/library/hymns/music/hymn-to-joy.ts diff --git a/src/library/hymns/music/in-the-bleak-midwinter.ts b/engine/src/library/hymns/music/in-the-bleak-midwinter.ts similarity index 100% rename from src/library/hymns/music/in-the-bleak-midwinter.ts rename to engine/src/library/hymns/music/in-the-bleak-midwinter.ts diff --git a/src/library/hymns/music/jesus-christ-is-risen-today.ts b/engine/src/library/hymns/music/jesus-christ-is-risen-today.ts similarity index 100% rename from src/library/hymns/music/jesus-christ-is-risen-today.ts rename to engine/src/library/hymns/music/jesus-christ-is-risen-today.ts diff --git a/src/library/hymns/music/joy-to-the-world.ts b/engine/src/library/hymns/music/joy-to-the-world.ts similarity index 100% rename from src/library/hymns/music/joy-to-the-world.ts rename to engine/src/library/hymns/music/joy-to-the-world.ts diff --git a/src/library/hymns/music/o-come-all-ye-faithful.ts b/engine/src/library/hymns/music/o-come-all-ye-faithful.ts similarity index 100% rename from src/library/hymns/music/o-come-all-ye-faithful.ts rename to engine/src/library/hymns/music/o-come-all-ye-faithful.ts diff --git a/src/library/hymns/music/o-come-o-come-emmanuel.ts b/engine/src/library/hymns/music/o-come-o-come-emmanuel.ts similarity index 100% rename from src/library/hymns/music/o-come-o-come-emmanuel.ts rename to engine/src/library/hymns/music/o-come-o-come-emmanuel.ts diff --git a/src/library/hymns/music/o-fathers-of-our-ancient-faith.ts b/engine/src/library/hymns/music/o-fathers-of-our-ancient-faith.ts similarity index 100% rename from src/library/hymns/music/o-fathers-of-our-ancient-faith.ts rename to engine/src/library/hymns/music/o-fathers-of-our-ancient-faith.ts diff --git a/src/library/hymns/music/o-god-beyond-all-praising.ts b/engine/src/library/hymns/music/o-god-beyond-all-praising.ts similarity index 100% rename from src/library/hymns/music/o-god-beyond-all-praising.ts rename to engine/src/library/hymns/music/o-god-beyond-all-praising.ts diff --git a/src/library/hymns/music/o-sacred-head-now-wounded.ts b/engine/src/library/hymns/music/o-sacred-head-now-wounded.ts similarity index 100% rename from src/library/hymns/music/o-sacred-head-now-wounded.ts rename to engine/src/library/hymns/music/o-sacred-head-now-wounded.ts diff --git a/src/library/hymns/music/old-hundredth.ts b/engine/src/library/hymns/music/old-hundredth.ts similarity index 100% rename from src/library/hymns/music/old-hundredth.ts rename to engine/src/library/hymns/music/old-hundredth.ts diff --git a/src/library/hymns/music/once-in-royal-davids-city.ts b/engine/src/library/hymns/music/once-in-royal-davids-city.ts similarity index 100% rename from src/library/hymns/music/once-in-royal-davids-city.ts rename to engine/src/library/hymns/music/once-in-royal-davids-city.ts diff --git a/src/library/hymns/music/pange-lingua.ts b/engine/src/library/hymns/music/pange-lingua.ts similarity index 100% rename from src/library/hymns/music/pange-lingua.ts rename to engine/src/library/hymns/music/pange-lingua.ts diff --git a/src/library/hymns/music/psalm-51-miserere.ts b/engine/src/library/hymns/music/psalm-51-miserere.ts similarity index 100% rename from src/library/hymns/music/psalm-51-miserere.ts rename to engine/src/library/hymns/music/psalm-51-miserere.ts diff --git a/src/library/hymns/music/rector-potens-verax-deus.ts b/engine/src/library/hymns/music/rector-potens-verax-deus.ts similarity index 100% rename from src/library/hymns/music/rector-potens-verax-deus.ts rename to engine/src/library/hymns/music/rector-potens-verax-deus.ts diff --git a/src/library/hymns/music/regina-caeli-simple-tone.ts b/engine/src/library/hymns/music/regina-caeli-simple-tone.ts similarity index 100% rename from src/library/hymns/music/regina-caeli-simple-tone.ts rename to engine/src/library/hymns/music/regina-caeli-simple-tone.ts diff --git a/src/library/hymns/music/regina-caeli.ts b/engine/src/library/hymns/music/regina-caeli.ts similarity index 100% rename from src/library/hymns/music/regina-caeli.ts rename to engine/src/library/hymns/music/regina-caeli.ts diff --git a/src/library/hymns/music/salve-regina-simple-tone.ts b/engine/src/library/hymns/music/salve-regina-simple-tone.ts similarity index 100% rename from src/library/hymns/music/salve-regina-simple-tone.ts rename to engine/src/library/hymns/music/salve-regina-simple-tone.ts diff --git a/src/library/hymns/music/salve-regina.ts b/engine/src/library/hymns/music/salve-regina.ts similarity index 100% rename from src/library/hymns/music/salve-regina.ts rename to engine/src/library/hymns/music/salve-regina.ts diff --git a/src/library/hymns/music/sub-tuum-praesidium.ts b/engine/src/library/hymns/music/sub-tuum-praesidium.ts similarity index 100% rename from src/library/hymns/music/sub-tuum-praesidium.ts rename to engine/src/library/hymns/music/sub-tuum-praesidium.ts diff --git a/src/library/hymns/music/te-lucis-ante-terminum.ts b/engine/src/library/hymns/music/te-lucis-ante-terminum.ts similarity index 100% rename from src/library/hymns/music/te-lucis-ante-terminum.ts rename to engine/src/library/hymns/music/te-lucis-ante-terminum.ts diff --git a/src/library/hymns/music/veni-creator-spiritus.ts b/engine/src/library/hymns/music/veni-creator-spiritus.ts similarity index 100% rename from src/library/hymns/music/veni-creator-spiritus.ts rename to engine/src/library/hymns/music/veni-creator-spiritus.ts diff --git a/src/library/hymns/music/vexilla-regis.ts b/engine/src/library/hymns/music/vexilla-regis.ts similarity index 100% rename from src/library/hymns/music/vexilla-regis.ts rename to engine/src/library/hymns/music/vexilla-regis.ts diff --git a/src/library/hymns/music/victimae-paschali.ts b/engine/src/library/hymns/music/victimae-paschali.ts similarity index 100% rename from src/library/hymns/music/victimae-paschali.ts rename to engine/src/library/hymns/music/victimae-paschali.ts diff --git a/src/library/hymns/music/when-i-survey-the-wondrous-cross.ts b/engine/src/library/hymns/music/when-i-survey-the-wondrous-cross.ts similarity index 100% rename from src/library/hymns/music/when-i-survey-the-wondrous-cross.ts rename to engine/src/library/hymns/music/when-i-survey-the-wondrous-cross.ts diff --git a/src/library/hymns/music/while-shepherds-watched.ts b/engine/src/library/hymns/music/while-shepherds-watched.ts similarity index 100% rename from src/library/hymns/music/while-shepherds-watched.ts rename to engine/src/library/hymns/music/while-shepherds-watched.ts diff --git a/src/library/hymns/music/winchester-new.ts b/engine/src/library/hymns/music/winchester-new.ts similarity index 100% rename from src/library/hymns/music/winchester-new.ts rename to engine/src/library/hymns/music/winchester-new.ts diff --git a/src/library/hymns/notation/index.ts b/engine/src/library/hymns/notation/index.ts similarity index 100% rename from src/library/hymns/notation/index.ts rename to engine/src/library/hymns/notation/index.ts diff --git a/src/library/hymns/notation/toAbc.ts b/engine/src/library/hymns/notation/toAbc.ts similarity index 100% rename from src/library/hymns/notation/toAbc.ts rename to engine/src/library/hymns/notation/toAbc.ts diff --git a/src/library/hymns/notation/tunes/easter-hymn.ts b/engine/src/library/hymns/notation/tunes/easter-hymn.ts similarity index 100% rename from src/library/hymns/notation/tunes/easter-hymn.ts rename to engine/src/library/hymns/notation/tunes/easter-hymn.ts diff --git a/src/library/hymns/notation/tunes/hyfrydol.ts b/engine/src/library/hymns/notation/tunes/hyfrydol.ts similarity index 100% rename from src/library/hymns/notation/tunes/hyfrydol.ts rename to engine/src/library/hymns/notation/tunes/hyfrydol.ts diff --git a/src/library/hymns/notation/tunes/veni-creator.ts b/engine/src/library/hymns/notation/tunes/veni-creator.ts similarity index 100% rename from src/library/hymns/notation/tunes/veni-creator.ts rename to engine/src/library/hymns/notation/tunes/veni-creator.ts diff --git a/src/library/hymns/notation/types.ts b/engine/src/library/hymns/notation/types.ts similarity index 100% rename from src/library/hymns/notation/types.ts rename to engine/src/library/hymns/notation/types.ts diff --git a/src/library/hymns/types.ts b/engine/src/library/hymns/types.ts similarity index 100% rename from src/library/hymns/types.ts rename to engine/src/library/hymns/types.ts diff --git a/src/library/library.ts b/engine/src/library/library.ts similarity index 100% rename from src/library/library.ts rename to engine/src/library/library.ts diff --git a/src/liturgical/litcal.ts b/engine/src/liturgical/litcal.ts similarity index 100% rename from src/liturgical/litcal.ts rename to engine/src/liturgical/litcal.ts diff --git a/src/liturgical/resolver.ts b/engine/src/liturgical/resolver.ts similarity index 100% rename from src/liturgical/resolver.ts rename to engine/src/liturgical/resolver.ts diff --git a/src/liturgical/taxonomy.ts b/engine/src/liturgical/taxonomy.ts similarity index 100% rename from src/liturgical/taxonomy.ts rename to engine/src/liturgical/taxonomy.ts diff --git a/src/melodies/arranger.ts b/engine/src/melodies/arranger.ts similarity index 100% rename from src/melodies/arranger.ts rename to engine/src/melodies/arranger.ts diff --git a/src/melodies/parsers.ts b/engine/src/melodies/parsers.ts similarity index 100% rename from src/melodies/parsers.ts rename to engine/src/melodies/parsers.ts diff --git a/src/melodies/types.ts b/engine/src/melodies/types.ts similarity index 100% rename from src/melodies/types.ts rename to engine/src/melodies/types.ts diff --git a/src/scheduling/schedule.ts b/engine/src/scheduling/schedule.ts similarity index 100% rename from src/scheduling/schedule.ts rename to engine/src/scheduling/schedule.ts diff --git a/tests/api.test.ts b/engine/tests/api.test.ts similarity index 100% rename from tests/api.test.ts rename to engine/tests/api.test.ts diff --git a/tests/audio-overhaul.test.ts b/engine/tests/audio-overhaul.test.ts similarity index 100% rename from tests/audio-overhaul.test.ts rename to engine/tests/audio-overhaul.test.ts diff --git a/tests/catalog.test.ts b/engine/tests/catalog.test.ts similarity index 100% rename from tests/catalog.test.ts rename to engine/tests/catalog.test.ts diff --git a/tests/content.test.ts b/engine/tests/content.test.ts similarity index 100% rename from tests/content.test.ts rename to engine/tests/content.test.ts diff --git a/tests/database.test.ts b/engine/tests/database.test.ts similarity index 100% rename from tests/database.test.ts rename to engine/tests/database.test.ts diff --git a/tests/hymn-cycle.test.ts b/engine/tests/hymn-cycle.test.ts similarity index 100% rename from tests/hymn-cycle.test.ts rename to engine/tests/hymn-cycle.test.ts diff --git a/tests/liturgical.test.ts b/engine/tests/liturgical.test.ts similarity index 100% rename from tests/liturgical.test.ts rename to engine/tests/liturgical.test.ts diff --git a/tests/melodies.test.ts b/engine/tests/melodies.test.ts similarity index 100% rename from tests/melodies.test.ts rename to engine/tests/melodies.test.ts diff --git a/tests/notation.test.ts b/engine/tests/notation.test.ts similarity index 100% rename from tests/notation.test.ts rename to engine/tests/notation.test.ts diff --git a/tests/schedule-category.test.ts b/engine/tests/schedule-category.test.ts similarity index 100% rename from tests/schedule-category.test.ts rename to engine/tests/schedule-category.test.ts diff --git a/tests/synth.test.ts b/engine/tests/synth.test.ts similarity index 100% rename from tests/synth.test.ts rename to engine/tests/synth.test.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 674e83c..b47ce67 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,9 +2,9 @@ import tseslint from '@typescript-eslint/eslint-plugin'; import parser from '@typescript-eslint/parser'; export default [ - { ignores: ['dist/**', 'node_modules/**'] }, + { ignores: ['engine/dist/**', 'node_modules/**'] }, { - files: ['src/**/*.ts', 'tests/**/*.ts'], + files: ['engine/src/**/*.ts', 'engine/tests/**/*.ts'], languageOptions: { parser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module' } }, plugins: { '@typescript-eslint': tseslint }, rules: { diff --git a/homeassistant/app/DOCS.md b/homeassistant/app/DOCS.md index 16d452b..92a7165 100644 --- a/homeassistant/app/DOCS.md +++ b/homeassistant/app/DOCS.md @@ -12,4 +12,4 @@ The Home Assistant app runs the engine on port `9876`. The separate Virtual Cari The app exposes port `9876` to the Home Assistant host and internal app network. Use `http://:9876` when adding the integration; `http://local-virtual-carillon:9876` may also work for a local app installation. -The app is for Home Assistant OS and Home Assistant Supervised. For Home Assistant Container, run the engine with [Docker Compose](../../docs/docker.md) instead. +The app is for Home Assistant OS and Home Assistant Supervised. For Home Assistant Container, run the engine with [Docker Compose](../../README.md#2-home-assistant-container-with-docker) instead. diff --git a/homeassistant/app/README.md b/homeassistant/app/README.md deleted file mode 100644 index b549bde..0000000 --- a/homeassistant/app/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Virtual Carillon Home Assistant app - -This app runs the Virtual Carillon engine under Home Assistant Supervisor. Use it with Home Assistant OS or Home Assistant Supervised. It is not available for Home Assistant Container. - -The app runs the engine only. The separate **Virtual Carillon** integration supplies the media library, speaker selection, and schedules; follow the main README for its installation. - -## Setup - -Follow the [main README’s Home Assistant OS/Supervised path](../../README.md#1-home-assistant-os-and-home-assistant-supervised) for installation. It covers adding the app repository, installing the app, installing the integration with HACS (or manually), and connecting the two pieces. This page only documents the app’s own settings and storage. - -The app stores its database, rendered audio, LitCal cache, and imported recordings in Supervisor-managed persistent storage. Include that data in your Home Assistant backups. - -Home Assistant remains responsible for speaker configuration and playback. The integration sends the generated audio to the media players you select. - -For the app’s settings, see [DOCS.md](DOCS.md). For the complete integration guide, see [Home Assistant](../../doc/home-assistant.md). diff --git a/homeassistant/app/config.yaml b/homeassistant/app/config.yaml index 7aa6b12..347fa81 100644 --- a/homeassistant/app/config.yaml +++ b/homeassistant/app/config.yaml @@ -1,7 +1,7 @@ name: Virtual Carillon -version: 0.1.0-beta.1 +version: 0.1.0-beta.2 slug: virtual_carillon -description: Bells, traditional signals, and Catholic hymn arrangements for Home Assistant +description: Virtual Carillon turns your speakers into a programmable carillon. Play Westminster Chimes, the Angelus, and liturgically-appropriate Catholic hymns. url: https://github.com/HPaulson/virtual-carillon image: ghcr.io/hpaulson/virtual-carillon arch: diff --git a/homeassistant/app/entrypoint.mjs b/homeassistant/app/entrypoint.mjs index 0c0a2a8..772278f 100644 --- a/homeassistant/app/entrypoint.mjs +++ b/homeassistant/app/entrypoint.mjs @@ -35,7 +35,7 @@ for (const [option, variable] of Object.entries(optionEnv)) { } } -const child = spawn('node', ['dist/cli/index.js', 'server'], { +const child = spawn('node', ['engine/dist/cli/index.js', 'server'], { env, stdio: 'inherit', }); diff --git a/homeassistant/app/icon.png b/homeassistant/app/icon.png index 41308be..87d4c83 100644 Binary files a/homeassistant/app/icon.png and b/homeassistant/app/icon.png differ diff --git a/homeassistant/app/logo.png b/homeassistant/app/logo.png index 1f02435..ead41fb 100644 Binary files a/homeassistant/app/logo.png and b/homeassistant/app/logo.png differ diff --git a/custom_components/virtual_carillon/__init__.py b/homeassistant/integration/__init__.py similarity index 100% rename from custom_components/virtual_carillon/__init__.py rename to homeassistant/integration/__init__.py diff --git a/homeassistant/integration/brand/icon.png b/homeassistant/integration/brand/icon.png new file mode 100644 index 0000000..f856208 Binary files /dev/null and b/homeassistant/integration/brand/icon.png differ diff --git a/homeassistant/integration/brand/icon@2x.png b/homeassistant/integration/brand/icon@2x.png new file mode 100644 index 0000000..62d63dc Binary files /dev/null and b/homeassistant/integration/brand/icon@2x.png differ diff --git a/homeassistant/integration/brand/logo.png b/homeassistant/integration/brand/logo.png new file mode 100644 index 0000000..4c4d678 Binary files /dev/null and b/homeassistant/integration/brand/logo.png differ diff --git a/homeassistant/integration/brand/logo@2x.png b/homeassistant/integration/brand/logo@2x.png new file mode 100644 index 0000000..f1fccf1 Binary files /dev/null and b/homeassistant/integration/brand/logo@2x.png differ diff --git a/custom_components/virtual_carillon/config_flow.py b/homeassistant/integration/config_flow.py similarity index 100% rename from custom_components/virtual_carillon/config_flow.py rename to homeassistant/integration/config_flow.py diff --git a/custom_components/virtual_carillon/const.py b/homeassistant/integration/const.py similarity index 100% rename from custom_components/virtual_carillon/const.py rename to homeassistant/integration/const.py diff --git a/custom_components/virtual_carillon/coordinator.py b/homeassistant/integration/coordinator.py similarity index 100% rename from custom_components/virtual_carillon/coordinator.py rename to homeassistant/integration/coordinator.py diff --git a/custom_components/virtual_carillon/manifest.json b/homeassistant/integration/manifest.json similarity index 91% rename from custom_components/virtual_carillon/manifest.json rename to homeassistant/integration/manifest.json index 3045e47..c8215b4 100644 --- a/custom_components/virtual_carillon/manifest.json +++ b/homeassistant/integration/manifest.json @@ -1,7 +1,7 @@ { "domain": "virtual_carillon", "name": "Virtual Carillon", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "documentation": "https://github.com/HPaulson/virtual-carillon", "issue_tracker": "https://github.com/HPaulson/virtual-carillon/issues", "codeowners": [], diff --git a/custom_components/virtual_carillon/media_source.py b/homeassistant/integration/media_source.py similarity index 100% rename from custom_components/virtual_carillon/media_source.py rename to homeassistant/integration/media_source.py diff --git a/custom_components/virtual_carillon/schedule_runner.py b/homeassistant/integration/schedule_runner.py similarity index 100% rename from custom_components/virtual_carillon/schedule_runner.py rename to homeassistant/integration/schedule_runner.py diff --git a/custom_components/virtual_carillon/sensor.py b/homeassistant/integration/sensor.py similarity index 100% rename from custom_components/virtual_carillon/sensor.py rename to homeassistant/integration/sensor.py diff --git a/custom_components/virtual_carillon/services.yaml b/homeassistant/integration/services.yaml similarity index 100% rename from custom_components/virtual_carillon/services.yaml rename to homeassistant/integration/services.yaml diff --git a/custom_components/virtual_carillon/strings.json b/homeassistant/integration/strings.json similarity index 100% rename from custom_components/virtual_carillon/strings.json rename to homeassistant/integration/strings.json diff --git a/custom_components/virtual_carillon/translations/en.json b/homeassistant/integration/translations/en.json similarity index 100% rename from custom_components/virtual_carillon/translations/en.json rename to homeassistant/integration/translations/en.json diff --git a/package.json b/package.json index 8689828..d9bb234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "virtual-carillon", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "description": "A virtual carillon with Catholic bells, hymn arrangements, and Home Assistant integration.", "license": "MIT", "repository": { @@ -21,12 +21,12 @@ "type": "module", "packageManager": "pnpm@10.29.2", "bin": { - "virtual-carillon": "bin/virtual-carillon.mjs" + "virtual-carillon": "engine/bin/virtual-carillon.mjs" }, "files": [ - "dist", - "bin", - "custom_components", + "engine/dist", + "engine/bin", + "homeassistant", "README.md", "LICENSE" ], @@ -35,8 +35,8 @@ }, "scripts": { "build": "node scripts/build.mjs", - "dev": "tsx src/cli/index.ts server", - "start": "node dist/cli/index.js server", + "dev": "tsx engine/src/cli/index.ts server", + "start": "node engine/dist/cli/index.js server", "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit", diff --git a/repository.yaml b/repository.yaml index 5ccd78e..2580dfd 100644 --- a/repository.yaml +++ b/repository.yaml @@ -1,3 +1,3 @@ -name: Virtual Carillon Home Assistant apps +name: Virtual Carillon url: https://github.com/HPaulson/virtual-carillon maintainer: HPaulson diff --git a/scripts/build.mjs b/scripts/build.mjs index 893d1be..9f9c90c 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -1,4 +1,4 @@ -// Clear generated audio cache and compile the TypeScript application to dist/. +// Clear generated audio cache and compile the TypeScript engine to engine/dist/. import { rm } from 'node:fs/promises'; import path from 'node:path'; @@ -8,18 +8,13 @@ const projectRoot = process.cwd(); await rm(path.join(projectRoot, '.data', 'cache'), { recursive: true, force: true }); const tsc = process.platform === 'win32' ? 'tsc.cmd' : 'tsc'; -const compiler = spawn(tsc, ['-p', 'tsconfig.json'], { cwd: projectRoot, stdio: 'inherit' }); +await new Promise((resolve, reject) => { + const compiler = spawn(tsc, ['-p', 'tsconfig.json'], { cwd: projectRoot, stdio: 'inherit' }); -compiler.on('error', (error) => { - console.error(error); - process.exitCode = 1; -}); - -compiler.on('exit', (code, signal) => { - if (signal) { - console.error(`TypeScript compiler terminated with ${signal}`); - process.exitCode = 1; - } else if (code !== 0) { - process.exitCode = code ?? 1; - } + compiler.on('error', reject); + compiler.on('exit', (code, signal) => { + if (signal) reject(new Error(`TypeScript compiler terminated with ${signal}`)); + else if (code !== 0) reject(new Error(`TypeScript compiler exited with code ${code}`)); + else resolve(); + }); }); diff --git a/scripts/check-ci.mjs b/scripts/check-ci.mjs index d90d8e1..464667a 100644 --- a/scripts/check-ci.mjs +++ b/scripts/check-ci.mjs @@ -30,8 +30,8 @@ async function check(label, callback) { } function checkPythonMetadata() { - run('python3', ['-m', 'compileall', '-q', 'custom_components/virtual_carillon']); - run('python3', ['-m', 'json.tool', 'custom_components/virtual_carillon/manifest.json']); + run('python3', ['-m', 'compileall', '-q', 'homeassistant/integration']); + run('python3', ['-m', 'json.tool', 'homeassistant/integration/manifest.json']); run('python3', ['-m', 'json.tool', 'hacs.json']); } diff --git a/scripts/check-release.mjs b/scripts/check-release.mjs index 991f094..9710de0 100644 --- a/scripts/check-release.mjs +++ b/scripts/check-release.mjs @@ -13,9 +13,7 @@ if (packageJson.version !== expectedVersion) { failures.push(`package.json is ${packageJson.version}, expected ${expectedVersion}`); } -const manifest = JSON.parse( - await readFile('custom_components/virtual_carillon/manifest.json', 'utf8'), -); +const manifest = JSON.parse(await readFile('homeassistant/integration/manifest.json', 'utf8')); if (manifest.version !== expectedVersion) { failures.push(`Home Assistant manifest is ${manifest.version}, expected ${expectedVersion}`); } diff --git a/scripts/push-live.sh b/scripts/push-live.sh index 1329c36..982c922 100755 --- a/scripts/push-live.sh +++ b/scripts/push-live.sh @@ -41,14 +41,15 @@ COPYFILE_DISABLE=1 tar \ --no-fflags \ --exclude='._*' \ --exclude='*/._*' \ - --exclude='virtual_carillon/__pycache__' \ - --exclude='virtual_carillon/**/*.pyc' \ + --exclude='__pycache__' \ + --exclude='**/*.pyc' \ -czf "$LOCAL_ARCHIVE" \ - -C "$ROOT_DIR/custom_components" \ - virtual_carillon + -C "$ROOT_DIR/homeassistant/integration" \ + . scp "$LOCAL_ARCHIVE" "$REMOTE_HOST:$REMOTE_ARCHIVE" ssh "$REMOTE_HOST" "docker cp '$REMOTE_ARCHIVE' '$HA_CONTAINER:/tmp/virtual-carillon-ha.tar.gz'" -ssh "$REMOTE_HOST" "docker exec '$HA_CONTAINER' tar --extract --gzip --no-same-owner --no-same-permissions --file=/tmp/virtual-carillon-ha.tar.gz --directory=/config/custom_components" +ssh "$REMOTE_HOST" "docker exec '$HA_CONTAINER' mkdir -p /config/custom_components/virtual_carillon" +ssh "$REMOTE_HOST" "docker exec '$HA_CONTAINER' tar --extract --gzip --no-same-owner --no-same-permissions --file=/tmp/virtual-carillon-ha.tar.gz --directory=/config/custom_components/virtual_carillon" ssh "$REMOTE_HOST" "docker exec '$HA_CONTAINER' rm -f /tmp/virtual-carillon-ha.tar.gz" echo "==> Building the current engine source archive" @@ -66,7 +67,7 @@ COPYFILE_DISABLE=1 tar \ --exclude='*.sqlite-wal' \ -czf "$ENGINE_ARCHIVE" \ -C "$ROOT_DIR" \ - Dockerfile package.json pnpm-lock.yaml tsconfig.json eslint.config.mjs .prettierrc.json scripts src bin homeassistant README.md LICENSE + Dockerfile package.json pnpm-lock.yaml tsconfig.json eslint.config.mjs .prettierrc.json scripts engine homeassistant README.md LICENSE scp "$ENGINE_ARCHIVE" "$REMOTE_HOST:$REMOTE_ENGINE_ARCHIVE" echo "==> Rebuilding and restarting the Virtual Carillon engine" diff --git a/tsconfig.json b/tsconfig.json index f658c51..877235f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,8 @@ "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", - "outDir": "dist", - "rootDir": "src", + "outDir": "engine/dist", + "rootDir": "engine/src", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -12,6 +12,6 @@ "declaration": true, "sourceMap": true }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist"] + "include": ["engine/src/**/*.ts"], + "exclude": ["node_modules", "engine/dist"] }