claudible 0.1.3__tar.gz → 0.1.5__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claudible
3
- Version: 0.1.3
4
- Summary: Ambient audio soundscape feedback for terminal output - an opus for your terminals
3
+ Version: 0.1.5
4
+ Summary: Ambient audio soundscape feedback for terminal output - an opus for your terminals, claude code by default
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/anthropics/claudible
7
7
  Classifier: Development Status :: 4 - Beta
@@ -40,7 +40,9 @@ Requires-Dist: sounddevice>=0.4.0
40
40
 
41
41
  *An opus for your terminals.*
42
42
 
43
- Possibly the most annoying Claude utility ever made but here it is anyway. Ambient audio soundscape feedback for terminal output.
43
+ Possibly the most annoying Claude utility ever made but here it is anyway.
44
+
45
+ Ambient audio soundscape feedback for terminal output. It really shines when used with a lot of busy busy Claude Code terminals running at the same time.
44
46
 
45
47
  ## 🎼 The Idea
46
48
 
@@ -64,7 +66,7 @@ pip install claudible
64
66
  ## 🎹 Usage
65
67
 
66
68
  ```bash
67
- # Run claude with audio feedback (default)
69
+ # Run claude with audio feedback (default: ambient sound set)
68
70
  claudible
69
71
 
70
72
  # Run a different command
@@ -74,16 +76,20 @@ claudible "python my_script.py"
74
76
  some-command 2>&1 | claudible --pipe
75
77
 
76
78
  # Choose a sound character
77
- claudible --character crystal
79
+ claudible --character drift
80
+
81
+ # Use the percussive material sound set
82
+ claudible --set material -c bell
78
83
 
79
84
  # Adjust volume
80
85
  claudible --volume 0.3
81
86
 
82
- # List available characters
87
+ # List available characters across all sets
83
88
  claudible --list-characters
84
89
 
85
- # Demo all sound characters
90
+ # Demo all sound characters in a set
86
91
  claudible --demo
92
+ claudible --demo --set material
87
93
  ```
88
94
 
89
95
  ### 🔇 Reverse Mode
@@ -113,7 +119,26 @@ npm run dev 2>&1 | claudible --pipe
113
119
  tail -f /var/log/app.log | claudible --pipe -c droplet
114
120
  ```
115
121
 
116
- ## 🎻 Sound Characters
122
+ ## 🎻 Sound Sets
123
+
124
+ Claudible ships with two sound sets. The **ambient** set (default) produces soft, throbbing textures with rich overtones. The **material** set has crisp, percussive sounds modelled on physical materials.
125
+
126
+ ### Ambient (default) `--set ambient`
127
+
128
+ | Character | | Description |
129
+ |-----------|---|-------------|
130
+ | `drift` | 🌊 | Slow undulating low throb, gentle beating pairs |
131
+ | `tide` | 🌀 | Oceanic wash, phase interference, wide and enveloping |
132
+ | `breath` | 💨 | Soft exhale texture, breathy warmth with filtered noise |
133
+ | `haze` | 🌫️ | Dense foggy overtones, warm and thick with close partial clusters |
134
+ | `pulse` | 💗 | Gentle rhythmic throbbing from detuned pairs, hypnotic |
135
+ | `glow` | 🕯️ | Warm radiant harmonics, rich natural overtone series |
136
+ | `cloud` | ☁️ | Diffuse and soft, massive reverb space, floating |
137
+ | `murmur` | 🫧 | Low gentle rumble, warm harmonic murmur with subtle throb |
138
+ | `shimmer` | ✨ | High ethereal overtones, floating and luminous |
139
+ | `deep` | 🎚️ | Sub-bass throb, felt more than heard, very deep and slow |
140
+
141
+ ### Material `--set material`
117
142
 
118
143
  | Character | | Description |
119
144
  |-----------|---|-------------|
@@ -137,12 +162,13 @@ tail -f /var/log/app.log | claudible --pipe -c droplet
137
162
  | Flag | Description |
138
163
  |------|-------------|
139
164
  | `--pipe` | 📥 Read from stdin instead of wrapping |
140
- | `--character`, `-c` | 🎵 Sound character |
165
+ | `--set`, `-s` | 🎼 Sound set: `ambient` (default), `material` |
166
+ | `--character`, `-c` | 🎵 Sound character within the set |
141
167
  | `--volume`, `-v` | 🔊 Volume 0.0–1.0 (default: 0.5) |
142
168
  | `--attention`, `-a` | ⏰ Silence alert seconds (default: 30) |
143
169
  | `--reverse`, `-r` | 🔄 Reverse mode: sound during silence, quiet during output |
144
- | `--list-characters` | 📋 Show presets |
145
- | `--demo` | 🔊 Play a short demo of each sound character |
170
+ | `--list-characters` | 📋 Show all characters across all sets |
171
+ | `--demo` | 🔊 Demo characters in the selected set |
146
172
 
147
173
  ## 🛠️ Development
148
174
 
@@ -15,7 +15,9 @@
15
15
 
16
16
  *An opus for your terminals.*
17
17
 
18
- Possibly the most annoying Claude utility ever made but here it is anyway. Ambient audio soundscape feedback for terminal output.
18
+ Possibly the most annoying Claude utility ever made but here it is anyway.
19
+
20
+ Ambient audio soundscape feedback for terminal output. It really shines when used with a lot of busy busy Claude Code terminals running at the same time.
19
21
 
20
22
  ## 🎼 The Idea
21
23
 
@@ -39,7 +41,7 @@ pip install claudible
39
41
  ## 🎹 Usage
40
42
 
41
43
  ```bash
