Project portfolio

Open-source projects by Henry Romp.

RompMusic

Libre and gratis music streaming — your music, your server, your freedom. RompMusic is a complete self-hosted system: libre (GPL-3.0) and gratis (no subscription). You own your music and data; no tracking, no proprietary services.

Why RompMusic?

  • Libre software — GPL-3.0: use, study, modify, share.
  • Gratis — No paywalls or proprietary lock-in.
  • Self-hosted — Library stays on your server; full privacy.
  • Cross-platform — Android, iOS, and web.
  • Gapless playback — Albums without gaps between tracks.
  • No tracking — Zero telemetry or analytics.

Components

rompmusic-serverStreaming, library management, REST API, JWT auth, HTTP range requests, admin panel, Mutagen metadata, optional beets integration.
rompmusic-clientExpo / React Native: Android, iOS, web; gapless playback, playlists, play history, paginated library; JWT login.
rompmusic-websiteDocumentation and marketing at rompmusic.com.

Server (quick start)

python -m venv venv && source venv/bin/activate
pip install -e .
cp .env.example .env
uvicorn rompmusic_server.main:app --reload

Notable env: MUSIC_PATH (library root), AUTO_SCAN_INTERVAL_HOURS, BEETS_AUTO_INTERVAL_HOURS. Dashboard scans use a background DB session so they continue if the tab closes.

Client (Android playback)

Background playback uses react-native-track-player: native service owns the queue; JS dispatches play/pause/seek; events sync back to the store. Patch included for Kotlin nullability with Expo SDK 54.

Docker (umbrella)

git clone --recursive https://github.com/151henry151/rompmusic.git
cd rompmusic
cp .env.example .env
# Edit DB_PASSWORD, JWT_SECRET, …
docker compose up -d

Docs: Install, Configuration, Self-hosting.


Dark Chess

Multiplayer web chess where each player’s board is partially hidden: you always see your own pieces, but you only discover the rest along paths your pieces have traveled. Standard chess rules; server is authoritative. Credits: Charlie Bishop conceived Dark Chess; this implementation was built in collaboration with him.

Features

  • Fog-of-war board; lobby; vs computer (bot uses same fog rules); challenges; random colors.
  • Fischer time controls (minutes+increment); server-side clocks; captured-piece counts.
  • Post-game review: fog lifts; replay full position history.
  • Full chess via chess.js: check, mate, stalemate, castling, en passant, promotion, draws, resign.
  • Disconnect handling; return to lobby.

How visibility works

The server keeps the true position and per-player path memory; each client gets a masked view. Opponent moves do not reveal the board for you.

  1. Your pieces — always visible.
  2. Your traveled squares — cumulative paths: pawns (each step); knights (chosen L-route, every square on the L); sliders (full ray); kings (start/end; castling paths); en passant capture square on path.
  3. Captures against you — capture square revealed afterward.
  4. Visible squares — empty reads empty; occupied shows that piece if visible.
  5. Probes — illegal pawn push into block, or fake diagonal capture to empty (non–en passant): lose turn without moving; destination stays explored.
  6. Sliders — enemy on ray: capture first blocking piece; (friendly-block behavior per engine rules).
  7. Into check — illegal destinations still highlight; attempt fails without passing turn; hypothetical attackers flash red.

Server sends dark-chess targets after piece select; every move is validated.

Architecture

ExpressStatic from public/.
Socket.IOLobby, challenges, rooms, moves.
chess.jsAuthoritative position on server.
server/dark.jsMasked boards and path visibility.
darkchess/
├── server/index.js    # HTTP + Socket.IO
├── server/dark.js     # Fog / masking
├── public/            # index.html, app.js, style.css
└── package.json

Run & deploy

Node 18+; ESM. npm install, npm starthttp://localhost:3333 (override PORT). State is in memory—one process or sticky sessions. Behind nginx, proxy /darkchess/ and /darkchess/socket.io/ (v1.3.1+ uses relative assets and socket path). GPL-3.0+.


