Metadata-Version: 2.4
Name: c64cast
Version: 0.2.0
Summary: Turn a real Commodore 64 into a programmable display + audio device via Ultimate 64 or TeensyROM+ — video, SID-music scopes, webcam, MIDI/ASID synth, WLED, generative visuals, overlays, playlists.
Author-email: Kelly Fox <kfox@users.noreply.github.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/kfox/c64cast
Project-URL: Documentation, https://github.com/kfox/c64cast/tree/main/docs/guide
Project-URL: Releases, https://github.com/kfox/c64cast/releases/latest
Project-URL: Source, https://github.com/kfox/c64cast
Project-URL: Issues, https://github.com/kfox/c64cast/issues
Project-URL: Changelog, https://github.com/kfox/c64cast/blob/main/CHANGELOG.md
Project-URL: Videos, https://www.youtube.com/@c64cast
Keywords: 6502,c64,commodore-64,demoscene,petscii,retrocomputing,sid,streaming,teensyrom,ultimate-64,vic-ii,wled
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Environment :: MacOS X
Classifier: Environment :: Win32 (MS Windows)
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Artistic Software
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Video :: Display
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy<3,>=2.4.6
Requires-Dist: opencv-python<6,>=5.0.0.93
Requires-Dist: py65<2,>=1.2.0
Requires-Dist: requests<3,>=2.34.2
Provides-Extra: mic
Requires-Dist: sounddevice<0.6,>=0.5.5; extra == "mic"
Provides-Extra: video
Requires-Dist: av<19,>=18.0.0; extra == "video"
Provides-Extra: control
Requires-Dist: fastapi<1,>=0.140.0; extra == "control"
Requires-Dist: uvicorn<1,>=0.51.0; extra == "control"
Provides-Extra: obs
Requires-Dist: obsws-python<2,>=1.8.0; extra == "obs"
Provides-Extra: midi
Requires-Dist: mido<2,>=1.3.3; extra == "midi"
Requires-Dist: python-rtmidi<2,>=1.5.8; extra == "midi"
Provides-Extra: logging
Requires-Dist: rich<16,>=15.0.0; extra == "logging"
Provides-Extra: tr
Requires-Dist: pyserial<4,>=3.5; extra == "tr"
Provides-Extra: vision
Requires-Dist: mediapipe<0.11,>=0.10.35; extra == "vision"
Provides-Extra: wizard
Requires-Dist: questionary<3,>=2.1.1; extra == "wizard"
Provides-Extra: camera
Requires-Dist: cv2-enumerate-cameras<2,>=1.3.3; extra == "camera"
Provides-Extra: yt
Requires-Dist: yt-dlp>=2026.7.4; extra == "yt"
Provides-Extra: wled
Requires-Dist: zeroconf<1,>=0.150.0; extra == "wled"
Requires-Dist: fastapi<1,>=0.140.0; extra == "wled"
Requires-Dist: uvicorn<1,>=0.51.0; extra == "wled"
Requires-Dist: websockets<18,>=16.1.1; extra == "wled"
Provides-Extra: all
Requires-Dist: av<19,>=18.0.0; extra == "all"
Requires-Dist: cv2-enumerate-cameras<2,>=1.3.3; extra == "all"
Requires-Dist: fastapi<1,>=0.140.0; extra == "all"
Requires-Dist: mediapipe<0.11,>=0.10.35; extra == "all"
Requires-Dist: mido<2,>=1.3.3; extra == "all"
Requires-Dist: obsws-python<2,>=1.8.0; extra == "all"
Requires-Dist: pyserial<4,>=3.5; extra == "all"
Requires-Dist: python-rtmidi<2,>=1.5.8; extra == "all"
Requires-Dist: questionary<3,>=2.1.1; extra == "all"
Requires-Dist: rich<16,>=15.0.0; extra == "all"
Requires-Dist: sounddevice<0.6,>=0.5.5; extra == "all"
Requires-Dist: uvicorn<1,>=0.51.0; extra == "all"
Requires-Dist: websockets<18,>=16.1.1; extra == "all"
Requires-Dist: yt-dlp>=2026.7.4; extra == "all"
Requires-Dist: zeroconf<1,>=0.150.0; extra == "all"
Dynamic: license-file

<img width="800" height="271" alt="c64cast Logo" src="https://raw.githubusercontent.com/kfox/c64cast/main/assets/logo.png" />