42
- # Run claude with audio feedback (default)
44
+ # Run claude with audio feedback (default: ambient sound set)
43
45
  claudible
44
46
 
45
47
  # Run a different command
@@ -49,16 +51,20 @@ claudible "python my_script.py"
49
51
  some-command 2>&1 | claudible --pipe
50
52
 
51
53
  # Choose a sound character
52
- claudible --character crystal
54
+ claudible --character drift
55
+
56
+ # Use the percussive material sound set
57
+ claudible --set material -c bell
53
58
 
54
59
  # Adjust volume
55
60
  claudible --volume 0.3
56
61
 
57
- # List available characters
62
+ # List available characters across all sets
58
63
  claudible --list-characters
59
64
 
60
- # Demo all sound characters
65
+ # Demo all sound characters in a set
61
66
  claudible --demo
67
+ claudible --demo --set material
62
68
  ```
63
69
 
64
70
  ### 🔇 Reverse Mode
@@ -88,7 +94,26 @@ npm run dev 2>&1 | claudible --pipe
88
94
  tail -f /var/log/app.log | claudible --pipe -c droplet
89
95
  ```
90
96
 
91
- ## 🎻 Sound Characters
97
+ ## 🎻 Sound Sets
98
+
99
+ Claudible ships with two sound sets. The **ambient** set (default) produces soft, throbbing textures with rich overtones. The **material** set has crisp, percussive sounds modelled on physical materials.
100
+
101
+ ### Ambient (default) `--set ambient`
102
+
103
+ | Character | | Description |
104
+ |-----------|---|-------------|
105
+ | `drift` | 🌊 | Slow undulating low throb, gentle beating pairs |
106
+ | `tide` | 🌀 | Oceanic wash, phase interference, wide and enveloping |
107
+ | `breath` | 💨 | Soft exhale texture, breathy warmth with filtered noise |
108
+ | `haze` | 🌫️ | Dense foggy overtones, warm and thick with close partial clusters |
109
+ | `pulse` | 💗 | Gentle rhythmic throbbing from detuned pairs, hypnotic |
110
+ | `glow` | 🕯️ | Warm radiant harmonics, rich natural overtone series |
111
+ | `cloud` | ☁️ | Diffuse and soft, massive reverb space, floating |
112
+ | `murmur` | 🫧 | Low gentle rumble, warm harmonic murmur with subtle throb |
113
+ | `shimmer` | ✨ | High ethereal overtones, floating and luminous |
114
+ | `deep` | 🎚️ | Sub-bass throb, felt more than heard, very deep and slow |
115
+
116
+ ### Material `--set material`
92
117
 
93
118
  | Character | | Description |
94
119
  |-----------|---|-------------|
@@ -112,12 +137,13 @@ tail -f /var/log/app.log | claudible --pipe -c droplet
112
137
  | Flag | Description |
113
138
  |------|-------------|
114
139
  | `--pipe` | 📥 Read from stdin instead of wrapping |
115
- | `--character`, `-c` | 🎵 Sound character |
140
+ | `--set`, `-s` | 🎼 Sound set: `ambient` (default), `material` |
141
+ | `--character`, `-c` | 🎵 Sound character within the set |
116
142
  | `--volume`, `-v` | 🔊 Volume 0.0–1.0 (default: 0.5) |