Gödel, Escher, Bach companion

An interactive, chapter-by-chapter companion to Gödel, Escher, Bach: An Eternal Golden Braid by Douglas R. Hofstadter. You pick a chapter, choose how much help you want (ELI5 / ELI10 / ELI20 or full embedded PDF), read on the left, and use the chapter-specific interactive on the right (formal systems, puzzles, visual demos). Fan-made educational project; book text is Hofstadter’s (PDFs split per chapter).

What this is

  • Landing — Introduction + Chapters I–XX with short descriptions.
  • Per chapter — Reading level switcher: ELI5, ELI10, ELI20, and Full Text (embedded chapter PDF only).
  • Layout — Left: explanations or PDF iframe; right: unique interactive companion (MIU system, pq-system checker, figure–ground, etc.).
  • Design — Vanilla HTML/CSS/JS; shared/style.css, shared/nav.js; fonts EB Garamond + Inter.

Structure

  • index.html — chapter grid; intro/, chapter-01/chapter-20/.
  • pdfs/intro.pdf, chapter-01.pdf … (one scroll range per iframe).
  • scripts/split_geb_pdf.py — regenerate PDFs from full book (Python 3, pypdf).
  • PLAN.md — chapter plan and companion concepts.

Build: none—serve static files from /geb/. Each chapter embeds only its PDF segment so viewers cannot scroll into adjacent chapters.


Doodlr

Collaborative, zoomable pixel-art canvas with a 6-level hierarchical grid (729×729 pixels total). What you do: paint individual pixels with a chosen color; zoom in (levels 2–6) by moving through a 3×3 section grid so each zoom level shows a finer 9×9 tile of the parent—everyone sees the same world coordinates so edits stay aligned. Others’ strokes appear as you pan or refresh; the API persists the canvas in SQLite. Production: Expo web at /doodlr/app/, API proxied at /doodlr/api/.

Tech stack

Backend: FastAPI, Uvicorn, SQLAlchemy, SQLite. Frontend: Expo, React Native, React Native Web. E2E: Playwright.

API (excerpt)

  • GET /health, GET /colors
  • GET /level/{level} — canvas data; levels 2–6 need section_x, section_y
  • POST /paint{ x, y, color }
  • POST /zoom — validate { level, section_x, section_y }

Getting started

bash ./start-backend.sh    # http://localhost:8000
bash ./start-frontend.sh   # Expo; press w for web

Structure: backend/, frontend/, optional shared/. Version in VERSION; license GPL-3.0-or-later.


Listen

Android app that continuously records ambient audio in the background and maintains a configurable rolling buffer—review recent conversations or sounds. GPL-3.0; all storage local; no cloud upload for core recording.

Key features

  • Continuous background recording; configurable segment length (15s–30 min).
  • Auto Music Mode — silence-based splits targeting ~5 min segments.
  • Retention window; rotating buffer deletes oldest segments.
  • Quality presets: Low 16 kbps, Medium 32 kbps, High 128 kbps.
  • Export; WorkManager for rotation; Room DB for segment metadata.

How it works

ListenForegroundService orchestrates capture; MediaRecorder for encoding; segments as files; SegmentManagerService for rotation. Flow: record → split → prune when over retention.

Privacy

No network permission required for core recording; microphone permission explicit; optional encryption; one-tap delete.


Archive to Video

Turn archive.org audio collections into YouTube videos: paste a details URL, preview tracks, sign in with YouTube, then download, mux still artwork + audio (e.g. 1080p H.264 / AAC), upload, and create a playlist. GPL-3.0. Web UI at /archive-to-video/app/; CLI for headless use.

Features

  • URL input; preview; auto-generated videos; YouTube OAuth; playlist creation.
  • Metadata from archive pages (artist, venue, date, lineage).
  • Resume: skip downloads/uploads already done.

