8.7 KiB
RadiantWave — Codebase Architecture
What This Is
A kiosk application for "RadiantWave" wellness technology. It displays scrolling proclamation text rendered with custom shaders, plays audio at solfeggio frequencies, and manages WiFi/provisioning on a headless Linux kiosk.
Core libraries: SDL2, OpenGL 3.3, SQLite (GORM), D-Bus (NetworkManager). Build system: Nix flake. CI: Forgejo on a self-hosted runner.
Application Lifecycle
rhemawave.go → internal/application.Run()
Run() performs:
- SDL2 init (video, audio, events)
- Create window with
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN - OpenGL 3.3 context
- ShaderManager singleton init
- FontManager singleton init
- Mixer singleton init
- SQLite connect + GORM auto-migrate
- Sync proclamations from remote API
- Start audio download goroutine
- Enter SDL event loop
The event loop polls SDL events, calls Router.Update() + Router.Render(),
and runs checkState() to drive the provisioning state machine.
Provisioning State Machine
Defined in internal/application/application.go:
FIRST_RUN → NEEDS_INTERNET → NEEDS_LICENSE_KEY → NEEDS_EMAIL_ADDRESS → DOWNLOADING_ASSETS → READY
Each state maps to a page. checkState() evaluates DB values and network
status to decide which page to show.
Page System (internal/page/)
All pages implement the Page interface:
type Page interface {
Init(ctx *PageCtx)
HandleEvent(event sdl.Event) error
Update(dt float64)
Render()
Destroy()
}
PageCtx provides: *gorm.DB, screen dimensions, DownloadDone channel,
NavigateTo(pageType) callback.
Router (router.go) maps pageType enum → concrete page struct, manages
lifecycle (Destroy old → Init new).
| Page | Purpose |
|---|---|
Home |
Version info, download progress, license status, F2/F3 keybinds |
ScrollerPage |
Main screen: scrolling proclamation text with shaders, line patterns, color patterns, audio playback, velocity control |
SettingsPage |
Menu: audio, WiFi, proclamations, license, email, logs, volume |
AudioOptions |
Audio device selection (via PipeWire pactl), on/off, volume |
ProclamationOptions |
Browse/search/select proclamations from DB |
WiFiSetupPage |
WiFi scan, WPA connect, password entry |
LicenseKeyPage |
License key text input, saves to DB |
EmailAddressPage |
Email text input with regex validation |
LogViewerPage |
Scrollable log viewer from DB |
DownloadingAssets |
Stub during download phase |
Message |
Generic message page (stub) |
OpenGL Rendering
Virtual Framebuffer (internal/graphics/virtual_framebuffer.go)
Renders to a 1920x1080 FBO, then glBlitFramebuffer() to the native window
resolution. This decouples layout from display size.
Shaders (share/rhemawave/shaders/)
| Shader | Purpose |
|---|---|
scroller.vert/frag |
Text with font texture; uniform u_textColor for per-word coloring |
text.vert/frag |
Non-scrolling text elements |
solid_color.vert/frag |
Flat color (backgrounds, UI) |
Font System (internal/fontManager/)
FontManager (singleton) wraps SDL_ttf + OpenGL textures:
- Loads TTF/OTF fonts, creates GL texture atlas per font.
GetGlyph(fontName, rune)→ glyph metrics (advance, bearing, texture coords).RenderString(fontName, text, x, y, color, scale)→ renders to GL.- Scrambled font mode: Characters are spatially rearranged in the atlas, creating visual "frequency signatures" when rendered.
Fonts (share/rhemawave/fonts/)
| File | Use |
|---|---|
Phibonacci_regular.otf |
Decorative display font |
Roboto-Regular.ttf |
Clean sans-serif (UI) |
SpaceMonoNerdFont-Regular.ttf |
Monospace with icons |
Pattern System (internal/pattern/)
Line Patterns (internal/pattern/line/)
LinePattern interface: GetNextLine(words, wordIndex, ctx) []string.
Registered via init() (plugin-style Registry map).
| Pattern | Behavior |
|---|---|
| Fibonacci | Oscillating sequence (1,2,3,5,8,13,21) for words-per-line; creates wave in 2D |
| Sine Wave | Sine function → line width as % of screen; 5 cycles over 67 lines |
Color Patterns (internal/pattern/color/)
Distributes colors across words using Fibonacci-oscillating durations (1,1,2,3,5,8,13,21,34,55 words per color, then reverses).
Audio System
Mixer (internal/mixer/)
Singleton wrapping SDL_mixer:
- PipeWire sink enumeration via
pactl list sinks short - Volume persistence to DB
- Device switching with volume restore
Play(wavPath),Stop(),SwitchDevice(deviceName)
Audio Files
.wav files are gitignored. Downloaded at runtime from
files.radiantwavetech.com, verified against checksums.md5, stored in
~/.radiantwave/audio/. A goroutine handles async download; a channel
signals completion.
Solfeggio Frequencies
9 presets (174Hz–963Hz), each mapped to a "healing intention". Stored in DB, selectable in AudioOptions page.
Database (internal/db/)
SQLite at ~/.local/share/rhemawave/database.sqlite (WAL mode).
| Model | Purpose |
|---|---|
AppConfig |
Key-value store: license key, email, audio device, volume, download state |
Log |
Application logs (level, message, timestamp) |
Proclamation |
Synced proclamation text (title, content, category, intent colors) |
ProclamationCategory |
Category metadata |
SolfeggioFrequency |
9 frequency presets |
AudioFile |
Downloaded audio files (path, checksum, status) |
Network (internal/network/)
D-Bus integration via gonetworkmanager:
- Async WiFi scan (channel-based results)
- WPA/WPA2 connection support
- Captive portal detection
Remote APIs
| Endpoint | Purpose |
|---|---|
portal.radiantwavetech.com/api/v1/proclamations |
Sync proclamations |
files.radiantwavetech.com/files/ |
Audio file downloads |
| HTTP API | License validation |
Generic HTTP client in internal/netreq/netReq.go: typed GetJSON[T]() and
Download() with progress callback.
Assets (share/rhemawave/)
Copied into nix store at build time:
fonts/— 3 font files (see above)shaders/— 3 GLSL shader pairsproclamations/home/— 16 text files (healing, wellness, productivity)proclamations/commercial/— 1 text filetos/— 3 HTML files (Welcome, Notice, Getting Started)
C++ Rebuild Map
These are the components that need to be reimplemented in C++ with CMake/Ninja:
| Component | Go Package | C++ Equivalent | External Deps |
|---|---|---|---|
| Entry / Event Loop | rhemawave.go, application.go |
main.cpp |
SDL2 |
| Provisioning FSM | application.go |
State machine class | — |
| Page Router | page/router.go |
Router class | — |
| Page (11 types) | page/*.go |
11 page classes implementing Page interface | — |
| OpenGL Context | application.go |
GL context init | SDL2, OpenGL 3.3 |
| Virtual Framebuffer | graphics/virtual_framebuffer.go |
FBO + blit | OpenGL |
| Shader Manager | graphics/shaderManager.go |
Shader class | OpenGL |
| Font Manager | fontManager/fontManager.go |
Font atlas class | SDL_ttf, OpenGL |
| Line Patterns | pattern/line/*.go |
Pattern classes + registry | — |
| Color Patterns | pattern/color/*.go |
Color pattern class | — |
| Mixer | mixer/mixer.go |
Mixer class | SDL_mixer |
| Network / WiFi | network/network.go |
NetworkManager wrapper | D-Bus (sdbus-c++) |
| Database | db/*.go |
SQLite wrapper | SQLite3 (direct) or ORM |
| HTTP Client | netreq/netReq.go |
HTTP client | libcurl or cpp-httplib |
| Logging | logger/logger.go |
Logger class | — |
| Config / App State | db/models.go |
Config struct + DB | SQLite3 |
Build & Run
Dev shell via direnv (use flake in .envrc). Inside nix dev shell:
go build -o bin/rhemawave rhemawave.go # Build
./bin/rhemawave # Run
nix build -L # Nix build (CI)
CGO required (CGO_ENABLED=1). The nix shell sets CGO_CFLAGS/CGO_LDFLAGS
for SDL2. Do not go build outside the nix shell.
Tests
go test ./internal/mixer/
CI runs go test then nix build -L.
Branch Model
dev → main via PRs. CI in .forgejo/workflows/:
build-dev.yml: PRs todevbuild-prod.yml: PRs tomain
Both run nix build -L on self-hosted neils-server-runner.
Common Pitfalls
dev_scripts/reset.shreferenced in nix devShell hook does not exist.share/rhemawave/audio/*.wavis gitignored — never commit audio assets.vendor/is committed — rungo mod vendoraftergo mod tidy.- CI uses a self-hosted Forgejo runner, not GitHub-hosted runners.
packaging/contains a kiosk-specific updater, not part of the main build.