claudible 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
- claudible/audio.py +39 -12
- claudible/cli.py +34 -13
- claudible/materials.py +418 -13
- {claudible-0.1.3.dist-info → claudible-0.1.5.dist-info}/METADATA +37 -11
- claudible-0.1.5.dist-info/RECORD +11 -0
- claudible-0.1.3.dist-info/RECORD +0 -11
- {claudible-0.1.3.dist-info → claudible-0.1.5.dist-info}/WHEEL +0 -0
- {claudible-0.1.3.dist-info → claudible-0.1.5.dist-info}/entry_points.txt +0 -0
- {claudible-0.1.3.dist-info → claudible-0.1.5.dist-info}/top_level.txt +0 -0
claudible/audio.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
claudible/cli.py
CHANGED
|
@@ -10,14 +10,18 @@ import time
|
|
|
10
10
|
from typing import Optional
|
|
11
11
|
|
|
12
12
|
from .audio import SoundEngine
|
|
13
|
-
from .materials import
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
print(f" {
|
|
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
|
-
|
|
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
|
|
claudible/materials.py
CHANGED
|
@@ -27,12 +27,380 @@ class Material:
|
|
|
27
27
|
attack_ms: float # attack ramp time in milliseconds
|
|
28
28
|
freq_spread: float # semitones of random base freq variation per grain
|
|
29
29
|
volume: float # per-material volume scaling (0-1)
|
|
30
|
+
octave_range: Tuple[int, int] # (min, max) octave shifts for grain cache
|
|
30
31
|
description: str
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
# --- Original materials (upgraded with physics parameters) ---
|
|
34
|
+
DEFAULT_SOUND_SET = "ambient"
|
|
35
35
|
|
|
36
|
+
# --- Ambient sound set (default) ---
|
|
37
|
+
# Throbbing, textured, overtone-rich with percussive attack.
|
|
38
|
+
# Close partial pairs create beating/throbbing. High attack noise for punch.
|
|
39
|
+
# Kept low — nothing shrill. Dense, gritty texture.
|
|
40
|
+
|
|
41
|
+
AMBIENT_MATERIALS = {
|
|
42
|
+
"drift": Material(
|
|
43
|
+
name="drift",
|
|
44
|
+
base_freq=95,
|
|
45
|
+
partials=[
|
|
46
|
+
(1.0, 1.0), (1.005, 0.95), # beating pair ~0.48Hz throb
|
|
47
|
+
(2.0, 0.5), (2.008, 0.45), # octave beating pair
|
|
48
|
+
(3.01, 0.2), (4.02, 0.1),
|
|
49
|
+
],
|
|
50
|
+
decay_rates=[3, 3.2, 5, 5.3, 8, 11],
|
|
51
|
+
grain_duration=0.16,
|
|
52
|
+
attack_noise=0.35,
|
|
53
|
+
noise_freq=600,
|
|
54
|
+
detune_amount=12,
|
|
55
|
+
reverb_amount=0.6,
|
|
56
|
+
reverb_damping=0.55,
|
|
57
|
+
room_size=1.8,
|
|
58
|
+
pitch_drop=0.997,
|
|
59
|
+
attack_ms=1.5,
|
|
60
|
+
freq_spread=3.0,
|
|
61
|
+
volume=0.7,
|
|
62
|
+
octave_range=(-3, 0),
|
|
63
|
+
description="Low undulating throb, punchy attack into beating pairs",
|
|
64
|
+
),
|
|
65
|
+
|
|
66
|
+
"tide": Material(
|
|
67
|
+
name="tide",
|
|
68
|
+
base_freq=120,
|
|
69
|
+
partials=[
|
|
70
|
+
(1.0, 1.0), (1.003, 0.92), # slow beat
|
|
71
|
+
(1.498, 0.55), (1.502, 0.52), # fifth beating pair
|
|
72
|
+
(2.0, 0.35), (3.0, 0.12),
|
|
73
|
+
],
|
|
74
|
+
decay_rates=[4, 4.1, 6, 6.2, 8, 12],
|
|
75
|
+
grain_duration=0.18,
|
|
76
|
+
attack_noise=0.3,
|
|
77
|
+
noise_freq=500,
|
|
78
|
+
detune_amount=14,
|
|
79
|
+
reverb_amount=0.7,
|
|
80
|
+
reverb_damping=0.5,
|
|
81
|
+
room_size=2.0,
|
|
82
|
+
pitch_drop=0.996,
|
|
83
|
+
attack_ms=1.0,
|
|
84
|
+
freq_spread=4.0,
|
|
85
|
+
volume=0.65,
|
|
86
|
+
octave_range=(-3, 0),
|
|
87
|
+
description="Oceanic wash with percussive bite, wide phase interference",
|
|
88
|
+
),
|
|
89
|
+
|
|
90
|
+
"breath": Material(
|
|
91
|
+
name="breath",
|
|
92
|
+
base_freq=160,
|
|
93
|
+
partials=[
|
|
94
|
+
(1.0, 1.0), (1.006, 0.85), # beating pair
|
|
95
|
+
(2.01, 0.4), (3.02, 0.15),
|
|
96
|
+
],
|
|
97
|
+
decay_rates=[5, 5.2, 8, 12],
|
|
98
|
+
grain_duration=0.14,
|
|
99
|
+
attack_noise=0.4,
|
|
100
|
+
noise_freq=900,
|
|
101
|
+
detune_amount=18,
|
|
102
|
+
reverb_amount=0.55,
|
|
103
|
+
reverb_damping=0.6,
|
|
104
|
+
room_size=1.6,
|
|
105
|
+
pitch_drop=0.998,
|
|
106
|
+
attack_ms=1.2,
|
|
107
|
+
freq_spread=5.0,
|
|
108
|
+
volume=0.6,
|
|
109
|
+
octave_range=(-3, 0),
|
|
110
|
+
description="Gritty exhale, noisy attack decaying into warm throb",
|
|
111
|
+
),
|
|
112
|
+
|
|
113
|
+
"haze": Material(
|
|
114
|
+
name="haze",
|
|
115
|
+
base_freq=110,
|
|
116
|
+
partials=[
|
|
117
|
+
(1.0, 1.0), (1.004, 0.9), # tight beating
|
|
118
|
+
(2.003, 0.6), (2.007, 0.55), # octave beating
|
|
119
|
+
(3.005, 0.3), (4.01, 0.15),
|
|
120
|
+
(5.02, 0.06),
|
|
121
|
+
],
|
|
122
|
+
decay_rates=[3.5, 3.7, 5, 5.2, 7, 10, 14],
|
|
123
|
+
grain_duration=0.15,
|
|
124
|
+
attack_noise=0.32,
|
|
125
|
+
noise_freq=550,
|
|
126
|
+
detune_amount=10,
|
|
127
|
+
reverb_amount=0.65,
|
|
128
|
+
reverb_damping=0.6,
|
|
129
|
+
room_size=1.9,
|
|
130
|
+
pitch_drop=0.997,
|
|
131
|
+
attack_ms=1.0,
|
|
132
|
+
freq_spread=3.5,
|
|
133
|
+
volume=0.65,
|
|
134
|
+
octave_range=(-3, 0),
|
|
135
|
+
description="Dense foggy cluster, percussive onset into thick overtone haze",
|
|
136
|
+
),
|
|
137
|
+
|
|
138
|
+
"pulse": Material(
|
|
139
|
+
name="pulse",
|
|
140
|
+
base_freq=80,
|
|
141
|
+
partials=[
|
|
142
|
+
(1.0, 1.0), (1.008, 0.9), # ~0.64Hz beating = pulsing
|
|
143
|
+
(2.0, 0.55), (2.012, 0.5), # faster beat in octave
|
|
144
|
+
(3.0, 0.25),
|
|
145
|
+
],
|
|
146
|
+
decay_rates=[2.5, 2.6, 4, 4.2, 7],
|
|
147
|
+
grain_duration=0.2,
|
|
148
|
+
attack_noise=0.38,
|
|
149
|
+
noise_freq=400,
|
|
150
|
+
detune_amount=8,
|
|
151
|
+
reverb_amount=0.5,
|
|
152
|
+
reverb_damping=0.65,
|
|
153
|
+
room_size=1.5,
|
|
154
|
+
pitch_drop=0.995,
|
|
155
|
+
attack_ms=0.8,
|
|
156
|
+
freq_spread=2.5,
|
|
157
|
+
volume=0.7,
|
|
158
|
+
octave_range=(-3, 0),
|
|
159
|
+
description="Thumpy rhythmic throb, hard attack into hypnotic beating",
|
|
160
|
+
),
|
|
161
|
+
|
|
162
|
+
"glow": Material(
|
|
163
|
+
name="glow",
|
|
164
|
+
base_freq=145,
|
|
165
|
+
partials=[
|
|
166
|
+
(1.0, 1.0), (1.003, 0.88), # beating pair
|
|
167
|
+
(2.0, 0.6), (3.0, 0.35),
|
|
168
|
+
(4.0, 0.2), (5.0, 0.1),
|
|
169
|
+
],
|
|
170
|
+
decay_rates=[4, 4.2, 5, 6.5, 8, 11],
|
|
171
|
+
grain_duration=0.13,
|
|
172
|
+
attack_noise=0.28,
|
|
173
|
+
noise_freq=700,
|
|
174
|
+
detune_amount=15,
|
|
175
|
+
reverb_amount=0.55,
|
|
176
|
+
reverb_damping=0.45,
|
|
177
|
+
room_size=1.7,
|
|
178
|
+
pitch_drop=0.998,
|
|
179
|
+
attack_ms=1.5,
|
|
180
|
+
freq_spread=4.0,
|
|
181
|
+
volume=0.65,
|
|
182
|
+
octave_range=(-3, 0),
|
|
183
|
+
description="Warm radiant harmonics, punchy onset with rich overtone bloom",
|
|
184
|
+
),
|
|
185
|
+
|
|
186
|
+
"cloud": Material(
|
|
187
|
+
name="cloud",
|
|
188
|
+
base_freq=190,
|
|
189
|
+
partials=[
|
|
190
|
+
(1.0, 1.0), (1.002, 0.85),
|
|
191
|
+
(1.5, 0.4), (2.0, 0.25),
|
|
192
|
+
(2.5, 0.1),
|
|
193
|
+
],
|
|
194
|
+
decay_rates=[5, 5.2, 7, 9, 13],
|
|
195
|
+
grain_duration=0.14,
|
|
196
|
+
attack_noise=0.3,
|
|
197
|
+
noise_freq=800,
|
|
198
|
+
detune_amount=16,
|
|
199
|
+
reverb_amount=0.75,
|
|
200
|
+
reverb_damping=0.4,
|
|
201
|
+
room_size=2.2,
|
|
202
|
+
pitch_drop=0.998,
|
|
203
|
+
attack_ms=1.2,
|
|
204
|
+
freq_spread=5.0,
|
|
205
|
+
volume=0.55,
|
|
206
|
+
octave_range=(-3, 0),
|
|
207
|
+
description="Percussive puff into massive diffuse reverb, textured",
|
|
208
|
+
),
|
|
209
|
+
|
|
210
|
+
"murmur": Material(
|
|
211
|
+
name="murmur",
|
|
212
|
+
base_freq=70,
|
|
213
|
+
partials=[
|
|
214
|
+
(1.0, 1.0), (1.006, 0.88), # low beating
|
|
215
|
+
(2.0, 0.5), (3.0, 0.25),
|
|
216
|
+
(4.01, 0.1),
|
|
217
|
+
],
|
|
218
|
+
decay_rates=[3, 3.2, 5, 8, 12],
|
|
219
|
+
grain_duration=0.17,
|
|
220
|
+
attack_noise=0.35,
|
|
221
|
+
noise_freq=350,
|
|
222
|
+
detune_amount=12,
|
|
223
|
+
reverb_amount=0.5,
|
|
224
|
+
reverb_damping=0.7,
|
|
225
|
+
room_size=1.4,
|
|
226
|
+
pitch_drop=0.994,
|
|
227
|
+
attack_ms=0.8,
|
|
228
|
+
freq_spread=3.0,
|
|
229
|
+
volume=0.7,
|
|
230
|
+
octave_range=(-3, 0),
|
|
231
|
+
description="Deep rumble thump, heavy attack into warm beating murmur",
|
|
232
|
+
),
|
|
233
|
+
|
|
234
|
+
"shimmer": Material(
|
|
235
|
+
name="shimmer",
|
|
236
|
+
base_freq=260,
|
|
237
|
+
partials=[
|
|
238
|
+
(1.0, 1.0), (1.002, 0.9), # slow beating
|
|
239
|
+
(2.001, 0.55), (2.003, 0.52), # octave beating
|
|
240
|
+
(3.0, 0.25), (4.0, 0.1),
|
|
241
|
+
],
|
|
242
|
+
decay_rates=[5, 5.1, 7, 7.2, 10, 14],
|
|
243
|
+
grain_duration=0.11,
|
|
244
|
+
attack_noise=0.25,
|
|
245
|
+
noise_freq=1200,
|
|
246
|
+
detune_amount=10,
|
|
247
|
+
reverb_amount=0.65,
|
|
248
|
+
reverb_damping=0.3,
|
|
249
|
+
room_size=2.0,
|
|
250
|
+
pitch_drop=0.999,
|
|
251
|
+
attack_ms=1.0,
|
|
252
|
+
freq_spread=4.0,
|
|
253
|
+
volume=0.55,
|
|
254
|
+
octave_range=(-3, 0),
|
|
255
|
+
description="Bright-ish tap into shimmering beating overtones",
|
|
256
|
+
),
|
|
257
|
+
|
|
258
|
+
"deep": Material(
|
|
259
|
+
name="deep",
|
|
260
|
+
base_freq=50,
|
|
261
|
+
partials=[
|
|
262
|
+
(1.0, 1.0), (1.01, 0.85), # ~0.5Hz beating
|
|
263
|
+
(2.0, 0.55), (2.015, 0.5), # wider octave beat
|
|
264
|
+
(3.0, 0.2),
|
|
265
|
+
],
|
|
266
|
+
decay_rates=[2, 2.2, 3.5, 3.8, 6],
|
|
267
|
+
grain_duration=0.22,
|
|
268
|
+
attack_noise=0.4,
|
|
269
|
+
noise_freq=250,
|
|
270
|
+
detune_amount=8,
|
|
271
|
+
reverb_amount=0.45,
|
|
272
|
+
reverb_damping=0.8,
|
|
273
|
+
room_size=1.3,
|
|
274
|
+
pitch_drop=0.992,
|
|
275
|
+
attack_ms=0.6,
|
|
276
|
+
freq_spread=2.5,
|
|
277
|
+
volume=0.75,
|
|
278
|
+
octave_range=(-3, 0),
|
|
279
|
+
description="Sub-bass thump, hard hit into very deep slow throb",
|
|
280
|
+
),
|
|
281
|
+
|
|
282
|
+
# --- High register whispers (very quiet, above 3kHz) ---
|
|
283
|
+
|
|
284
|
+
"wisp": Material(
|
|
285
|
+
name="wisp",
|
|
286
|
+
base_freq=3200,
|
|
287
|
+
partials=[
|
|
288
|
+
(1.0, 1.0), (1.002, 0.9), # tight beating
|
|
289
|
+
(2.0, 0.2), (3.01, 0.05),
|
|
290
|
+
],
|
|
291
|
+
decay_rates=[12, 12.5, 20, 30],
|
|
292
|
+
grain_duration=0.06,
|
|
293
|
+
attack_noise=0.15,
|
|
294
|
+
noise_freq=5000,
|
|
295
|
+
detune_amount=4,
|
|
296
|
+
reverb_amount=0.7,
|
|
297
|
+
reverb_damping=0.2,
|
|
298
|
+
room_size=2.0,
|
|
299
|
+
pitch_drop=1.0,
|
|
300
|
+
attack_ms=2.0,
|
|
301
|
+
freq_spread=2.0,
|
|
302
|
+
volume=0.18,
|
|
303
|
+
octave_range=(-1, 1),
|
|
304
|
+
description="Faint crystalline wisp, barely there",
|
|
305
|
+
),
|
|
306
|
+
|
|
307
|
+
"glint": Material(
|
|
308
|
+
name="glint",
|
|
309
|
+
base_freq=3800,
|
|
310
|
+
partials=[
|
|
311
|
+
(1.0, 1.0), (1.5, 0.4), (2.01, 0.15),
|
|
312
|
+
],
|
|
313
|
+
decay_rates=[18, 25, 35],
|
|
314
|
+
grain_duration=0.04,
|
|
315
|
+
attack_noise=0.25,
|
|
316
|
+
noise_freq=6000,
|
|
317
|
+
detune_amount=6,
|
|
318
|
+
reverb_amount=0.65,
|
|
319
|
+
reverb_damping=0.15,
|
|
320
|
+
room_size=1.8,
|
|
321
|
+
pitch_drop=0.998,
|
|
322
|
+
attack_ms=0.8,
|
|
323
|
+
freq_spread=3.0,
|
|
324
|
+
volume=0.15,
|
|
325
|
+
octave_range=(-1, 0),
|
|
326
|
+
description="Tiny metallic glint, quiet and sharp",
|
|
327
|
+
),
|
|
328
|
+
|
|
329
|
+
"arc": Material(
|
|
330
|
+
name="arc",
|
|
331
|
+
base_freq=4200,
|
|
332
|
+
partials=[
|
|
333
|
+
(1.0, 1.0), (1.003, 0.85), # slow beating at high freq
|
|
334
|
+
(1.5, 0.3), (2.0, 0.1),
|
|
335
|
+
],
|
|
336
|
+
decay_rates=[10, 10.5, 16, 24],
|
|
337
|
+
grain_duration=0.07,
|
|
338
|
+
attack_noise=0.1,
|
|
339
|
+
noise_freq=7000,
|
|
340
|
+
detune_amount=3,
|
|
341
|
+
reverb_amount=0.75,
|
|
342
|
+
reverb_damping=0.1,
|
|
343
|
+
room_size=2.2,
|
|
344
|
+
pitch_drop=1.0,
|
|
345
|
+
attack_ms=3.0,
|
|
346
|
+
freq_spread=1.5,
|
|
347
|
+
volume=0.14,
|
|
348
|
+
octave_range=(-1, 0),
|
|
349
|
+
description="High glass arc, delicate beating shimmer",
|
|
350
|
+
),
|
|
351
|
+
|
|
352
|
+
"spark": Material(
|
|
353
|
+
name="spark",
|
|
354
|
+
base_freq=5000,
|
|
355
|
+
partials=[
|
|
356
|
+
(1.0, 1.0), (1.41, 0.35), (2.0, 0.08),
|
|
357
|
+
],
|
|
358
|
+
decay_rates=[22, 30, 45],
|
|
359
|
+
grain_duration=0.03,
|
|
360
|
+
attack_noise=0.3,
|
|
361
|
+
noise_freq=8000,
|
|
362
|
+
detune_amount=8,
|
|
363
|
+
reverb_amount=0.6,
|
|
364
|
+
reverb_damping=0.15,
|
|
365
|
+
room_size=1.6,
|
|
366
|
+
pitch_drop=0.996,
|
|
367
|
+
attack_ms=0.5,
|
|
368
|
+
freq_spread=4.0,
|
|
369
|
+
volume=0.12,
|
|
370
|
+
octave_range=(-1, 0),
|
|
371
|
+
description="Tiny electric spark, fast and faint",
|
|
372
|
+
),
|
|
373
|
+
|
|
374
|
+
"dust": Material(
|
|
375
|
+
name="dust",
|
|
376
|
+
base_freq=3500,
|
|
377
|
+
partials=[
|
|
378
|
+
(1.0, 1.0), (1.001, 0.95), # very tight beating
|
|
379
|
+
(2.003, 0.3), (2.006, 0.28), # octave beating pair
|
|
380
|
+
(3.0, 0.08),
|
|
381
|
+
],
|
|
382
|
+
decay_rates=[8, 8.2, 14, 14.5, 22],
|
|
383
|
+
grain_duration=0.08,
|
|
384
|
+
attack_noise=0.12,
|
|
385
|
+
noise_freq=5500,
|
|
386
|
+
detune_amount=5,
|
|
387
|
+
reverb_amount=0.8,
|
|
388
|
+
reverb_damping=0.1,
|
|
389
|
+
room_size=2.4,
|
|
390
|
+
pitch_drop=1.0,
|
|
391
|
+
attack_ms=4.0,
|
|
392
|
+
freq_spread=2.0,
|
|
393
|
+
volume=0.16,
|
|
394
|
+
octave_range=(-1, 0),
|
|
395
|
+
description="Soft high dust motes, floating reverb texture",
|
|
396
|
+
),
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# --- Material sound set (percussive, physical) ---
|
|
401
|
+
# The original set: crisp, percussive sounds modelled on physical materials.
|
|
402
|
+
|
|
403
|
+
MATERIAL_MATERIALS = {
|
|
36
404
|
"ice": Material(
|
|
37
405
|
name="ice",
|
|
38
406
|
base_freq=2800,
|
|
@@ -49,6 +417,7 @@ MATERIALS = {
|
|
|
49
417
|
attack_ms=0.5,
|
|
50
418
|
freq_spread=4.0,
|
|
51
419
|
volume=0.85,
|
|
420
|
+
octave_range=(-3, 3),
|
|
52
421
|
description="Brittle, very high, fast decay with pitch drop",
|
|
53
422
|
),
|
|
54
423
|
|
|
@@ -68,6 +437,7 @@ MATERIALS = {
|
|
|
68
437
|
attack_ms=0.8,
|
|
69
438
|
freq_spread=3.0,
|
|
70
439
|
volume=0.9,
|
|
440
|
+
octave_range=(-3, 3),
|
|
71
441
|
description="Classic wine glass ping",
|
|
72
442
|
),
|
|
73
443
|
|
|
@@ -87,6 +457,7 @@ MATERIALS = {
|
|
|
87
457
|
attack_ms=1.0,
|
|
88
458
|
freq_spread=2.5,
|
|
89
459
|
volume=0.85,
|
|
460
|
+
octave_range=(-3, 3),
|
|
90
461
|
description="Pure lead crystal with beating from close partial pairs",
|
|
91
462
|
),
|
|
92
463
|
|
|
@@ -106,6 +477,7 @@ MATERIALS = {
|
|
|
106
477
|
attack_ms=1.2,
|
|
107
478
|
freq_spread=3.5,
|
|
108
479
|
volume=1.0,
|
|
480
|
+
octave_range=(-3, 3),
|
|
109
481
|
description="Duller muted earthenware tap",
|
|
110
482
|
),
|
|
111
483
|
|
|
@@ -128,6 +500,7 @@ MATERIALS = {
|
|
|
128
500
|
attack_ms=0.3,
|
|
129
501
|
freq_spread=2.5,
|
|
130
502
|
volume=0.8,
|
|
503
|
+
octave_range=(-3, 3),
|
|
131
504
|
description="Small metallic bell, classic ratios, long ring",
|
|
132
505
|
),
|
|
133
506
|
|
|
@@ -147,6 +520,7 @@ MATERIALS = {
|
|
|
147
520
|
attack_ms=0.3,
|
|
148
521
|
freq_spread=4.0,
|
|
149
522
|
volume=0.8,
|
|
523
|
+
octave_range=(-3, 3),
|
|
150
524
|
description="Water droplet, pitch bend down, liquid",
|
|
151
525
|
),
|
|
152
526
|
|
|
@@ -166,6 +540,7 @@ MATERIALS = {
|
|
|
166
540
|
attack_ms=0.2,
|
|
167
541
|
freq_spread=6.0,
|
|
168
542
|
volume=0.95,
|
|
543
|
+
octave_range=(-3, 3),
|
|
169
544
|
description="Sharp mechanical click, keyboard-like",
|
|
170
545
|
),
|
|
171
546
|
|
|
@@ -190,6 +565,7 @@ MATERIALS = {
|
|
|
190
565
|
attack_ms=0.6,
|
|
191
566
|
freq_spread=4.0,
|
|
192
567
|
volume=1.0,
|
|
568
|
+
octave_range=(-3, 3),
|
|
193
569
|
description="Hollow wooden tap, warm marimba-like resonance",
|
|
194
570
|
),
|
|
195
571
|
|
|
@@ -212,6 +588,7 @@ MATERIALS = {
|
|
|
212
588
|
attack_ms=0.4,
|
|
213
589
|
freq_spread=5.0,
|
|
214
590
|
volume=1.0,
|
|
591
|
+
octave_range=(-3, 3),
|
|
215
592
|
description="Dense slate tap, heavy and earthy",
|
|
216
593
|
),
|
|
217
594
|
|
|
@@ -233,6 +610,7 @@ MATERIALS = {
|
|
|
233
610
|
attack_ms=1.0,
|
|
234
611
|
freq_spread=3.5,
|
|
235
612
|
volume=0.85,
|
|
613
|
+
octave_range=(-3, 3),
|
|
236
614
|
description="Hollow tube resonance, odd harmonics, breathy and airy",
|
|
237
615
|
),
|
|
238
616
|
|
|
@@ -254,6 +632,7 @@ MATERIALS = {
|
|
|
254
632
|
attack_ms=0.3,
|
|
255
633
|
freq_spread=8.0,
|
|
256
634
|
volume=0.9,
|
|
635
|
+
octave_range=(-3, 3),
|
|
257
636
|
description="Warm crackling ember, fire-like with wide pitch scatter",
|
|
258
637
|
),
|
|
259
638
|
|
|
@@ -273,6 +652,7 @@ MATERIALS = {
|
|
|
273
652
|
attack_ms=2.5,
|
|
274
653
|
freq_spread=5.0,
|
|
275
654
|
volume=0.6,
|
|
655
|
+
octave_range=(-3, 3),
|
|
276
656
|
description="Soft breathy whisper, delicate airy texture",
|
|
277
657
|
),
|
|
278
658
|
|
|
@@ -295,6 +675,7 @@ MATERIALS = {
|
|
|
295
675
|
attack_ms=1.5,
|
|
296
676
|
freq_spread=2.0,
|
|
297
677
|
volume=0.75,
|
|
678
|
+
octave_range=(-3, 3),
|
|
298
679
|
description="Swirly ocean interference, dense phase beating",
|
|
299
680
|
),
|
|
300
681
|
|
|
@@ -314,23 +695,47 @@ MATERIALS = {
|
|
|
314
695
|
attack_ms=3.0,
|
|
315
696
|
freq_spread=4.0,
|
|
316
697
|
volume=0.7,
|
|
698
|
+
octave_range=(-3, 3),
|
|
317
699
|
description="Ultra-soft muffled earth, mossy dampness",
|
|
318
700
|
),
|
|
319
701
|
}
|
|
320
702
|
|
|
321
703
|
|
|
322
|
-
|
|
323
|
-
""
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
704
|
+
SOUND_SETS = {
|
|
705
|
+
"ambient": AMBIENT_MATERIALS,
|
|
706
|
+
"material": MATERIAL_MATERIALS,
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
# Default MATERIALS points to the default set for backwards compat
|
|
710
|
+
MATERIALS = SOUND_SETS[DEFAULT_SOUND_SET]
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def list_sound_sets() -> List[str]:
|
|
714
|
+
"""List all available sound set names."""
|
|
715
|
+
return list(SOUND_SETS.keys())
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def get_sound_set(name: str) -> dict:
|
|
719
|
+
"""Get a sound set by name."""
|
|
720
|
+
if name not in SOUND_SETS:
|
|
721
|
+
raise ValueError(f"Unknown sound set: {name}. Available: {list(SOUND_SETS.keys())}")
|
|
722
|
+
return SOUND_SETS[name]
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def get_material(name: str, sound_set: str = DEFAULT_SOUND_SET) -> Material:
|
|
726
|
+
"""Get a material by name from a given sound set."""
|
|
727
|
+
materials = get_sound_set(sound_set)
|
|
728
|
+
if name not in materials:
|
|
729
|
+
raise ValueError(f"Unknown character: {name}. Available: {list(materials.keys())}")
|
|
730
|
+
return materials[name]
|
|
327
731
|
|
|
328
732
|
|
|
329
|
-
def get_random_material() -> Material:
|
|
330
|
-
"""Get a random material."""
|
|
331
|
-
|
|
733
|
+
def get_random_material(sound_set: str = DEFAULT_SOUND_SET) -> Material:
|
|
734
|
+
"""Get a random material from a given sound set."""
|
|
735
|
+
materials = get_sound_set(sound_set)
|
|
736
|
+
return random.choice(list(materials.values()))
|
|
332
737
|
|
|
333
738
|
|
|
334
|
-
def list_materials() -> List[str]:
|
|
335
|
-
"""List all available material names."""
|
|
336
|
-
return list(
|
|
739
|
+
def list_materials(sound_set: str = DEFAULT_SOUND_SET) -> List[str]:
|
|
740
|
+
"""List all available material names in a sound set."""
|
|
741
|
+
return list(get_sound_set(sound_set).keys())
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claudible
|
|
3
|
-
Version: 0.1.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
| `--
|
|
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
|
|
145
|
-
| `--demo` | 🔊
|
|
170
|
+
| `--list-characters` | 📋 Show all characters across all sets |
|
|
171
|
+
| `--demo` | 🔊 Demo characters in the selected set |
|
|
146
172
|
|
|
147
173
|
## 🛠️ Development
|
|
148
174
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
claudible/__init__.py,sha256=xSLL4yfc1p79BWVb65PJBTFMxyJ2PWuu7pCLYcCuOVM,96
|
|
2
|
+
claudible/__main__.py,sha256=5mFnPjtOjk5Hw-mvUhRYtwXVnXFR7ZbXKC27VIXu_dI,106
|
|
3
|
+
claudible/audio.py,sha256=8lHgp74Igh6Hs4HAbTZIRos-LVA0HCxJ71cTlyAlmFk,15945
|
|
4
|
+
claudible/cli.py,sha256=gLGaNDFVf4Bfr9UAH2yZUPeTUVxZ-5OR9DNjEwTRNsI,6549
|
|
5
|
+
claudible/materials.py,sha256=M49vT0oL63Iv1e8qxa2bsz3G6sQgPXwsJ3kAHMggX7g,21039
|
|
6
|
+
claudible/monitor.py,sha256=KAnb0rmnuTDanD7pgls50Oxd3-TlhXv2tUDjX8RkHIg,4106
|
|
7
|
+
claudible-0.1.5.dist-info/METADATA,sha256=PcTB4TJgiYVPqMB1xQH32-VIpZkkyghZ0Xr09yeO9ms,6736
|
|
8
|
+
claudible-0.1.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
+
claudible-0.1.5.dist-info/entry_points.txt,sha256=4tBIsIGcdtJ-1N9YZ3MrADyNvPBDcUxVgAnt9IuC7TA,49
|
|
10
|
+
claudible-0.1.5.dist-info/top_level.txt,sha256=4ZB16Aa5ZDS12bmxzrQmAJejjsTgQ9Yozwc0Z3qpJp4,10
|
|
11
|
+
claudible-0.1.5.dist-info/RECORD,,
|
claudible-0.1.3.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
claudible/__init__.py,sha256=xSLL4yfc1p79BWVb65PJBTFMxyJ2PWuu7pCLYcCuOVM,96
|
|
2
|
-
claudible/__main__.py,sha256=5mFnPjtOjk5Hw-mvUhRYtwXVnXFR7ZbXKC27VIXu_dI,106
|
|
3
|
-
claudible/audio.py,sha256=NXINffeLQlH89JJli-QF29WyYtpPl1BjcmfStvJ7geE,14741
|
|
4
|
-
claudible/cli.py,sha256=R7WttU-idl_S4yBx_bXKKRtftkdBS2_ioiNgZQ68a4w,5665
|
|
5
|
-
claudible/materials.py,sha256=6OsobcWOFvLBCuvzkeWi9C7fYHFnW9RED6nEElK-Ki4,9517
|
|
6
|
-
claudible/monitor.py,sha256=KAnb0rmnuTDanD7pgls50Oxd3-TlhXv2tUDjX8RkHIg,4106
|
|
7
|
-
claudible-0.1.3.dist-info/METADATA,sha256=SjWWWyDZIwGt1eQmcbQr_8UMCeCILUlS1fYb0mKrL10,5269
|
|
8
|
-
claudible-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
-
claudible-0.1.3.dist-info/entry_points.txt,sha256=4tBIsIGcdtJ-1N9YZ3MrADyNvPBDcUxVgAnt9IuC7TA,49
|
|
10
|
-
claudible-0.1.3.dist-info/top_level.txt,sha256=4ZB16Aa5ZDS12bmxzrQmAJejjsTgQ9Yozwc0Z3qpJp4,10
|
|
11
|
-
claudible-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|