ommp.git

ommp

~ Oh My Music Player — a standalone terminal music player built with Rust and ratatui
git clone https://github.com/debuman/ommp.git

site/tools/note-field.py (2991B)

      1 #!/usr/bin/env python3
      2 """Generate the hero's background: ommp's splash-screen note field.
      3 
      4 The splash fills the terminal with dark and scatters music notes across it
      5 (src/ui/widgets/about_modal.rs, render_splash_screen). This reproduces that
      6 exact scatter — the same hash, the same four glyphs, the same three colours —
      7 as a tile the page can repeat.
      8 
      9 The scatter is periodic. A note falls on cell (x, y) when
     10 
     11     (7x + 13y) mod 37 == 0
     12 
     13 and shifting x or y by 37 adds 7*37 or 13*37 to the hash, both multiples of 37.
     14 So a 37x37 tile repeats seamlessly. Which glyph and which colour a note gets
     15 depends on hash/37, whose period is 444 rather than 37, so those cycle sooner
     16 here than in the app. At this contrast — the brightest of the three is #3C1450
     17 on #0A051E — that is not a difference anyone can see.
     18 
     19 Rendered to a raster on this machine rather than shipped as text: the glyphs
     20 live in the CJK fonts, not in the page's latin-subset JetBrains Mono, and a
     21 visitor without them would get four kinds of tofu.
     22 """
     23 
     24 import subprocess
     25 import sys
     26 
     27 # src/ui/widgets/about_modal.rs
     28 NOTES = ["♪", "♫", "♩", "◆"]
     29 DIM_COLORS = ["#3C1450", "#143C50", "#501432"]  # DIM_PURPLE, DIM_CYAN, DIM_PINK
     30 PERIOD = 37
     31 
     32 # One terminal cell, at 2x so the tile stays sharp on a hidpi screen. The 1:2
     33 # ratio is roughly what a monospace cell is.
     34 CELL_W, CELL_H = 20, 40
     35 FONT_SIZE = 26
     36 
     37 OUT = sys.argv[1] if len(sys.argv) > 1 else "static/img/note-field.png"
     38 
     39 
     40 def font():
     41     """A font with all four glyphs. Prefer the one the terminal itself falls
     42     back to for U+266A, so the page's notes are the shapes ommp actually draws."""
     43     listing = subprocess.run(
     44         ["fc-list", ":charset=266a 266b 2669 25c6", "file"],
     45         capture_output=True, text=True, check=True,
     46     ).stdout
     47     files = [l.split(":")[0].strip() for l in listing.splitlines() if l.strip()]
     48     for preferred in ("NotoSansMonoCJK", "NotoSansCJK", "DejaVuSans"):
     49         for f in files:
     50             if preferred in f:
     51                 return f
     52     if not files:
     53         sys.exit("no font on this system has all four glyphs")
     54     return files[0]
     55 
     56 
     57 def main():
     58     args = ["magick", "-size", f"{PERIOD * CELL_W}x{PERIOD * CELL_H}", "xc:none",
     59             "-font", font(), "-pointsize", str(FONT_SIZE), "-gravity", "none"]
     60 
     61     drawn = 0
     62     for y in range(PERIOD):
     63         for x in range(PERIOD):
     64             h = (x * 7 + y * 13) & 0xFFFFFFFF
     65             if h % PERIOD:
     66                 continue
     67             glyph = NOTES[(h // PERIOD) % len(NOTES)]
     68             colour = DIM_COLORS[(h // PERIOD + 1) % len(DIM_COLORS)]
     69             # annotate's origin is the baseline; sit the glyph in its cell.
     70             args += ["-fill", colour,
     71                      "-annotate", f"+{x * CELL_W + 2}+{y * CELL_H + CELL_H - 12}", glyph]
     72             drawn += 1
     73 
     74     args += ["-strip", OUT]
     75     subprocess.run(args, check=True)
     76     print(f"  {OUT}: {drawn} notes in a {PERIOD}x{PERIOD} cell tile")
     77 
     78 
     79 if __name__ == "__main__":
     80     main()