117
143
  | `--attention`, `-a` | ⏰ Silence alert seconds (default: 30) |
118
144
  | `--reverse`, `-r` | 🔄 Reverse mode: sound during silence, quiet during output |
119
- | `--list-characters` | 📋 Show presets |
120
- | `--demo` | 🔊 Play a short demo of each sound character |
145
+ | `--list-characters` | 📋 Show all characters across all sets |
146
+ | `--demo` | 🔊 Demo characters in the selected set |
121
147
 
122
148
  ## 🛠️ Development
123
149
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claudible"
7
- version = "0.1.3"
8
- description = "Ambient audio soundscape feedback for terminal output - an opus for your terminals"
7
+ version = "0.1.5"
8
+ description = "Ambient audio soundscape feedback for terminal output - an opus for your terminals, claude code by default"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  requires-python = ">=3.8"
@@ -26,6 +26,7 @@ class SoundEngine:
26
26
  self._stream: Optional[sd.OutputStream] = None
27
27
  self._running = False
28
28
  self._hum_phase = 0.0
29
+ self._last_pitch = 1.0 # portamento tracking
29
30
  self._lock = threading.Lock()
30
31
 
31
32
  # Ring buffer for mixing audio
@@ -142,11 +143,11 @@ class SoundEngine:
142
143
  # --- Sound generation ---
143
144
 
144
145
  def _build_grain_cache(self) -> list:
145
- """Build grain cache with voices at full-octave intervals, biased low + high."""
146
+ """Build grain cache with voices at full-octave intervals, filtered by material range."""
146
147
  # Each register is a full octave apart.
147
148
  # (octave_shift, noise_mult, decay_mult, duration_mult)
148
149
  # Multiple character variations per register.
