Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
midiviz
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PLN
midiviz
Commits
e6ff74db
Commit
e6ff74db
authored
Sep 28, 2026
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
themes: a ui_font per theme, devcon sets every digit in Jersey 15
parent
33202899
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
67 additions
and
13 deletions
+67
-13
midiviz.py
midiviz.py
+39
-13
test_midiviz.py
tests/test_midiviz.py
+28
-0
No files found.
midiviz.py
View file @
e6ff74db
...
@@ -374,6 +374,9 @@ class Theme(NamedTuple):
...
@@ -374,6 +374,9 @@ class Theme(NamedTuple):
scan
:
"tuple[tuple[int, int, int, int], tuple[int, int, int, int]] | None"
scan
:
"tuple[tuple[int, int, int, int], tuple[int, int, int, int]] | None"
bar_frac
:
float
# value-bar height as a fraction of the cell
bar_frac
:
float
# value-bar height as a fraction of the cell
bold
:
bool
# a heavier face, for a screen in daylight
bold
:
bool
# a heavier face, for a screen in daylight
# The face every digit and label is set in: "mono" (the cockpit's
# monospace stack) or "jersey" (the vendored pixel face, see JERSEY_PATH).
ui_font
:
str
# The dark ink ramp, sampled at levels 1..4, IS the dark surface ramp -- which
# The dark ink ramp, sampled at levels 1..4, IS the dark surface ramp -- which
...
@@ -391,7 +394,7 @@ THEMES: dict[str, Theme] = {
...
@@ -391,7 +394,7 @@ THEMES: dict[str, Theme] = {
spec
=
SpecRamp
(
0.68
,
-
0.055
,
0.80
,
0.30
,
0.70
,
12
,
84
),
spec
=
SpecRamp
(
0.68
,
-
0.055
,
0.80
,
0.30
,
0.70
,
12
,
84
),
spec_peak
=
(
0xc8
,
0xa8
,
0xff
,
90
),
spec_peak
=
(
0xc8
,
0xa8
,
0xff
,
90
),
scan
=
((
0xa0
,
0x60
,
0xff
,
16
),
(
0xa0
,
0x60
,
0xff
,
9
)),
scan
=
((
0xa0
,
0x60
,
0xff
,
16
),
(
0xa0
,
0x60
,
0xff
,
9
)),
bar_frac
=
0.22
,
bold
=
False
,
bar_frac
=
0.22
,
bold
=
False
,
ui_font
=
"mono"
,
),
),
# Indoors on a pale desktop. Still a lens, still the violet arc, still a
# Indoors on a pale desktop. Still a lens, still the violet arc, still a
# decay gradient the eye can follow -- the ink simply runs the other way.
# decay gradient the eye can follow -- the ink simply runs the other way.
...
@@ -410,7 +413,7 @@ THEMES: dict[str, Theme] = {
...
@@ -410,7 +413,7 @@ THEMES: dict[str, Theme] = {
spec
=
SpecRamp
(
0.62
,
-
0.05
,
0.55
,
0.62
,
-
0.28
,
34
,
120
),
spec
=
SpecRamp
(
0.62
,
-
0.05
,
0.55
,
0.62
,
-
0.28
,
34
,
120
),
spec_peak
=
(
0x4a
,
0x2a
,
0x78
,
110
),
spec_peak
=
(
0x4a
,
0x2a
,
0x78
,
110
),
scan
=
((
0x50
,
0x3a
,
0x78
,
10
),
(
0x50
,
0x3a
,
0x78
,
6
)),
scan
=
((
0x50
,
0x3a
,
0x78
,
10
),
(
0x50
,
0x3a
,
0x78
,
6
)),
bar_frac
=
0.22
,
bold
=
False
,
bar_frac
=
0.22
,
bold
=
False
,
ui_font
=
"mono"
,
),
),
# Direct sunlight, and built for nothing else. Near-white page, heavy ink
# Direct sunlight, and built for nothing else. Near-white page, heavy ink
# at every decay level (a "dim" state outdoors is a blank state), thicker
# at every decay level (a "dim" state outdoors is a blank state), thicker
...
@@ -434,7 +437,9 @@ THEMES: dict[str, Theme] = {
...
@@ -434,7 +437,9 @@ THEMES: dict[str, Theme] = {
spec
=
SpecRamp
(
0.058
,
-
0.45
,
0.85
,
0.75
,
-
0.30
,
40
,
140
),
spec
=
SpecRamp
(
0.058
,
-
0.45
,
0.85
,
0.75
,
-
0.30
,
40
,
140
),
spec_peak
=
(
0x07
,
0xff
,
0xda
,
160
),
# the site's mint
spec_peak
=
(
0x07
,
0xff
,
0xda
,
160
),
# the site's mint
scan
=
((
0x00
,
0x3d
,
0xff
,
14
),
(
0x00
,
0x3d
,
0xff
,
8
)),
scan
=
((
0x00
,
0x3d
,
0xff
,
14
),
(
0x00
,
0x3d
,
0xff
,
8
)),
bar_frac
=
0.34
,
bold
=
True
,
# The site sets everything that is not body copy in Jersey 15, so
# every digit here is too.
bar_frac
=
0.34
,
bold
=
True
,
ui_font
=
"jersey"
,
),
),
# Direct sunlight, and built for nothing else. Near-white page, heavy ink
# Direct sunlight, and built for nothing else. Near-white page, heavy ink
# at every decay level (a "dim" state outdoors is a blank state), thicker
# at every decay level (a "dim" state outdoors is a blank state), thicker
...
@@ -460,7 +465,7 @@ THEMES: dict[str, Theme] = {
...
@@ -460,7 +465,7 @@ THEMES: dict[str, Theme] = {
spec
=
SpecRamp
(
0.60
,
-
0.04
,
0.72
,
0.48
,
-
0.26
,
48
,
150
),
spec
=
SpecRamp
(
0.60
,
-
0.04
,
0.72
,
0.48
,
-
0.26
,
48
,
150
),
spec_peak
=
(
0x18
,
0x0c
,
0x38
,
190
),
spec_peak
=
(
0x18
,
0x0c
,
0x38
,
190
),
scan
=
None
,
scan
=
None
,
bar_frac
=
0.34
,
bold
=
True
,
bar_frac
=
0.34
,
bold
=
True
,
ui_font
=
"mono"
,
),
),
}
}
# Cycle order for the `d` key and the menu: darkest to brightest page, so the
# Cycle order for the `d` key and the menu: darkest to brightest page, so the
...
@@ -542,6 +547,7 @@ SYNE_PATH = Path(__file__).resolve().parent / "ui" / "syne-extrabold.ttf"
...
@@ -542,6 +547,7 @@ SYNE_PATH = Path(__file__).resolve().parent / "ui" / "syne-extrabold.ttf"
# default stays the brand's Syne.
# default stays the brand's Syne.
JERSEY_PATH
=
Path
(
__file__
)
.
resolve
()
.
parent
/
"ui"
/
"Jersey15-Regular.ttf"
JERSEY_PATH
=
Path
(
__file__
)
.
resolve
()
.
parent
/
"ui"
/
"Jersey15-Regular.ttf"
WORD_FACES
=
(
"syne"
,
"jersey"
)
WORD_FACES
=
(
"syne"
,
"jersey"
)
JERSEY_UI_SIZE
=
1.3
# px multiplier when Jersey sets the digits
WORD_ALPHA
=
0.8
WORD_ALPHA
=
0.8
# ── the HL feed ────────────────────────────────────────────────────────────
# ── the HL feed ────────────────────────────────────────────────────────────
...
@@ -1293,7 +1299,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -1293,7 +1299,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
self
.
_ref_w
=
max
(
1
,
round
(
BASE_W
*
self
.
scale
))
self
.
_ref_w
=
max
(
1
,
round
(
BASE_W
*
self
.
scale
))
self
.
_ref_h
=
max
(
1
,
round
(
BASE_H
*
self
.
scale
))
self
.
_ref_h
=
max
(
1
,
round
(
BASE_H
*
self
.
scale
))
self
.
dens
=
self
.
scale
self
.
dens
=
self
.
scale
self
.
_metrics_key
=
None
# (main px, micro px, bold) last measured
self
.
_metrics_key
=
None
# (main px, micro px, bold
, face
) last measured
self
.
setMinimumSize
(
MIN_W
,
MIN_H
)
self
.
setMinimumSize
(
MIN_W
,
MIN_H
)
self
.
resize
(
self
.
_ref_w
,
self
.
_ref_h
)
self
.
resize
(
self
.
_ref_w
,
self
.
_ref_h
)
# …then read it BACK off the widget. If a minimumSize ever clamped
# …then read it BACK off the widget. If a minimumSize ever clamped
...
@@ -1472,10 +1478,10 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -1472,10 +1478,10 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
th
=
THEMES
.
get
(
name
)
th
=
THEMES
.
get
(
name
)
if
th
is
None
or
th
is
self
.
theme
:
if
th
is
None
or
th
is
self
.
theme
:
return
return
was_
bold
=
self
.
theme
.
bold
was_
face
=
(
self
.
theme
.
bold
,
self
.
theme
.
ui_font
)
self
.
theme
=
th
self
.
theme
=
th
self
.
_build_palette
()
self
.
_build_palette
()
if
th
.
bold
!=
was_bold
:
if
(
th
.
bold
,
th
.
ui_font
)
!=
was_face
:
self
.
_apply_scale
()
# new weight, new metrics, new layout
self
.
_apply_scale
()
# new weight, new metrics, new layout
if
getattr
(
self
,
"tray"
,
None
)
is
not
None
:
if
getattr
(
self
,
"tray"
,
None
)
is
not
None
:
# The tray icon is painted from the palette, so it goes stale
# The tray icon is painted from the palette, so it goes stale
...
@@ -1500,6 +1506,21 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -1500,6 +1506,21 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
save_config
(
self
.
theme
.
name
,
self
.
scale
)
save_config
(
self
.
theme
.
name
,
self
.
scale
)
def
_font
(
self
,
px
:
int
):
def
_font
(
self
,
px
:
int
):
if
self
.
theme
.
ui_font
==
"jersey"
and
self
.
_jersey_id
>=
0
:
fams
=
QtGui
.
QFontDatabase
.
applicationFontFamilies
(
self
.
_jersey_id
)
if
fams
:
f
=
QtGui
.
QFont
(
fams
[
0
])
# Jersey's caps stand shorter in their em than the mono's,
# so the same pixel size reads a size smaller.
f
.
setPixelSize
(
max
(
5
,
round
(
px
*
JERSEY_UI_SIZE
)))
# Tabular figures, so every digit shares one advance. A-F
# still do not (see `_paint_stream`). And no bold: Jersey
# ships a Regular only, and a synthesized bold smears the
# pixel grid that is the whole point of the face.
f
.
setFeature
(
QtGui
.
QFont
.
Tag
(
"tnum"
),
1
)
return
f
# No Jersey on this checkout (id -1): the mono stack, silently,
# the same non-crash rule as the wordmark.
f
=
QtGui
.
QFont
()
f
=
QtGui
.
QFont
()
f
.
setFamilies
([
"Geist Mono"
,
"JetBrains Mono"
,
"Fira Code"
,
f
.
setFamilies
([
"Geist Mono"
,
"JetBrains Mono"
,
"Fira Code"
,
"DejaVu Sans Mono"
,
"monospace"
])
"DejaVu Sans Mono"
,
"monospace"
])
...
@@ -1541,7 +1562,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -1541,7 +1562,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
"""
"""
self
.
dens
=
d
=
self
.
_density
()
self
.
dens
=
d
=
self
.
_density
()
px
,
upx
=
max
(
5
,
round
(
11
*
d
)),
max
(
5
,
round
(
8
*
d
))
px
,
upx
=
max
(
5
,
round
(
11
*
d
)),
max
(
5
,
round
(
8
*
d
))
key
=
(
px
,
upx
,
self
.
theme
.
bold
)
key
=
(
px
,
upx
,
self
.
theme
.
bold
,
self
.
theme
.
ui_font
)
if
key
!=
self
.
_metrics_key
:
if
key
!=
self
.
_metrics_key
:
# Re-measured only when the FACE actually changed. The weight
# Re-measured only when the FACE actually changed. The weight
# belongs in that key because `set_theme` calls this precisely
# belongs in that key because `set_theme` calls this precisely
...
@@ -1552,7 +1573,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -1552,7 +1573,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
self
.
f_micro
=
self
.
_font
(
upx
)
self
.
f_micro
=
self
.
_font
(
upx
)
fm
=
QtGui
.
QFontMetricsF
(
self
.
f_main
)
fm
=
QtGui
.
QFontMetricsF
(
self
.
f_main
)
fmm
=
QtGui
.
QFontMetricsF
(
self
.
f_micro
)
fmm
=
QtGui
.
QFontMetricsF
(
self
.
f_micro
)
self
.
mw
,
self
.
mh
=
fm
.
horizontalAdvance
(
"0"
),
fm
.
height
()
# The WIDEST hex glyph is the ribbon's grid step; on a
# monospaced face they are all the same and this is "0".
self
.
mw
=
max
(
fm
.
horizontalAdvance
(
c
)
for
c
in
HEX
)
self
.
mh
=
fm
.
height
()
self
.
_hex_mono
=
len
({
fm
.
horizontalAdvance
(
c
)
for
c
in
HEX
})
==
1
self
.
uw
,
self
.
uh
=
fmm
.
horizontalAdvance
(
"0"
),
fmm
.
height
()
self
.
uw
,
self
.
uh
=
fmm
.
horizontalAdvance
(
"0"
),
fmm
.
height
()
self
.
m_asc
,
self
.
u_asc
=
fm
.
ascent
(),
fmm
.
ascent
()
self
.
m_asc
,
self
.
u_asc
=
fm
.
ascent
(),
fmm
.
ascent
()
self
.
_relayout
()
self
.
_relayout
()
...
@@ -2624,9 +2649,8 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -2624,9 +2649,8 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
One drawText per glyph was 2.1 ms of an 8.9 ms frame. Consecutive
One drawText per glyph was 2.1 ms of an 8.9 ms frame. Consecutive
entries sharing a colour AND a decay level are concatenated into one
entries sharing a colour AND a decay level are concatenated into one
call — which is legitimate only for hex glyphs, because those are
call — which is legitimate only for hex glyphs, and only on a
guaranteed to come from the monospaced face at exactly `self.mw`
monospaced face, where they all advance exactly `self.mw`. A class glyph (◆ ≈ ≡ …) may be served by a fallback face
advance. A class glyph (◆ ≈ ≡ …) may be served by a fallback face
with a different advance, so those still draw one at a time or the
with a different advance, so those still draw one at a time or the
ribbon would drift out of its grid.
ribbon would drift out of its grid.
"""
"""
...
@@ -2640,7 +2664,9 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
...
@@ -2640,7 +2664,9 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
run_x
,
run_key
=
x
,
(
0
,
0
)
run_x
,
run_key
=
x
,
(
0
,
0
)
for
g
,
fam
,
t
in
self
.
stream
:
for
g
,
fam
,
t
in
self
.
stream
:
lv
=
self
.
_lv
(
now
-
t
,
floor
=
2
)
lv
=
self
.
_lv
(
now
-
t
,
floor
=
2
)
key
=
(
fam
,
lv
)
if
g
in
HEX
else
None
# Runs need every hex glyph at exactly `mw`; a proportional
# face (Jersey's A-F) gets one call per glyph instead.
key
=
(
fam
,
lv
)
if
g
in
HEX
and
self
.
_hex_mono
else
None
if
run
and
key
==
run_key
:
if
run
and
key
==
run_key
:
run
.
append
(
g
)
run
.
append
(
g
)
else
:
else
:
...
...
tests/test_midiviz.py
View file @
e6ff74db
...
@@ -1137,3 +1137,31 @@ def test_a_corner_press_really_arms_a_resize():
...
@@ -1137,3 +1137,31 @@ def test_a_corner_press_really_arms_a_resize():
# …and the type followed, without anybody calling _apply_scale by hand
# …and the type followed, without anybody calling _apply_scale by hand
assert
w
.
dens
==
w
.
_density
(),
"the density did not follow the resize"
assert
w
.
dens
==
w
.
_density
(),
"the density did not follow the resize"
w
.
mouseReleaseEvent
(
None
)
w
.
mouseReleaseEvent
(
None
)
def
test_every_theme_names_a_face_the_painter_knows
():
"""`_font` branches on `ui_font`; a typo there silently means the mono."""
import
pytest
if
not
hasattr
(
V
,
"THEMES"
):
pytest
.
skip
(
"no themes in this tree"
)
for
name
,
th
in
V
.
THEMES
.
items
():
assert
th
.
ui_font
in
(
"mono"
,
"jersey"
),
f
"{name}.ui_font = {th.ui_font!r}"
def
test_devcon_sets_its_digits_in_jersey_and_the_ribbon_keeps_its_grid
():
"""Jersey is proportional (A-F), so the ribbon must stop concatenating
runs on it, and its grid step must be the widest hex glyph."""
import
pytest
mv
,
w
=
_themed
(
"devcon"
)
if
mv
is
None
:
pytest
.
skip
(
"no Qt available"
)
if
w
.
_jersey_id
<
0
:
pytest
.
skip
(
"Jersey not vendored on this checkout"
)
assert
"Jersey"
in
w
.
f_main
.
family
()
assert
not
w
.
f_main
.
bold
(),
"a synthesized bold smears the pixel face"
QtGui
=
mv
.
_qt
()[
1
]
fm
=
QtGui
.
QFontMetricsF
(
w
.
f_main
)
assert
w
.
mw
==
max
(
fm
.
horizontalAdvance
(
c
)
for
c
in
mv
.
HEX
)
assert
w
.
_hex_mono
is
False
w
.
set_theme
(
"dark"
,
save
=
False
)
assert
"Jersey"
not
in
w
.
f_main
.
family
()
and
w
.
_hex_mono
is
True
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment