2.4 KiB
Architecture
File layout
player/
├── build.jai # metaprogram, sets import_path
├── CLAUDE.md # project context
├── ai/ # AI-collaboration notes
├── data/ # fonts, static assets
├── modules/ # vendored: Jaison, stb_image
└── src/
├── main.jai # tiny entry, #loads each index.jai
├── core/index.jai # app state, window, time
├── jellyfin/index.jai # http client + endpoints
├── audio/index.jai # Sound_Player wrapper, queue, FFT
├── ui/index.jai # theme, fonts, views
├── gfx/index.jai # custom shaders, textures
└── util/index.jai # log, helpers
Convention: index.jai per folder
Each folder has an index.jai that #loads every other .jai file in the folder. main.jai only loads the index.jai files. This keeps main.jai ~10 lines and lets us add a new file by editing one #load line.
Convention: one giant App struct
core/app.jai defines App :: struct { ... } and a global app: App;. Everything that needs to live across frames hangs off app — the window handle, fonts, the jellyfin client, the audio player, the UI state, the current view.
This is on purpose. We don't want plumbing arguments through five layers. The whole codebase has implicit access to app.
Convention: views
A "view" is a screen (login, library, now-playing). Each lives in src/ui/views/<name>_view.jai and exposes one proc:
draw_<name>_view :: (dt: float)
app.current_view is an enum that picks which one runs each frame. Transitions are app.current_view = .LIBRARY;.
Convention: HTTP
jellyfin/client.jai wraps libcurl with a small http_get / http_post helper that returns a string body + status code. The auth token gets stamped into headers automatically once auth.login() succeeds.
Convention: audio
audio/player.jai owns a single *Sound_Stream for the currently playing track plus a queue of pending tracks. Sound_Player handles the actual decoding and output.
Convention: gfx
gfx/shaders.jai keeps GLSL strings inline (compiled via Simp's GL backend). The visualizer is a fullscreen quad with a fragment shader that reads from FFT data uploaded as a 1D texture.