Flow

  1. Paste archive.org details URL.
  2. Preview tracks and durations.
  3. Authenticate with YouTube.
  4. Process: download audio → ffmpeg → upload → playlist.

Stack

FastAPI/uvicorn (systemd on hromp), ffmpeg, Python 3. CLI: python upload.py <archive.org URL> after install—see repo README.


Anthill

Godot 4.2+ (GDScript) grain-scale voxel simulation of an ant colony inspired by Lasius niger: recruitment pheromone trails, cuticular hydrocarbon footprints, alarm-like signaling, nest excavation in granular substrate, brood and workers—not a casual game but a research-oriented model with HUD and validation export.

Model contents

  • Substrate & nest — heightmap-style world; sand grains; gravity; nest galleries.
  • Pheromone & CHC fields — recruitment trail (deposit, diffusion, evaporation), footprint traffic field, nest cue, stress alarm.
  • Life cycle — queen founding, brood, nanitics/workers, trophallaxis, task allocation (forage, dig, care).
  • Workers — scout/recruit tropotaxis, zigzag sampling, food memory, trail marking.
  • Instrumentation — CSV logs, tracing; see docs/reference/technical_specification.txt.

Run

Prebuilt Windows EXE, Linux AppImage, and raw ELF on GitHub Releases (mirrored under hromp /downloads/). Or open game/anthill in Godot 4.2+.


invoice-gen

Flask app for small businesses and freelancers: manage companies and clients, add labor and itemized lines, upload logos, apply sales tax, and generate invoices (HTML preview + Excel download). Draft autosave syncs to the server and browser storage. Production on hromp: Docker + Gunicorn behind nginx at /invoice/ via SCRIPT_NAME=/invoice.

Structure

invoice_gen/app.py — main Flask app; templates/ — Jinja2 UI; static/ — CSS/JS and uploaded logos; requirements.txt; Alembic migrations under migrations/. Pretty invoice template: templates/invoice_pretty.html.

Run (dev / Docker)

python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
flask run

Production-style: docker-compose up --build (Gunicorn). Dev with hot reload: docker-compose -f docker-compose.dev.yml or ./switch-env.sh dev / prod. Key env: FLASK_ENV, SECRET_KEY, DATABASE_URL, SCRIPT_NAME=/invoice when mounted under a prefix.

Typical workflow

  1. Register / log in; create or select company and client records.
  2. Add line items and/or labor rows; adjust tax and notes; preview in the browser.
  3. Generate the invoice (pretty HTML template) and download Excel where configured.
  4. Drafts autosave locally and on the server so a refresh mid-edit does not wipe work.

Extras

  • Optional Google Places address autocomplete — API key in credentials.ini (see README).
  • REST-style JSON endpoints for clients, companies, invoice number checks, sales tax CRUD, session selections.
  • Test harness script populates sample businesses, clients, and invoices for manual QA.

Weather dashboard

Real-time GOES-19 satellite animations, NWS current conditions and 7-day forecast, forecast maps, and severe weather alerts—vanilla HTML/CSS/JS (no framework), dark theme, grid layout. MIT.

Satellite bands

CONUS GeoColor, snow/ice, day/night cloud combo, full-disk GeoColor, air mass RGB, sandwich RGB, ozone, water vapor (lower/mid/upper), dust RGB, and more—24-frame loops with hover-to-pause and click-through to NOAA.

Location & alerts

ZIP-based location with coordinate lookup (Zippopotam.us); dismissible, color-coded alerts. Temperature and precipitation forecast map links update with the selected location.

Technical

Custom frame-based animation; fetch() to NWS and STAR endpoints; CSS Grid/Flexbox. Files: index.html, styles.css, links.txt. Serve with any static host (python3 -m http.server for local dev).

Using the dashboard

Open the page to see current conditions, 7-day forecast, and the satellite grid; use Change next to the ZIP to reload everything for a new location. Hover satellite tiles to pause loops; click through to NOAA for full products. Dismiss individual alerts with the × control.


