262 lines
8.7 KiB
Markdown
262 lines
8.7 KiB
Markdown
# 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:
|
||
1. SDL2 init (video, audio, events)
|
||
2. Create window with `SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN`
|
||
3. OpenGL 3.3 context
|
||
4. ShaderManager singleton init
|
||
5. FontManager singleton init
|
||
6. Mixer singleton init
|
||
7. SQLite connect + GORM auto-migrate
|
||
8. Sync proclamations from remote API
|
||
9. Start audio download goroutine
|
||
10. 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:
|
||
|
||
```go
|
||
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 pairs
|
||
- `proclamations/home/` — 16 text files (healing, wellness, productivity)
|
||
- `proclamations/commercial/` — 1 text file
|
||
- `tos/` — 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:
|
||
|
||
```sh
|
||
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
|
||
|
||
```sh
|
||
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 to `dev`
|
||
- `build-prod.yml`: PRs to `main`
|
||
|
||
Both run `nix build -L` on self-hosted `neils-server-runner`.
|
||
|
||
## Common Pitfalls
|
||
|
||
- `dev_scripts/reset.sh` referenced in nix devShell hook does not exist.
|
||
- `share/rhemawave/audio/*.wav` is gitignored — never commit audio assets.
|
||
- `vendor/` is committed — run `go mod vendor` after `go 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.
|