# c64cast

[![CI](https://github.com/kfox/c64cast/actions/workflows/ci.yml/badge.svg)](https://github.com/kfox/c64cast/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/kfox/c64cast/branch/main/graph/badge.svg)](https://codecov.io/gh/kfox/c64cast)
[![PyPI](https://img.shields.io/pypi/v/c64cast.svg)](https://pypi.org/project/c64cast/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/kfox/c64cast/blob/main/LICENSE)

c64cast turns a real Commodore 64 — driven over the network through an
[Ultimate 64](https://ultimate64.com/) or
[TeensyROM+](https://lectronz.com/products/teensyrom) — into a programmable
display and audio device. It runs a **playlist of scenes** on the real
hardware: play videos and images, stream a live webcam, visualize SID music
on a 3-voice oscilloscope, synthesize a MIDI keyboard or an ASID stream
through the real SID chip, render reactive generative visuals, or hand the
machine over to a native game or demo. Frames from any source are quantized
in real time to a VIC-II display mode (PETSCII, MCM, hi-res bitmap, multicolor
hi-res); audio plays through the SID's `$D418` DAC or the hi-fi Ultimate Audio
PCM sampler. Stackable **overlays** decorate any scene with scrolling text,
spectrum analyzers, clocks, weather, RSS, logos, and more — and **ensemble
mode** drives a whole wall of C64s at once.

## Install

```bash
uv tool install 'c64cast[all]'          # or: pipx install 'c64cast[all]'
c64cast --config example:hello -u u64://192.168.2.64
```

That puts a `c64cast` command on your `PATH`. To try it without installing
anything permanently:

```bash
uvx --from 'c64cast[all]' c64cast clip.mp4 -u u64://192.168.2.64
```

`[all]` pulls in every optional feature — video files and YouTube URLs, mic
capture, MIDI, webcam gestures, the WLED bridge, the HTTP control plane, the
config wizard. Plain `uv tool install c64cast` gets a much smaller core install
(no mediapipe, no yt-dlp) that still covers every generative scene, PETSCII/
bitmap rendering, SID playback, and overlays; add extras à la carte later
(`uv tool install 'c64cast[video,midi]'`). Extras don't accumulate, so name
every one you want in a single command.

You need a reachable [Ultimate 64 or TeensyROM+](#hardware-needed) — there is
no emulator path for the streaming side. On the Ultimate, enable **Ultimate DMA
Service** under **F2 → Network Settings** first; c64cast prints an actionable
error if it can't connect.

## What do you want to do?

Every row below is a runnable, single-scene demo that ships inside the
package — pass it to `--config` and it loops forever until you Ctrl+C. Point
it at your hardware with `-u` (see [Quick start](#quick-start)).
The
[Programmer's Reference Guide](https://github.com/kfox/c64cast/tree/main/docs/reference)
documents every option for these scenes and overlays.

| I want to…                          | Try                                                     | Reference |
|-------------------------------------|---------------------------------------------------------|-----------|
| Play a video (or YouTube URL)       | `c64cast clip.mp4` · `example:scene-video`              | [Quick playback](https://github.com/kfox/c64cast/blob/main/docs/reference/26-appendix-g-cli-flags.md#quick-playback-with-media-args) |
| Show a live webcam as C64 art       | `example:scene-webcam-petscii`                          | [`webcam`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#webcam) |
| Visualize a SID tune (oscilloscope) | `example:scene-waveform`                                | [`waveform`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#waveform) |
| Play a SID from a MIDI keyboard     | `example:scene-midi`                                    | [`midi`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#midi) |
| Stream from DeepSID / SIDFactory II | `example:scene-asid`                                    | [`asid`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#asid) |
| Slideshow of images                 | `example:scene-slideshow`                               | [`slideshow`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#slideshow) |
| Generative / music-reactive visuals | `example:scene-generative-plasma`                       | [`generative`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#generative) |
| Run a native `.prg`/`.crt` game or demo | `example:scene-launcher`                            | [`launcher`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#launcher) |
| An info board (clock/weather/RSS)   | `example:overlay-clock`                                 | [Overlays](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#overlays) |
| Drive multiple C64s as one video wall | `example:ensemble/master`                             | [Ensemble mode](https://github.com/kfox/c64cast/blob/main/docs/reference/02-config-rules.md#the-ensemble-cascade) |
| Make the C64 a WLED LED matrix       | `example:scene-wled`                                   | [`wled`](https://github.com/kfox/c64cast/blob/main/docs/reference/03-vocabulary.md#wled) |
| Control c64cast from the WLED app    | `example:wled-control`                                 | [WLED bridge](https://github.com/kfox/c64cast/blob/main/docs/reference/07-inputs-and-outputs.md#wled) |

Run any of them with `c64cast --config example:<name>`, or list the whole set
with `c64cast --list-examples` (one demo per scene type and per overlay).

## Features

**Scenes** — a TOML playlist runs any mix of these on the real C64, each
for a set duration, with an "UP NEXT" interstitial between them:

* **Video** — MP4/MKV/etc. (and YouTube/other URLs via yt-dlp), soundtrack
  and all, keyed off the audio clock so A/V can't drift.
* **Webcam** — live capture quantized to any display mode in real time.
* **Slideshow** — still images from a directory/glob, aspect-fit.
* **SID waveform** — plays a `.sid` natively on the C64 (via a small
  player PRG, not the firmware's own runner) with a per-voice oscilloscope.
  Handles multi-SID tunes — up to 8 chips using the U64's UltiSIDs.
* **MIDI → SID** — bridge a live MIDI source (USB controller, DAW) into
  the real SID and visualize each voice (`midi` extra).
* **ASID client** — receive an ASID stream (DeepSID in a browser,
  SIDFactory II, Plogue chipsynth C64, …) and play it on the real SID with
  the same 3-voice scope (`midi` extra).
* **Generative** — ≈20 procedural sources (plasma, tunnel, fire,
  mandelbrot, metaballs, game of life, …), optionally music-reactive and
  with pixel effects (trails, pulse, RGB shift, blur).
* **Launcher** — hand the machine over to a native `.prg`/`.crt` game or
  demo, then reclaim it.
* **WLED matrix** — turn the C64 into a virtual LED matrix and stream live
  pixels to it from LedFx / xLights (DDP or WLED realtime UDP); part of the
  WLED bridge below.
* **Blank** — a solid PETSCII canvas for title cards + overlays.

**Display + audio** — six VIC-II display modes (`hires`, `hires_edges`,
`mhires`, `petscii`, `mcm`, `blank`), each with its own vectorized
quantizer (≈30 fps bitmap, 50/60 fps char over a LAN). Audio plays through
the SID's lo-fi `$D418` DAC (4-bit, or ≈6-7-bit via the Mahoney companding
technique) or, on the U64, the high-fidelity Ultimate Audio FPGA PCM sampler.

**Overlays** — stack on any compatible scene: scrolling text, marquee, RSS
ticker, PETSCII spectrum analyzer, clock, weather, callsign, countdown,
network info, multi-line logo, demo-scene big text, OBS Studio status.

**Ensemble mode** — one process drives **N systems at once** as a
video wall, with cross-system orchestration (e.g. a `big_text` message
scrolling across every screen as a single canvas).

**Control surfaces** — the C64 keyboard itself (C= pauses, CTRL skips,
SHIFT cycles the style), an on-C64 SPACE menu for live scene tweaks, webcam
hand gestures (`vision` extra), a FastAPI control plane (`/pause`,
`/resume`, `/skip`, `/reload`), MIDI CC control, and `SIGHUP` to reload the
config.

**WLED bridge** — interoperate with the [WLED](https://kno.wled.ge/) LED
ecosystem in three directions, all under one `[wled]` config section: drive
real LED matrices *from* the C64's SID with no microphone (audio-sync
broadcast), present c64cast *as* a virtual WLED device the WLED app / Home
Assistant can discover and control (effects ↔ scenes, sliders ↔ live params,
presets), and turn the C64 *into* a virtual LED matrix that LedFx / xLights
stream live pixels to. See
[WLED bridge](https://github.com/kfox/c64cast/blob/main/docs/reference/07-inputs-and-outputs.md#wled)
for the full reference.

**Quick playback** — skip the config file entirely and pass media straight
on the command line: `c64cast clip.mp4 tune.sid pics/` plays each in turn.

**Preview + recording** — an optional local window mirroring what the C64 is
showing, plus recording to MP4. Both are cv2-based, so neither needs an extra.

## Quick start

```bash
# "Hello world": scrolls big text across a solid canvas. Needs nothing but a
# reachable U64/TR+ — no webcam, mic, SID, or video files. Ctrl-C to exit.
c64cast --config example:hello -u u64://192.168.2.64

# Save the connection target so you never type -u again:
c64cast -u u64://192.168.2.64 --save-settings
c64cast --config example:hello
```

`-u/--url` is a scheme-aware target that picks the backend + endpoint:
`u64://HOST` or `http(s)://HOST` (Ultimate 64 / II+), `tr://` (TeensyROM+ over
auto-detected USB serial), `tr:///dev/cu.usbmodem*` / `tr://COM3` (a specific
serial device), or `tr://HOST` (TeensyROM+ over TCP). `$C64CAST_URL` is the env
fallback, and `--save-settings` persists it (plus the capture device and SID
model) to `~/.config/c64cast/settings.toml`.

`example:hello` is the gentlest starting point. From there:

```bash
# Try a single feature in isolation — one demo per scene type / overlay:
c64cast --config example:scene-webcam-petscii
c64cast --config example:overlay-clock

# Build your own config the easy way — the interactive wizard walks you
# through either a single scene or a multi-scene playlist (with the "UP NEXT"
# interstitial, video interleaving, and loop control) and writes a c64cast.toml
# (needs the 'wizard' extra, included in [all]; a ./c64cast.toml is picked up
# automatically when no --config is given):
c64cast --init

# ...or by hand: the `c64cast.example` demo is a fully-annotated reference
# exercising every scene + overlay. Copy it out and edit:
c64cast --print-example c64cast.example > c64cast.toml && $EDITOR c64cast.toml
c64cast

# Validate any config + check which optional extras are installed without
# touching the C64 (skip the connectivity probe to keep it offline):
c64cast --doctor --config c64cast.toml --skip-probe
```

The demos ship **inside the package**, so `example:NAME` works the same from an
installed wheel, from `uvx`, or from a git checkout.
`c64cast --list-examples` prints the whole set with one-line summaries;
`--print-example NAME` copies one out to edit. See
[`c64cast/examples/README.md`](https://github.com/kfox/c64cast/blob/main/c64cast/examples/README.md)
for the narrative version.

`c64cast -h` lists every CLI flag grouped by section (`connection`,
`quick playback`, `video input`, `audio`, `vision input`, `playlist`,
`introspection`, `debug`).

### Quick playback (no config file)

Pass media files/directories/globs/URLs as positional arguments to play them
once, in order, without writing a TOML (mutually exclusive with `--config`).
Audio is on by default; `--no-audio` mutes.

```bash
# A video, a SID tune, then a folder of pictures, on an Ultimate 64:
c64cast -u u64://192.168.1.64 clip.mp4 tune.sid ~/Pictures/

# A clip on a TeensyROM+ over auto-detected USB serial:
c64cast -u tr:// clip.mp4

# A YouTube URL (needs the 'yt' extra, included in [all]):
c64cast 'https://youtu.be/dQw4w9WgXcQ'
```

## Configuration

A config is a single TOML file (`--config PATH`, else `./c64cast.toml`,
else built-in defaults) that defines the playlist and every overridable
option. Three ways to author one, plus tooling to discover and validate it —
none of which needs the C64:

```bash
# Build one interactively (single scene or multi-scene playlist):
c64cast --init                    # needs the 'wizard' extra

# Discover the config surface straight from the code (always in sync):
c64cast --list-scenes             # scene types
c64cast --list-overlays           # overlays + their restrictions
c64cast --list-modes              # display modes
c64cast --list-examples           # the packaged demo configs
c64cast --describe overlay:clock  # full reference for one thing
c64cast --compat                  # overlay × display-mode matrix
c64cast --print-schema            # JSON Schema for editor autocomplete

# Validate a config (and check which extras are installed) without hardware:
c64cast --doctor --config c64cast.toml --skip-probe
```

The discovery output and the JSON schema are generated from the same field
metadata the loader runs on, so they can't drift from the code.
`c64cast --print-example c64cast.example` is the fully-annotated reference; see
[The Configuration Language](https://github.com/kfox/c64cast/blob/main/docs/reference/02-config-rules.md)
for the complete config walkthrough.

Machine-local defaults — connection target, capture device, SID model — live in
`~/.config/c64cast/settings.toml` and apply to every run, including quick
playback; write them with `--save-settings`. Persisted state (DAC calibrations,
WLED and loop presets) lives under `~/.local/share/c64cast/`.

## Live controls

While the stream is running, you control it from the C64 keyboard itself
(c64cast polls `$028D`, the kernal's keyboard-modifier scratch byte, at
10 Hz):

| Key on the C64                             | What it does                                                                                    |
|--------------------------------------------|-------------------------------------------------------------------------------------------------|
| **Commodore (C=)** — tap                   | Pause: scene + overlays tear down, screen clears, audio stops                                   |
| **Commodore (C=)** — hold 3 s while paused | Resume: re-sets-up the same scene (audio + polling threads all come back)                       |
| **CTRL** — tap while playing               | Skip: advance to the next interstitial after the current frame                                  |
| **SHIFT** — tap while playing              | Cycle the current scene's display style (palette mode / edge variant / waveform subtune / etc.) |

The C= + CTRL chord pressed on the same poll tick prefers **pause** —
skip is suppressed. SHIFT held alongside C= or CTRL is dropped so a
thumb resting on shift doesn't phantom-cycle the style. Cycled style
persists across single-scene loop iterations and across pause/resume,
but resets to the configured default on a real scene boundary
(multi-scene transitions construct fresh display_mode instances).

Same actions are exposed over HTTP when `[control] enabled = true`:

```bash
curl -X POST http://127.0.0.1:8765/pause
curl -X POST http://127.0.0.1:8765/resume
curl -X POST http://127.0.0.1:8765/skip
curl -X POST http://127.0.0.1:8765/reload   # re-read config from disk
```

Or, on macOS and Linux, send `SIGHUP` to the process to trigger a config
reload from the shell. Windows has no `SIGHUP`, so `POST /reload` above is
the portable route.

## Documentation

* [docs/guide/](https://github.com/kfox/c64cast/tree/main/docs/guide) —
  **the User's Guide**: a friendly, read-in-order introduction that starts
  from nothing and builds up. Start at
  [Quick Start](https://github.com/kfox/c64cast/blob/main/docs/guide/01-quick-start.md),
  or download the typeset PDF for your version from
  [the latest release](https://github.com/kfox/c64cast/releases/latest).
* [docs/reference/](https://github.com/kfox/c64cast/tree/main/docs/reference) —
  **the Programmer's Reference Guide**: the volume you open at the page you
  need. The rules of the configuration language, every scene and overlay, the
  display and sound paths in full, what lands in the Commodore's memory, and
  ten appendices, nine of them generated from the code. Typeset PDF on
  [the latest release](https://github.com/kfox/c64cast/releases/latest).
* [docs/card/](https://github.com/kfox/c64cast/tree/main/docs/card) —
  **the Performance Card**: two printable pages of controls, live targets and
  clip-grid syntax for the desk beside the controller
* [docs/caveats.md](https://github.com/kfox/c64cast/blob/main/docs/caveats.md) —
  known quirks (6502 emulator scope, char ROM substitution, U64 endpoint
  variance, licensing of SIDs / videos)
* [docs/troubleshooting.md](https://github.com/kfox/c64cast/blob/main/docs/troubleshooting.md) —
  symptom-first index for "I saw X, what now?"
* [docs/extending.md](https://github.com/kfox/c64cast/blob/main/docs/extending.md) —
  how to add a new Scene, Overlay, DisplayMode, or interstitial Background
* [docs/architecture.md](https://github.com/kfox/c64cast/blob/main/docs/architecture.md) —
  per-module internals: design rationale, hardware constraints, and edge-case
  history. Split by topic area under
  [docs/architecture/](https://github.com/kfox/c64cast/tree/main/docs/architecture);
  the index routes each module to its notes
* [CHANGELOG.md](https://github.com/kfox/c64cast/blob/main/CHANGELOG.md) —
  what changed in each release

## Hardware needed

One of the following:

* An [Ultimate 64](https://ultimate64.com/) — confirmed with Elite I, Elite II,
  Ultimate II+ cartridge, or Commodore 64 Ultimate. Best results will be
  obtained from using the Elite II or the Commodore 64 Ultimate.
  Under **F2 → Network Settings**, enable **Ultimate DMA Service**,
  **Command Interface** (TCP port 64 — the Command
  Interface toggle gates command dispatch even when the socket is open),
  and **Ultimate Audio** for streaming PCM audio.
  The REST API is used for the few operations that have no DMA equivalent.
* A [TeensyROM+ Multi-Capable Cartridge for C64/128](https://lectronz.com/products/teensyrom)
  plugged into an original Commodore 64 or one of the above modern
  "ultimate" equivalents.

Depending on how you use it, you'll also want some of these things:

* Any C64 video output path supported by a U64/C64.
* A webcam (any cv2-compatible USB device) for live capture scenes.
* A microphone for live audio; otherwise the audio path can sit
  idle or play a video's soundtrack via PyAV.
* A MIDI controller if you want to use MIDI scenes or control
  playlists/scenes via MIDI CC messages.
* An HDMI capture device if you want to capture output directly from a
  U64 or C64 equipped with a Kawari Large. Example capture devices include
  the Elgato Cam Link 4K or the Genki ShadowCast.
* A [WLED](https://kno.wled.ge/) device (or a WLED-ecosystem sender like
  LedFx/xLights) on the same LAN if you want to use the WLED bridge in
  any direction — none of this is required for the core streaming
  experience.

There is no software emulator path for the *streaming* side — c64cast
writes directly to U64 memory/registers over the Ultimate DMA Service
(TCP port 64), with REST used only for the few non-DMA operations. SID
playback is
driven by a small player PRG uploaded into C64 RAM so the real 6510
calls PLAY at IRQ time (the U64 firmware's `runners:sidplay` runner is
deliberately avoided because it hijacks the HDMI output with its own
UI); see
[docs/caveats.md](https://github.com/kfox/c64cast/blob/main/docs/caveats.md)
for the PSID-only limitation.

## Contributing

Bug reports, feature ideas, and pull requests are all welcome. See
[CONTRIBUTING.md](https://github.com/kfox/c64cast/blob/main/CONTRIBUTING.md)
for the development setup (a git checkout and `uv sync --all-extras`), the
`make check` gate, and the conventions this repo follows. Security reports go
through [SECURITY.md](https://github.com/kfox/c64cast/blob/main/SECURITY.md)
rather than a public issue.

## Acknowledgments

* [Gideon Zweijtzer](https://1541ultimate.net/) for the Ultimate 64
  hardware and firmware.
* Travis Smith for the [TeensyROM+](https://github.com/SensoriumEmbedded/TeensyROM) -
  including cartridge, firmware, hands-on testing, and suggestions.
* [Bo Zimmerman](http://zimmers.net) for his excellent online and physical
  collections of all things Commodore.
* The [HVSC](https://hvsc.c64.org/) team for the SID archive and the
  Songlengths database.
* Pex 'Mahoney' Tufvesson for the 8-bit `$D418` DAC technique (his
  ["Musings in the Key of C64" white paper](https://livet.se/mahoney/c64-files/Musings_in_the_key_of_C64_by_Pex_Mahoney_Tufvesson.pdf))
  behind the optional `dac_curve = "mahoney_ultisid"` audio path.
* Jürgen Wothke (webSID / Tiny'R'Sid) for
  [documenting the `$D418` filter-bit "almost 8-bit" playback approach](https://www.wothke.ch/tinyrsid/index.php/digi-samples)
  behind Mahoney's technique.
* Antonio Savona for the
  [48 kHz `$D418` write-up](https://brokenbytes.blogspot.com/2018/03/a-48khz-digital-music-player-for.html).
* [CodeBase64](https://codebase64.net/) for the extensive reference material.
* Many open source contributors for all of the _many_ Python packages
  that make this app possible. <3

## License

MIT — see [LICENSE](https://github.com/kfox/c64cast/blob/main/LICENSE).

**Third-party assets.** The User's Guide is typeset in two fonts that are
redistributed in this repository under the [SIL Open Font
License 1.1](https://openfontlicense.org/), not under MIT: **Jost\***
(Copyright 2020 The Jost Project Authors) and **Inconsolata** (Copyright 2006
The Inconsolata Project Authors). They live in
[`docs/shared/fonts/`](https://github.com/kfox/c64cast/tree/main/docs/shared/fonts)
alongside their license texts — see
[that directory's README](https://github.com/kfox/c64cast/blob/main/docs/shared/fonts/README.md)
for provenance and for what has to travel with them.