Word roots

Browser tools around Donald J. Borror’s Dictionary of Word Roots and Combining Forms (1960): searchable index extracted from the bundled PDF, a species-name / compound builder from plain English, and an in-app link to the full PDF. Vite + React + TypeScript; runtime loads only dictionary.json—no API. GPL-3.0.

Features

  • Search + A–Z browse — substring search (up to 200 hits) or letter row in book column order; optional linter scripts flag OCR ordering quirks.
  • Name builder — drop stopwords, match glosses, chain stems with Greek/Latin linking rules; outputs draft epithet—still needs real taxonomy checks.
  • Entry cards — expand (G)/(L) tags; collapsed introduction panel with Borror’s preface.

Build & data

cd web && npm install && npm run dev
# Production: VITE_BASE=/word_roots/ for https://hromp.com/word_roots/
python3 scripts/extract_dictionary.py   # regenerate dictionary.json from PDF

Requires pdftotext (Poppler) to regenerate JSON from the PDF.


Eternal Weave

Canvas puzzle game in a “block universe” framing: you shape one 4D worldtube—3D space (x,y,z) across discrete time slices. The UI tracks level, score, chronons, lockline, anchors, and toggles A-series / B-series observation modes plus frame tilt and orbit camera. Static game.js + styles.css under /time/.

Gameplay loop

Shape future slices before the lockline passes them; follow anchor schedule and resolve entries on the paradox ledger. Lore text per level explains the temporal metaphor (“every slice exists at once,” observation frontier freezing the past).

Controls (excerpt)

  • F/R or arrows — move selected time slice; WASD / QE — move y / x / z at that slice.
  • Z/X — frame tilt; Space — A/B view; Shift — camera preset; mouse drag orbit, wheel zoom; Backspace reset; Enter advance after stabilization.
  • Mobile: on-screen slice and move grids.

SpeedRead

RSVP (rapid serial visual presentation) shows one word at a time at the center of the screen so you minimize eye movement and control pace with a single fixation—useful for speed-reading drills and plowing through long documents. Paste text or upload PDF, TXT, MD, HTML, XML, JSON, CSV. pdf.js (bundled worker) extracts PDF text in the browser—no document upload to a backend. Adjustable WPM, font size, optional center-letter highlight, play/pause and word skip. DM Sans + JetBrains Mono.

Architecture

Vanilla JS modules: timer, tokenizer, display; CSS variables for typography. Keyboard: Space play/pause, arrows for prev/next word. Privacy: processing stays in the tab; hromp only serves static assets.


Day / night map

Real-time day/night terminator on a world map: D3 v7 + TopoJSON v3, 110m country data, current time display, and short educational text on axial tilt and seasons. Dependencies load from CDN—open index.html locally or visit /mercator/. MIT.

Files

index.html, style.css, map.js, data/countries-110m.json. The map redraws the terminator continuously for the viewer’s “now.”

What you see

The world map fills with night shading on the side of Earth opposite the Sun; the curved terminator sweeps as time advances. Clock and date update with the visualization; scroll the page for short notes on Earth’s tilt and seasons.


Solar System calculator

Single-file interactive tool: enter a Sun diameter and get all planet diameters and orbital distances to the same scale, plus a pairwise distance calculator between solar system bodies. No npm—one index.html, MIT.

Using the calculator

  1. Enter a Sun diameter (any unit—outputs scale to match).
  2. Read scaled planet diameters and distances from the Sun.
  3. Use the second control to measure distance between any two bodies in the same model.

Deploy

Serve the file from any static root; README includes an example nginx location block for a subpath.


Travel time calculator

Trip duration from a trapezoidal velocity profile: total distance, max speed, and separate acceleration and deceleration durations. Supports metric (km, km/h) and imperial (mi, mph). Static index.html; no dependencies.

Model

Accel phase — user-specified ramp time. Cruise — remaining distance at max speed. Decel phase — user-specified ramp down. Total time is the sum—useful for rough ETA when acceleration matters (not constant-speed-only).