149
- voices = [
150
+ all_voices = [
150
151
  # --- oct -3: sub rumble (3 voices) ---
151
152
  (-3, 3.5, 2.5, 1.8), # sub thump
152
153
  (-3, 2.0, 3.0, 2.0), # sub knock
@@ -181,6 +182,8 @@ class SoundEngine:
181
182
  ( 3, 0.3, 0.2, 0.45), # air tick
182
183
  ( 3, 0.05, 0.3, 0.35), # air wisp
183
184
  ]
185
+ oct_min, oct_max = self.material.octave_range
186
+ voices = [v for v in all_voices if oct_min <= v[0] <= oct_max]
184
187
  cache = []
185
188
  for octave, noise_m, decay_m, dur_m in voices:
186
189
  cache.append(self._generate_grain(
@@ -269,6 +272,16 @@ class SoundEngine:
269
272
  h = ((h * 33) ^ ord(c)) & 0xFFFFFFFF
270
273
  return h
271
274
 
275
+ # Minor pentatonic scale degrees in semitones (wraps at octave)
276
+ _SCALE_DEGREES = [0, 3, 5, 7, 10]
277
+
278
+ def _snap_to_scale(self, semitones: float) -> float:
279
+ """Quantise a semitone offset to the nearest scale degree."""
280
+ octave = int(semitones // 12)
281
+ remainder = semitones % 12
282
+ nearest = min(self._SCALE_DEGREES, key=lambda d: abs(d - remainder))
283
+ return octave * 12 + nearest
284
+
272
285
  def play_grain(self, token: str = ""):
273
286
  """Play a grain/sparkle sound, deterministic for a given token."""
274
287
  if token:
@@ -280,16 +293,30 @@ class SoundEngine:
280
293
  idx = rng.randint(len(self._grain_cache))
281
294
  grain = self._grain_cache[idx].copy()
282
295
 
283
- # Narrow pitch micro-variation (±1.5 semitones) stays in its register
284
- pitch_shift = 2 ** (rng.uniform(-1.5, 1.5) / 12)
285
- if abs(pitch_shift - 1.0) > 0.001:
286
- new_len = int(len(grain) / pitch_shift)
287
- if new_len > 0:
288
- grain = np.interp(
289
- np.linspace(0, len(grain) - 1, new_len),
290
- np.arange(len(grain)),
291
- grain,
292
- ).astype(np.float32)
296
+ # Pitch variation snapped to minor pentatonic scale
297
+ raw_semi = rng.uniform(-1.5, 1.5)
298
+ snapped_semi = self._snap_to_scale(raw_semi)
299
+ pitch_shift = 2 ** (snapped_semi / 12)
300
+
301
+ # Fast portamento: slide from last pitch to new target
302
+ prev_pitch = self._last_pitch
303
+ self._last_pitch = pitch_shift
304
+
305
+ n = len(grain)
306
+ if n > 0 and (abs(pitch_shift - 1.0) > 0.001 or abs(prev_pitch - 1.0) > 0.001):
307
+ # Glide over first 30% of grain, then hold target
308
+ glide_len = int(n * 0.3)
309
+ pitch_env = np.ones(n, dtype=np.float32)
310
+ if glide_len > 0:
311
+ pitch_env[:glide_len] = np.linspace(
312
+ prev_pitch, pitch_shift, glide_len, dtype=np.float32,
313
+ )
314
+ pitch_env[glide_len:] = pitch_shift
315
+
316
+ # Time-varying resample using cumulative phase
317
+ phase = np.cumsum(pitch_env)
318
+ phase = phase / phase[-1] * (n - 1)
319
+ grain = np.interp(phase, np.arange(n), grain).astype(np.float32)
293
320
 
294
321
  grain *= rng.uniform(0.7, 1.0)
295
322
 
@@ -10,14 +10,18 @@ import time
10
10
  from typing import Optional
11
11
 
12
12
  from .audio import SoundEngine
13
- from .materials import get_material, get_random_material, list_materials, MATERIALS
13
+ from .materials import (
14
+ get_material, get_random_material, list_materials, list_sound_sets,
15
+ get_sound_set, SOUND_SETS, DEFAULT_SOUND_SET,
16
+ )
14
17
  from .monitor import ActivityMonitor
15
18
 
16
19
 
17
- def run_demo(volume: float):
18
- """Play a short demo of every sound character."""
19
- print("Claudible character demo\n", file=sys.stderr)
20
- for name, mat in MATERIALS.items():
20
+ def run_demo(volume: float, sound_set: str = DEFAULT_SOUND_SET):
21
+ """Play a short demo of every sound character in a set."""
22
+ materials = get_sound_set(sound_set)
23
+ print(f"Claudible character demo [{sound_set}]\n", file=sys.stderr)
24
+ for name, mat in materials.items():
21
25
  print(f" {name:10} - {mat.description}", file=sys.stderr)
22
26
  engine = SoundEngine(material=mat, volume=volume)
23
27
  engine.start()
@@ -129,10 +133,15 @@ def main():
129
133
  action='store_true',
130
134
  help='Pipe mode: read from stdin instead of wrapping a command',
131
135
  )
136
+ parser.add_argument(
137
+ '--set', '-s',
138
+ choices=list_sound_sets(),
139
+ default=DEFAULT_SOUND_SET,
140
+ help=f'Sound set (default: {DEFAULT_SOUND_SET})',
141
+ )
132
142
  parser.add_argument(
133
143
  '--character', '-c',
134
- choices=list_materials(),
135
- help='Sound character (default: random)',
144
+ help='Sound character (default: random). Use --list-characters to see options.',
136
145
  )
137
146
  parser.add_argument(
138
147
  '--volume', '-v',
@@ -164,21 +173,33 @@ def main():
164
173
 
165
174
  args = parser.parse_args()
166
175
 
176
+ sound_set = args.set
177
+
167
178
  if args.list_characters:
168
- print("Available sound characters:\n")
169
- for name, mat in MATERIALS.items():
170
- print(f" {name:10} - {mat.description}")
179
+ for set_name, materials in SOUND_SETS.items():
180
+ default_tag = " (default)" if set_name == DEFAULT_SOUND_SET else ""
181
+ print(f"\n [{set_name}]{default_tag}\n")
182
+ for name, mat in materials.items():
183
+ print(f" {name:10} - {mat.description}")
184
+ print()
171
185
  return
172
186
 
173
187
  if args.demo:
174
188
  volume = max(0.0, min(1.0, args.volume))
175
- run_demo(volume)
189
+ run_demo(volume, sound_set)
176
190
  return
177
191
 
192
+ # Validate character against the chosen set
178
193
  if args.character:
179
- material = get_material(args.character)
194
+ available = list_materials(sound_set)
195
+ if args.character not in available:
196
+ parser.error(
197
+ f"character '{args.character}' not in set '{sound_set}'. "
198
+ f"Available: {', '.join(available)}"
199
+ )
200
+ material = get_material(args.character, sound_set)
180
201
  else:
181
- material = get_random_material()
202
+ material = get_random_material(sound_set)
182
203
 
183
204
  volume = max(0.0, min(1.0, args.volume))
184
205