fi-geon
v1.6.0
Published
A minimalist terminal music player — one screen, no chrome, and everything on it moving.
Maintainers
Readme
lofi
A minimalist terminal music player. One screen, no chrome, and everything on it moving.
fi-geon ● focus 24:58 ○○○○ rain · bars
▸ Mind Soup - Day 42
groove salad · somafm · ambient · downtempo
▆
▃█
███▄▁ │▃ ▃▃ ▂ │
▁█████▅▁▁ ▁ ▂▄▁█▇▂ ▃██ ██▅▁▃▄▄▆▅▁ ▁█ ▃▅ ▄ ▂▁ ▁▃▂▂██▆▃ ▁▃▃
█████████▆██████████▇███▅███████████▇▆███▆▆▇██▇▆█▇████████████▇▆█████████
●──────────────────────────────────────────────────────────── 0:04 70%
space ⏯ n next s stations w scene v visual t deck f focus q quitThat spectrum is a real FFT of the audio you are hearing, not an animation that happens to wiggle. Rain falls behind the text rather than through it.
Install
npm install -g fi-geon
fi-geonThen fi-geon -demo if you want to try it without a network — the demo track is
synthesised in-process.
Keys
| key | |
|---|---|
| space | play / pause |
| n p | next / previous, within the genre you are in |
| g | genres — seven families; n and p stay inside the one you pick |
| + - m | volume, mute |
| s | sources — tab cycles catalog → radio → casts → starred |
| / | search wherever you are |
| r | radio — search ~60,000 stations by name, country or tag |
| c | casts — podcasts; enter on a show lists its episodes |
| * | star the highlighted row, or show what you starred |
| h | what played — enter replays it |
| w | cycle scene — 27 of them, or pick from the list in the menu |
| v | cycle visual |
| t | talk mode — carves 1-4kHz so music sits under your voice |
| a A | ambient bed, forward and back |
| < > | ambient level |
| f | focus timer — again pauses, again hides |
| T | name what you are working on |
| o · tab | options — deck, skin, colour, speed, layouts, saving |
| S | stream mode — hides the chrome |
| W | save the current setup |
| ? | keys |
| q | quit |
Scenes
Twenty-six, plus a blank one. w cycles them, or pick from the list in the o menu.
| scene | what it is |
|---|---|
| sweethearts | the author's own chibi drawing, converted to cells — the default |
| reef | fish crossing at three depths, kelp and bubbles |
| koi pond | koi turning under the surface, ripple rings, lily pads |
| jellyfish | bells pulsing upward, trailing tentacles, near-black water |
| night fishing | a boat in a shimmering moonpath, lantern at the mast |
| rain | rain falling at three depths, splashing where it lands |
| snow | snow drifting down through the dark |
| drift | slow cloud over a low horizon |
| fireflies | fireflies over grass that ripples in the wind |
| steam | steam rising and curling |
| aurora | curtains of light over a dark horizon |
| drive | a night road unspooling under headlights |
| nipa hut | a hut in a field under a wide sky |
| jeepney | a Philippine jeepney running a night road, city parallax behind |
| wires | pigeons on power lines, each with its own bob, fidget and hop |
| cafe | rain on the window, pendant lamps, an espresso machine venting steam |
| lanterns | paper lanterns rising against indigo, warm on cold |
| sakura | petals falling past a blossom branch, pastel dawn |
| grid | banded sun over a scrolling perspective grid |
| lighthouse | a beam sweeping the sea in a slow arc |
| night market | rows of lit stalls, swaying bulbs, drifting crowd |
| campfire | flames driven by the spectrum, embers rising under a starfield |
| bedroom | a lamp-lit room at night |
| city | a rain-soaked skyline with lit windows |
| cyberpunk | neon and scan lines that quicken with the music |
| starry night | swirling sky after the painting |
| clear | nothing, for when you want the visualiser alone |
Every one is audio-reactive and density-aware, so -density genuinely thins them.
All are measured rather than eyeballed: each is stepped for 350 frames against a
synthetic signal and checked for subject size, per-frame motion, single-frame
jumps and draw cost. See Testing.
Your own artwork as a scene
Point fi-geon at a picture and it becomes a scene. No toolchain, no rebuild — the conversion happens when the player starts.
fi-geon -art ~/Pictures/my-drawing.pngIt converts the picture, starts on it straight away, and prints the name it
took. Pass -scene as well if you would rather start somewhere else — a scene
you name yourself always wins. To keep it, put it in the config:
art = "~/Pictures/my-drawing.png"
art_name = "my drawing"-art-width controls how many cells wide it converts at (default 200). Higher
is more detailed but only helps up to what your terminal can show.
Tuning a conversion
| flag | what it does |
|---|---|
| -art-width N | how many cells wide to convert at (default 200). More detail, up to what your terminal shows |
| -art-pixels | solid colour blocks instead of characters — closest to the original, but no longer ascii |
| -art-dither X | spread colour error between neighbouring cells, 0 to 1. On automatically for photographs |
Dithering is what lets a handful of colours stand in for many: instead of rounding each cell to the nearest palette colour and throwing the remainder away, the remainder is pushed into the cells not yet drawn, so a gradient reads as a gradient rather than banding into stripes. It is judged over a small neighbourhood rather than cell by cell — per-cell error necessarily rises, which is the trade. On a photograph it lowers neighbourhood error by about 8%.
What converts well
Every cell is one character in one colour, so the drawing is reduced a long way. Flat pixel or vector art survives that; photographs do not.
| works | does not | |---|---| | flat colour fields | photographs, soft gradients | | a handful of colours | dense detail | | strong outlines | painterly edges | | one clear subject | busy scenes | | plain or transparent background | cluttered backgrounds |
png, jpeg and gif are read. webp and heic are not — export as png first.
How it works
The palette is learned from the picture, not mapped onto a fixed one:
k-means over the non-background pixels picks the colours the drawing is actually
made of, and each gets its own slot. A fixed palette cannot help but be wrong —
the bundled drawing's hair is rgb(255,167,158), and the closest colour the
scene palette could offer was eight points of red and ten of blue away, which
reads as the wrong pink.
The character comes from how dark each patch is, drawn from the ramp
" .:-=+*#%". Because flat art occupies a narrow slice of the luminance range,
the ramp is stretched to what is actually present rather than to 0–1; without
that every cell lands on two or three characters and the drawing reads flat.
Cells with enough dark pixels are treated as linework instead of averaged, so
outlines and eyes survive being made small.
Converted scenes animate: they breathe, they shed drifting motes, and the whole
drawing steps along the shade ramp with the music — + becomes * becomes #
on a loud passage — so it brightens without anything changing colour.
Genres
The catalog is two levels, not one flat list. Every stream carries a genre, and
n and p move within it, so stepping from a lofi station lands on another
lofi station rather than dropping you into drone. g opens the seven families —
lofi, chillout, piano, jazz, ambient, space, sleep — and enter drops you into one.
On a wide terminal the sidebar lists that genre's stations with the playing one
marked, so you can see where n goes next. On a narrow one the genre and your
position in it (piano 1/4) sit under the track title instead.
Ambient
Fourteen textures, generated in-process — no assets, works offline, never loops:
render an ambient texture to WAV and exit ("all" for every one)a cycles forward, A back, < > set the level. Render any of them to WAV
to listen properly, or to keep a long one:
fi-geon -export-ambient rain -out rain.wav -seconds 600
fi-geon -export-ambient all -out ./beds -seconds 60They mix under whatever is playing, or pick the ambient only station for the bed by itself.
Platforms
macOS (Intel and Apple Silicon), Linux (x64 and arm64), Windows (x64). One package carries all five; the launcher picks yours at run time.
Linux needs the ALSA runtime, which desktop installs already have. On a
minimal container or WSL image: sudo apt install libasound2 (or alsa-lib on
Fedora, Arch and Alpine). fi-geon tells you this if it hits it.
Streaming
fi-geon -chroma green # flat keyable background, for an OBS colour key
fi-geon -size 80x24 # lock the layout so the source never reflows
fi-geon -state s.json # full player state as JSON, for custom overlays
fi-geon -nowplaying np.txt # just the current track, for an OBS text source
fi-geon -density 0.4 # thinner scenes, cheaper for a stream encoder
fi-geon -motion 0.3 # calmer, slower animation
fi-geon -sleep 45m # fade out over the last 30s, then quitL evens out stations mastered at different levels, h shows what played.
The o menu holds every option grouped; under focus the sessions, focus,
break and long-break lengths adjust live with + and −.
Podcasts: press c, then enter on a show to list its episodes. Type to filter
them. Where you stopped is remembered — an episode picks up where you left off —
and z changes speed without turning voices into chipmunks.
Favourites: * stars whatever row is highlighted, in any picker — a station, a
radio result from anywhere in the world, or a single episode. * on its own opens the lot.
Focus: f starts a block, T names what you are working on, and today's total,
completed blocks and day streak are kept. G decides whether the next block
starts on its own or waits for you.
On a terminal 116 columns or wider, a sidebar appears with the current track, the focus clock and what played recently.
Config
~/.config/fi-geon/config.toml. Write a starting point with fi-geon -save:
station = "lofi"
scene = "rain"
visual = "bars"
deck = "bar"
volume = 0.7Flags beat the file. A typo is reported with its line number and skipped — one bad key never costs you the rest of the file.
Source
Go, three direct dependencies. https://github.com/aiscenblue/lofi
go install github.com/aiscenblue/lofi@latestMIT.