Inputs

  1. Total distance (miles or km).
  2. Maximum speed (mph or km/h).
  3. Time to accelerate from 0 to that speed; time to decelerate back to 0.
  4. Submit to see total trip duration across all three phases.

Birthday in π

Pick a calendar date and search for MMDDYYYY as a digit string in the first ten million decimal digits of π. Digits load in chunks to keep memory low; search runs entirely in the browser. Includes short educational copy on irrational/transcendental π and normality.

Data & credits

Digit file from calculat.io. Conceptually inspired by the classic Am I in Pi? toy.

Using the app

Choose month, day, and year; submit to search the digit stream for that eight-digit MMDDYYYY pattern. The UI loads π in chunks and reports the first offset if found (or that it does not occur in the bundled range).


Rubik’s cube

Single-file Three.js (r128) + OrbitControls 3D cube; cubejs Kociemba two-phase solver vendored under lib/ with a same-origin worker so Firefox can run the solver. Face turns via keyboard (US, Shift for prime) or drag on a face; scramble, show scramble, animated solve, reset. Single static index.html workflow; live at /charlie/.

View options

HUD Top menu picks which WCA face is “up” (persisted in localStorage); camera distance rotates with the choice so the perspective stays corner-like. Notation and solver stay standard (U = white on +Y in the model).


On Denoting

Interactive companion to Bertrand Russell’s 1905 Mind paper “On Denoting.” Left pane: full article typeset for long reading (Libre Baskerville) with a reading level switcher—Full article vs ELI5 / ELI10 / ELI20 summaries that walk through definite descriptions, Meinong/Frege objections, and the three puzzles (Waverley, king of France, difference between A and B). Right pane: companion iframe (split layout collapses to stack on narrow screens).

Purpose

Makes Russell’s analysis of denoting phrases—existence/uniqueness conditions, primary vs secondary occurrence, knowledge by description—readable without a PDF reader, while optional ELI layers scaffold the logic for students.


IPA reader

Understanding the IPA — read how to interpret the standard consonant and vowel charts (place, manner, voicing; height, backness, rounding); open the static chart image or jump to UCLA’s interactive chart with per-symbol audio (ucla_ipa.html); watch the Ken Stevens x-ray articulation clip; read a long companion piece on English spelling vs phonology. Optionally type a word or phrase into the on-page field and use the Flask API (proxied at /ipa/api/) to retrieve an IPA rendering. Submodule ipa-chart/ → served at /ipa/.

Credit

Teaching copy credited to Amy Reynolds (UNC-Chapel Hill); English spelling article credited to Sarada Mauck (Iowa State) in-page.


Too Hot

Climate awareness campaign at its2hot.org: compare today’s (forecast) temperature to a ~30-year historical average for the same calendar day and place. When the forecast is hotter than normal by a set margin (e.g. 10 °F in production), the system flags a heat anomaly and can email or push-notify subscribers. The project promotes wearing an “IT’S TOO HOT!” T-shirt on those days to start conversations; the site includes list signup, shop/checkout (PayPal + Printful), and an Expo mobile app for alerts and purchases. GPL-3.0; Flask backend on Cloud Run with scheduled temperature checks and an admin dashboard.

What you get as a user

  • Alerts — Opt-in email and/or app push when a scheduled check finds the threshold exceeded.
  • Context — Framing is data-driven (current vs long-term norm), not a single thermometer reading in isolation.
  • Action — Campaign copy and merch tie the anomaly to visible solidarity (shirt) and community list (optional location).

Implementation notes

Backend: Flask API (subscribe/unsubscribe, device tokens, /api/check-temperatures, logs). Mobile: React Native/Expo + Firebase push. Ops: Cloud Scheduler triggers checks; threshold and frequency configurable (stricter in dev). hromp’s /too-hot/ page summarizes the project for the main site; live app is on its2hot.org.