lowhum 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lowhum-0.4.0/.gitignore +15 -0
- lowhum-0.4.0/LICENSE +21 -0
- lowhum-0.4.0/LowHum.icns +0 -0
- lowhum-0.4.0/PKG-INFO +96 -0
- lowhum-0.4.0/README.md +71 -0
- lowhum-0.4.0/docs/index.html +197 -0
- lowhum-0.4.0/info_about_dock_icon.md +38 -0
- lowhum-0.4.0/justfile +14 -0
- lowhum-0.4.0/pyproject.toml +57 -0
- lowhum-0.4.0/setup.py +85 -0
- lowhum-0.4.0/src/lowhum/__init__.py +19 -0
- lowhum-0.4.0/src/lowhum/app.py +760 -0
- lowhum-0.4.0/src/lowhum/audio.py +400 -0
- lowhum-0.4.0/src/lowhum/cli.py +222 -0
- lowhum-0.4.0/src/lowhum/coreaudio.py +124 -0
- lowhum-0.4.0/src/lowhum/generator.py +440 -0
- lowhum-0.4.0/src/lowhum/icons.py +51 -0
- lowhum-0.4.0/src/lowhum/resources/__init__.py +18 -0
- lowhum-0.4.0/src/lowhum/resources/icons/.gitkeep +0 -0
- lowhum-0.4.0/src/lowhum/resources/icons/app_icon.png +0 -0
- lowhum-0.4.0/src/lowhum/resources/icons/menubar_dark.png +0 -0
- lowhum-0.4.0/src/lowhum/resources/icons/menubar_light.png +0 -0
- lowhum-0.4.0/tests/__init__.py +0 -0
- lowhum-0.4.0/tests/test_app.py +80 -0
- lowhum-0.4.0/tests/test_audio.py +125 -0
- lowhum-0.4.0/tests/test_generator.py +148 -0
- lowhum-0.4.0/uv.lock +602 -0
lowhum-0.4.0/.gitignore
ADDED
lowhum-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luis Markmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lowhum-0.4.0/LowHum.icns
ADDED
|
Binary file
|
lowhum-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lowhum
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Deep Brown Noise for Focus as a MenuBar App & CLI Tool
|
|
5
|
+
Project-URL: Homepage, https://lmarkmann.github.io/lowhum/
|
|
6
|
+
Project-URL: Repository, https://github.com/lmarkmann/lowhum
|
|
7
|
+
Author: Luis Markmann
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: audio,brown-noise,focus,macos,menu-bar
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: numpy>=2.0
|
|
19
|
+
Requires-Dist: pillow>=11.0
|
|
20
|
+
Requires-Dist: rumps>=0.4.0
|
|
21
|
+
Requires-Dist: scipy>=1.14
|
|
22
|
+
Requires-Dist: sounddevice>=0.5
|
|
23
|
+
Requires-Dist: typer>=0.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
<pre>
|
|
28
|
+
_.-````'-,_
|
|
29
|
+
_,.,_ ,-' `'-.,_
|
|
30
|
+
/) (\ '`-.
|
|
31
|
+
(( ) ) `\
|
|
32
|
+
\) (_/ )\
|
|
33
|
+
| /) ' ,' / \
|
|
34
|
+
`\ ^' ' ( / ))
|
|
35
|
+
| _/\ , / ,,`\ ( "`
|
|
36
|
+
\Y, | \ \ | ``````| / \_ \
|
|
37
|
+
`)_/ \ \ ) ( > ( >
|
|
38
|
+
\( \( |/ |/
|
|
39
|
+
/_(/_( /_( /_(
|
|
40
|
+
</pre>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
# LowHum
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/lowhum/)
|
|
46
|
+
[](https://pypi.org/project/lowhum/)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](https://pypi.org/project/lowhum/)
|
|
49
|
+
|
|
50
|
+
Deep brown noise for focus, for macOS menu bar. Requires macOS and Python 3.12+.
|
|
51
|
+
|
|
52
|
+
Single-purpose menu bar app that generates deep brown noise locally and plays it on loop. Install it, click play, forget about it.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
**pip / uv** (requires Python 3.12+):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv tool install lowhum
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Standalone .app** — download `LowHum.app` from [the latest release](https://github.com/lmarkmann/lowhum/releases/latest). The app is not notarized, so macOS will block it on first launch. To allow it:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
xattr -cr /Applications/LowHum.app
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
lowhum # launch the menu-bar app (runs in background)
|
|
72
|
+
lowhum devices # list output devices
|
|
73
|
+
lowhum generate # pre-generate the audio file
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`lhm` is a shorthand alias for all commands.
|
|
77
|
+
|
|
78
|
+
### Menu bar controls
|
|
79
|
+
|
|
80
|
+
- Play / Stop from the menu bar
|
|
81
|
+
- Pick any connected audio device from the Output Device submenu
|
|
82
|
+
- Binaural beats overlay (Theta, Alpha, Beta, Gamma)
|
|
83
|
+
- Noise color selection (brown, pink, white)
|
|
84
|
+
- Auto-stops when headphones connect or disconnect
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
On first launch, a 10-minute WAV is synthesized locally for every noise color and binaural combination. Cumulative-sum brown noise through a Butterworth bandpass (1 to 500 Hz, 20 Hz sub-bass highpass), RMS-normalized per chunk, crossfaded at boundaries. Everything is stored in `~/.lowhum/`.
|
|
89
|
+
|
|
90
|
+
Playback streams through PortAudio via memory-mapped files, so the full WAV never sits in RAM. The app polls audio devices every 2 seconds and stops instantly if headphones disconnect or a Bluetooth device drops.
|
|
91
|
+
|
|
92
|
+
The menu bar icon is a template image, so macOS handles dark/light mode automatically.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
lowhum-0.4.0/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<pre>
|
|
3
|
+
_.-````'-,_
|
|
4
|
+
_,.,_ ,-' `'-.,_
|
|
5
|
+
/) (\ '`-.
|
|
6
|
+
(( ) ) `\
|
|
7
|
+
\) (_/ )\
|
|
8
|
+
| /) ' ,' / \
|
|
9
|
+
`\ ^' ' ( / ))
|
|
10
|
+
| _/\ , / ,,`\ ( "`
|
|
11
|
+
\Y, | \ \ | ``````| / \_ \
|
|
12
|
+
`)_/ \ \ ) ( > ( >
|
|
13
|
+
\( \( |/ |/
|
|
14
|
+
/_(/_( /_( /_(
|
|
15
|
+
</pre>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
# LowHum
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/lowhum/)
|
|
21
|
+
[](https://pypi.org/project/lowhum/)
|
|
22
|
+
[](LICENSE)
|
|
23
|
+
[](https://pypi.org/project/lowhum/)
|
|
24
|
+
|
|
25
|
+
Deep brown noise for focus, for macOS menu bar. Requires macOS and Python 3.12+.
|
|
26
|
+
|
|
27
|
+
Single-purpose menu bar app that generates deep brown noise locally and plays it on loop. Install it, click play, forget about it.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
**pip / uv** (requires Python 3.12+):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install lowhum
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Standalone .app** — download `LowHum.app` from [the latest release](https://github.com/lmarkmann/lowhum/releases/latest). The app is not notarized, so macOS will block it on first launch. To allow it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
xattr -cr /Applications/LowHum.app
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
lowhum # launch the menu-bar app (runs in background)
|
|
47
|
+
lowhum devices # list output devices
|
|
48
|
+
lowhum generate # pre-generate the audio file
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`lhm` is a shorthand alias for all commands.
|
|
52
|
+
|
|
53
|
+
### Menu bar controls
|
|
54
|
+
|
|
55
|
+
- Play / Stop from the menu bar
|
|
56
|
+
- Pick any connected audio device from the Output Device submenu
|
|
57
|
+
- Binaural beats overlay (Theta, Alpha, Beta, Gamma)
|
|
58
|
+
- Noise color selection (brown, pink, white)
|
|
59
|
+
- Auto-stops when headphones connect or disconnect
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
On first launch, a 10-minute WAV is synthesized locally for every noise color and binaural combination. Cumulative-sum brown noise through a Butterworth bandpass (1 to 500 Hz, 20 Hz sub-bass highpass), RMS-normalized per chunk, crossfaded at boundaries. Everything is stored in `~/.lowhum/`.
|
|
64
|
+
|
|
65
|
+
Playback streams through PortAudio via memory-mapped files, so the full WAV never sits in RAM. The app polls audio devices every 2 seconds and stops instantly if headphones disconnect or a Bluetooth device drops.
|
|
66
|
+
|
|
67
|
+
The menu bar icon is a template image, so macOS handles dark/light mode automatically.
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>LowHum</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--bg: #0d0d0d;
|
|
12
|
+
--fg: #e0e0e0;
|
|
13
|
+
--mute: #7a7a7a;
|
|
14
|
+
--accent: #c8b88a;
|
|
15
|
+
--card: #1a1a1a;
|
|
16
|
+
--border: #2a2a2a;
|
|
17
|
+
--ok: #5a8a5a;
|
|
18
|
+
}
|
|
19
|
+
.light {
|
|
20
|
+
--bg: #ede8d8;
|
|
21
|
+
--fg: #2a2418;
|
|
22
|
+
--mute: #8a7e6a;
|
|
23
|
+
--accent: #6b5c3a;
|
|
24
|
+
--card: #ddd6c2;
|
|
25
|
+
--border: #c8c0aa;
|
|
26
|
+
--ok: #4a7a3a;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
background: var(--bg);
|
|
31
|
+
color: var(--fg);
|
|
32
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
padding: 2rem;
|
|
39
|
+
transition: background 0.3s, color 0.3s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.theme-toggle {
|
|
43
|
+
position: fixed;
|
|
44
|
+
top: 1.2rem;
|
|
45
|
+
right: 1.4rem;
|
|
46
|
+
background: none;
|
|
47
|
+
border: none;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
color: var(--mute);
|
|
50
|
+
transition: color 0.2s;
|
|
51
|
+
padding: 0.4rem;
|
|
52
|
+
}
|
|
53
|
+
.theme-toggle:hover { color: var(--accent); }
|
|
54
|
+
.theme-toggle svg { display: block; }
|
|
55
|
+
|
|
56
|
+
a.bison-link {
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
color: var(--accent);
|
|
59
|
+
cursor: default;
|
|
60
|
+
transition: color 0.3s;
|
|
61
|
+
}
|
|
62
|
+
a.bison-link:hover { color: var(--fg); }
|
|
63
|
+
|
|
64
|
+
pre.bison {
|
|
65
|
+
font-size: clamp(0.45rem, 1.8vw, 0.95rem);
|
|
66
|
+
line-height: 1.3;
|
|
67
|
+
white-space: pre;
|
|
68
|
+
text-align: left;
|
|
69
|
+
margin-bottom: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tagline {
|
|
73
|
+
color: var(--mute);
|
|
74
|
+
font-size: 0.95rem;
|
|
75
|
+
margin-bottom: 2.5rem;
|
|
76
|
+
letter-spacing: 0.05em;
|
|
77
|
+
transition: color 0.3s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.install-row {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 0.5rem;
|
|
84
|
+
margin-bottom: 0.75rem;
|
|
85
|
+
}
|
|
86
|
+
.install-cmd {
|
|
87
|
+
background: var(--card);
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
padding: 0.6rem 1rem;
|
|
91
|
+
color: var(--accent);
|
|
92
|
+
font-size: 0.85rem;
|
|
93
|
+
font-family: inherit;
|
|
94
|
+
min-width: 280px;
|
|
95
|
+
transition: background 0.3s, border-color 0.3s, color 0.3s;
|
|
96
|
+
}
|
|
97
|
+
.copy-btn {
|
|
98
|
+
background: var(--card);
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
width: 36px;
|
|
102
|
+
height: 36px;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
color: var(--mute);
|
|
108
|
+
transition: border-color 0.2s, color 0.2s, background 0.3s;
|
|
109
|
+
}
|
|
110
|
+
.copy-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
111
|
+
.copy-btn.copied { border-color: var(--ok); color: var(--ok); }
|
|
112
|
+
.copy-btn svg { display: block; }
|
|
113
|
+
</style>
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
|
|
117
|
+
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
|
|
118
|
+
<svg id="icon-sun" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
119
|
+
<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>
|
|
120
|
+
</svg>
|
|
121
|
+
<svg id="icon-moon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
|
|
122
|
+
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
|
|
123
|
+
</svg>
|
|
124
|
+
</button>
|
|
125
|
+
|
|
126
|
+
<a class="bison-link" href="https://github.com/lmarkmann/lowhum" target="_blank" rel="noopener">
|
|
127
|
+
<pre class="bison">
|
|
128
|
+
_.-````'-,_
|
|
129
|
+
_,.,_ ,-' `'-.,_
|
|
130
|
+
/) (\ '`-.
|
|
131
|
+
(( ) ) `\
|
|
132
|
+
\) (_/ )\
|
|
133
|
+
| /) ' ,' / \
|
|
134
|
+
`\ ^' ' ( / ))
|
|
135
|
+
| _/\ , / ,,`\ ( "`
|
|
136
|
+
\Y, | \ \ | ``````| / \_ \
|
|
137
|
+
`)_/ \ \ ) ( > ( >
|
|
138
|
+
\( \( |/ |/
|
|
139
|
+
/_(/_( /_( /_(
|
|
140
|
+
</pre>
|
|
141
|
+
</a>
|
|
142
|
+
|
|
143
|
+
<p class="tagline">deep brown noise for focus</p>
|
|
144
|
+
|
|
145
|
+
<div class="install-row">
|
|
146
|
+
<code class="install-cmd" id="cmd-uv">uv tool install -U lowhum</code>
|
|
147
|
+
<button class="copy-btn" onclick="copy('cmd-uv', this)" aria-label="Copy">
|
|
148
|
+
<svg class="ico-clip" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
149
|
+
<svg class="ico-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M20 6 9 17l-5-5"/></svg>
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="install-row">
|
|
154
|
+
<code class="install-cmd" id="cmd-pip">pip install -U lowhum</code>
|
|
155
|
+
<button class="copy-btn" onclick="copy('cmd-pip', this)" aria-label="Copy">
|
|
156
|
+
<svg class="ico-clip" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
157
|
+
<svg class="ico-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M20 6 9 17l-5-5"/></svg>
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<script>
|
|
162
|
+
function copy(id, btn) {
|
|
163
|
+
navigator.clipboard.writeText(document.getElementById(id).textContent);
|
|
164
|
+
btn.querySelector('.ico-clip').style.display = 'none';
|
|
165
|
+
btn.querySelector('.ico-check').style.display = 'block';
|
|
166
|
+
btn.classList.add('copied');
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
btn.querySelector('.ico-clip').style.display = 'block';
|
|
169
|
+
btn.querySelector('.ico-check').style.display = 'none';
|
|
170
|
+
btn.classList.remove('copied');
|
|
171
|
+
}, 1500);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function applyTheme(light) {
|
|
175
|
+
document.body.classList.toggle('light', light);
|
|
176
|
+
document.getElementById('icon-sun').style.display = light ? 'none' : 'block';
|
|
177
|
+
document.getElementById('icon-moon').style.display = light ? 'block' : 'none';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function toggleTheme() {
|
|
181
|
+
var light = !document.body.classList.contains('light');
|
|
182
|
+
applyTheme(light);
|
|
183
|
+
localStorage.setItem('theme', light ? 'light' : 'dark');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
(function() {
|
|
187
|
+
var saved = localStorage.getItem('theme');
|
|
188
|
+
if (saved) {
|
|
189
|
+
applyTheme(saved === 'light');
|
|
190
|
+
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
|
191
|
+
applyTheme(true);
|
|
192
|
+
}
|
|
193
|
+
})()
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Basic usage:
|
|
2
|
+
|
|
3
|
+
1 import rumps
|
|
4
|
+
2
|
|
5
|
+
3 class MyApp(rumps.App):
|
|
6
|
+
4 def __init__(self):
|
|
7
|
+
5 super().__init__("My App")
|
|
8
|
+
6 # Set dock icon from image file
|
|
9
|
+
7 self.dock_icon = "path/to/icon.png"
|
|
10
|
+
|
|
11
|
+
Key points:
|
|
12
|
+
|
|
13
|
+
- dock_icon accepts a path to an image file (PNG, ICNS, etc.)
|
|
14
|
+
- You can change it dynamically at runtime
|
|
15
|
+
- Set to None to hide the dock icon entirely
|
|
16
|
+
- Useful for status bar apps that want to optionally show/hide their dock presence
|
|
17
|
+
|
|
18
|
+
Example - toggling dock icon:
|
|
19
|
+
|
|
20
|
+
1 import rumps
|
|
21
|
+
2
|
|
22
|
+
3 class MyApp(rumps.App):
|
|
23
|
+
4 def __init__(self):
|
|
24
|
+
5 super().__init__("My App")
|
|
25
|
+
6 self.menu = ["Toggle Dock Icon", "Quit"]
|
|
26
|
+
7
|
|
27
|
+
8 @rumps.clicked("Toggle Dock Icon")
|
|
28
|
+
9 def toggle(self, _):
|
|
29
|
+
10 if self.dock_icon:
|
|
30
|
+
11 self.dock_icon = None # Hide
|
|
31
|
+
12 else:
|
|
32
|
+
13 self.dock_icon = "icon.png" # Show
|
|
33
|
+
14
|
|
34
|
+
15 if __name__ == "__main__":
|
|
35
|
+
16 MyApp().run()
|
|
36
|
+
|
|
37
|
+
This is separate from the status bar icon - you can have both, one, or neither
|
|
38
|
+
depending on your app's needs.
|
lowhum-0.4.0/justfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version := `python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"`
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
uv run python setup.py py2app
|
|
5
|
+
|
|
6
|
+
archive: build
|
|
7
|
+
cd dist && zip -r "LowHum-{{version}}-macOS-arm64.zip" LowHum.app
|
|
8
|
+
|
|
9
|
+
clean:
|
|
10
|
+
rm -rf .venv build dist .ruff_cache .pytest_cache .mypy_cache
|
|
11
|
+
find . -type d -name "__pycache__" -prune -exec rm -rf {} +
|
|
12
|
+
|
|
13
|
+
sha256:
|
|
14
|
+
shasum -a 256 dist/LowHum-{{version}}-macOS-arm64.zip
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lowhum"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Deep Brown Noise for Focus as a MenuBar App & CLI Tool"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Luis Markmann" }]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
keywords = ["brown-noise", "focus", "audio", "macos", "menu-bar"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Environment :: MacOS X",
|
|
13
|
+
"Intended Audience :: End Users/Desktop",
|
|
14
|
+
"Operating System :: MacOS",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"numpy>=2.0",
|
|
20
|
+
"pillow>=11.0",
|
|
21
|
+
"rumps>=0.4.0",
|
|
22
|
+
"scipy>=1.14",
|
|
23
|
+
"sounddevice>=0.5",
|
|
24
|
+
"typer>=0.12",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"ty>=0.0.1a7",
|
|
30
|
+
"py2app>=0.28",
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
"ruff>=0.9",
|
|
33
|
+
"setuptools>=82",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
lowhum = "lowhum.cli:app"
|
|
39
|
+
lhm = "lowhum.cli:app"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://lmarkmann.github.io/lowhum/"
|
|
43
|
+
Repository = "https://github.com/lmarkmann/lowhum"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["hatchling"]
|
|
47
|
+
build-backend = "hatchling.build"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/lowhum"]
|
|
51
|
+
exclude = ["*.wav"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 80
|
|
55
|
+
|
|
56
|
+
[tool.mypy]
|
|
57
|
+
ignore_missing_imports = true
|
lowhum-0.4.0/setup.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""py2app build configuration for LowHum.app.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
uv run python setup.py py2app
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Ensure py2app can find the lowhum package from src/ layout,
|
|
12
|
+
# even when the editable install .pth file isn't processed.
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
|
14
|
+
|
|
15
|
+
from setuptools import setup
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
_VERSION = importlib.metadata.version("lowhum")
|
|
19
|
+
except Exception:
|
|
20
|
+
_VERSION = "0.0.0"
|
|
21
|
+
|
|
22
|
+
# py2app 0.28 rejects install_requires, but setuptools auto-populates
|
|
23
|
+
# it from pyproject.toml [project].dependencies. Patch the check out.
|
|
24
|
+
import py2app.build_app as _build_app # noqa: E402
|
|
25
|
+
|
|
26
|
+
_orig_finalize = _build_app.py2app.finalize_options
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _finalize_no_install_requires(self):
|
|
30
|
+
self.distribution.install_requires = []
|
|
31
|
+
_orig_finalize(self)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_build_app.py2app.finalize_options = _finalize_no_install_requires
|
|
35
|
+
|
|
36
|
+
APP = ["src/lowhum/cli.py"]
|
|
37
|
+
|
|
38
|
+
OPTIONS = {
|
|
39
|
+
"argv_emulation": False,
|
|
40
|
+
# Avoid py2app's Tk/Tcl recipe (we don't ship/use tkinter).
|
|
41
|
+
# This prevents flaky builds caused by environment-dependent Tk checks.
|
|
42
|
+
"excludes": [
|
|
43
|
+
"tkinter",
|
|
44
|
+
"_tkinter",
|
|
45
|
+
"tk",
|
|
46
|
+
"tcl",
|
|
47
|
+
"Tkinter",
|
|
48
|
+
"Tix",
|
|
49
|
+
"ttk",
|
|
50
|
+
],
|
|
51
|
+
"plist": {
|
|
52
|
+
"CFBundleName": "LowHum",
|
|
53
|
+
"CFBundleDisplayName": "LowHum",
|
|
54
|
+
"CFBundleIdentifier": "com.lowhum.app",
|
|
55
|
+
"CFBundleVersion": _VERSION,
|
|
56
|
+
"CFBundleShortVersionString": _VERSION,
|
|
57
|
+
# LSUIElement=False so app appears in Force Quit (Cmd+Option+Esc).
|
|
58
|
+
# Dock visibility is controlled at runtime via setActivationPolicy.
|
|
59
|
+
"LSUIElement": False,
|
|
60
|
+
},
|
|
61
|
+
"packages": [
|
|
62
|
+
"lowhum",
|
|
63
|
+
"numpy",
|
|
64
|
+
"scipy",
|
|
65
|
+
"sounddevice",
|
|
66
|
+
"rumps",
|
|
67
|
+
"typer",
|
|
68
|
+
"click",
|
|
69
|
+
"rich",
|
|
70
|
+
"pygments",
|
|
71
|
+
"markdown_it",
|
|
72
|
+
"mdurl",
|
|
73
|
+
"PIL",
|
|
74
|
+
"_sounddevice_data",
|
|
75
|
+
],
|
|
76
|
+
"includes": ["typing_extensions"],
|
|
77
|
+
"iconfile": "LowHum.icns",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setup(
|
|
81
|
+
app=APP,
|
|
82
|
+
name="LowHum",
|
|
83
|
+
options={"py2app": OPTIONS},
|
|
84
|
+
setup_requires=["py2app"],
|
|
85
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""LowHum: deep brown noise for focus."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from importlib.metadata import metadata as _metadata
|
|
5
|
+
|
|
6
|
+
_meta = _metadata("lowhum")
|
|
7
|
+
__version__ = _meta["Version"]
|
|
8
|
+
__title__ = _meta["Name"]
|
|
9
|
+
__author__ = _meta["Author"]
|
|
10
|
+
__license__ = _meta["License-Expression"]
|
|
11
|
+
__summary__ = _meta["Summary"]
|
|
12
|
+
__url__ = _meta["Project-URL"].split(", ", 1)[1] # "Homepage, https://..."
|
|
13
|
+
except Exception:
|
|
14
|
+
__version__ = "0.4.0"
|
|
15
|
+
__title__ = "lowhum"
|
|
16
|
+
__author__ = "Luis Markmann"
|
|
17
|
+
__license__ = "MIT"
|
|
18
|
+
__summary__ = "Deep Brown Noise for Focus as a MenuBar App & CLI Tool"
|
|
19
|
+
__url__ = "https://lmarkmann.github.io/lowhum/"
|