cycdp 0.1.1__cp314-cp314-macosx_10_15_x86_64.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.
- cycdp/__init__.py +390 -0
- cycdp/_core.cpython-314-darwin.so +0 -0
- cycdp/_core.pyi +1049 -0
- cycdp/py.typed +0 -0
- cycdp-0.1.1.dist-info/METADATA +558 -0
- cycdp-0.1.1.dist-info/RECORD +8 -0
- cycdp-0.1.1.dist-info/WHEEL +6 -0
- cycdp-0.1.1.dist-info/licenses/LICENSE +504 -0
cycdp/_core.pyi
ADDED
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
"""Type stubs for cycdp._core Cython extension module."""
|
|
2
|
+
|
|
3
|
+
from typing import Sequence, overload
|
|
4
|
+
|
|
5
|
+
# Type alias for buffer-like objects (numpy arrays, array.array('f'), memoryview, etc.)
|
|
6
|
+
# In practice, any object supporting the buffer protocol with float32 data
|
|
7
|
+
from typing import Protocol, runtime_checkable
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class BufferLike(Protocol):
|
|
11
|
+
"""Protocol for objects supporting the buffer protocol with float32 data."""
|
|
12
|
+
def __buffer__(self, flags: int) -> memoryview: ...
|
|
13
|
+
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# Constants
|
|
16
|
+
# =============================================================================
|
|
17
|
+
|
|
18
|
+
FLAG_NONE: int
|
|
19
|
+
FLAG_CLIP: int
|
|
20
|
+
|
|
21
|
+
# Waveform types for synth_wave
|
|
22
|
+
WAVE_SINE: int
|
|
23
|
+
WAVE_SQUARE: int
|
|
24
|
+
WAVE_SAW: int
|
|
25
|
+
WAVE_RAMP: int
|
|
26
|
+
WAVE_TRIANGLE: int
|
|
27
|
+
|
|
28
|
+
# Scramble modes
|
|
29
|
+
SCRAMBLE_SHUFFLE: int
|
|
30
|
+
SCRAMBLE_REVERSE: int
|
|
31
|
+
SCRAMBLE_SIZE_UP: int
|
|
32
|
+
SCRAMBLE_SIZE_DOWN: int
|
|
33
|
+
SCRAMBLE_LEVEL_UP: int
|
|
34
|
+
SCRAMBLE_LEVEL_DOWN: int
|
|
35
|
+
|
|
36
|
+
# =============================================================================
|
|
37
|
+
# Exception
|
|
38
|
+
# =============================================================================
|
|
39
|
+
|
|
40
|
+
class CDPError(Exception):
|
|
41
|
+
"""Exception raised for CDP library errors."""
|
|
42
|
+
|
|
43
|
+
code: int
|
|
44
|
+
def __init__(self, code: int, message: str) -> None: ...
|
|
45
|
+
|
|
46
|
+
# =============================================================================
|
|
47
|
+
# Utility functions
|
|
48
|
+
# =============================================================================
|
|
49
|
+
|
|
50
|
+
def version() -> str:
|
|
51
|
+
"""Get CDP library version string."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def gain_to_db(gain: float) -> float:
|
|
55
|
+
"""Convert linear gain to decibels."""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def db_to_gain(db: float) -> float:
|
|
59
|
+
"""Convert decibels to linear gain."""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# Classes
|
|
64
|
+
# =============================================================================
|
|
65
|
+
|
|
66
|
+
class Context:
|
|
67
|
+
"""CDP processing context."""
|
|
68
|
+
def __init__(self) -> None: ...
|
|
69
|
+
def get_error(self) -> int:
|
|
70
|
+
"""Get last error code."""
|
|
71
|
+
...
|
|
72
|
+
def get_error_message(self) -> str:
|
|
73
|
+
"""Get last error message."""
|
|
74
|
+
...
|
|
75
|
+
def clear_error(self) -> None:
|
|
76
|
+
"""Clear error state."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
class Buffer:
|
|
80
|
+
"""CDP audio buffer with Python buffer protocol support."""
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def create(frame_count: int, channels: int = 1, sample_rate: int = 44100) -> Buffer:
|
|
84
|
+
"""Create a new buffer with allocated memory."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def from_memoryview(
|
|
89
|
+
samples: BufferLike, channels: int = 1, sample_rate: int = 44100
|
|
90
|
+
) -> Buffer:
|
|
91
|
+
"""Create a buffer by copying from a memoryview."""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def to_list(self) -> list[float]:
|
|
95
|
+
"""Convert buffer to Python list."""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def to_bytes(self) -> bytes:
|
|
99
|
+
"""Convert buffer to bytes (raw float32 data)."""
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def sample_count(self) -> int: ...
|
|
104
|
+
@property
|
|
105
|
+
def frame_count(self) -> int: ...
|
|
106
|
+
@property
|
|
107
|
+
def channels(self) -> int: ...
|
|
108
|
+
@property
|
|
109
|
+
def sample_rate(self) -> int: ...
|
|
110
|
+
def __len__(self) -> int: ...
|
|
111
|
+
def __getitem__(self, index: int) -> float: ...
|
|
112
|
+
def __setitem__(self, index: int, value: float) -> None: ...
|
|
113
|
+
def clear(self) -> None:
|
|
114
|
+
"""Set all samples to zero."""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
# =============================================================================
|
|
118
|
+
# Low-level functions (work with Buffer objects)
|
|
119
|
+
# =============================================================================
|
|
120
|
+
|
|
121
|
+
def apply_gain(ctx: Context, buf: Buffer, gain: float, clip: bool = False) -> None:
|
|
122
|
+
"""Apply gain to buffer (in-place)."""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def apply_gain_db(
|
|
126
|
+
ctx: Context, buf: Buffer, gain_db: float, clip: bool = False
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Apply gain in dB to buffer (in-place)."""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def apply_normalize(ctx: Context, buf: Buffer, target_level: float = 1.0) -> None:
|
|
132
|
+
"""Normalize buffer to target level (in-place)."""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
def apply_normalize_db(ctx: Context, buf: Buffer, target_db: float = 0.0) -> None:
|
|
136
|
+
"""Normalize buffer to target dB level (in-place)."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def apply_phase_invert(ctx: Context, buf: Buffer) -> None:
|
|
140
|
+
"""Invert phase of buffer (in-place)."""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def get_peak(ctx: Context, buf: Buffer) -> tuple[float, int]:
|
|
144
|
+
"""Find peak level in buffer. Returns (level, frame_position)."""
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
# =============================================================================
|
|
148
|
+
# High-level functions (accept any float32 buffer via memoryview)
|
|
149
|
+
# =============================================================================
|
|
150
|
+
|
|
151
|
+
def gain(
|
|
152
|
+
samples: BufferLike,
|
|
153
|
+
gain_factor: float = 1.0,
|
|
154
|
+
sample_rate: int = 44100,
|
|
155
|
+
clip: bool = False,
|
|
156
|
+
) -> Buffer:
|
|
157
|
+
"""Apply gain to audio samples."""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
def gain_db(
|
|
161
|
+
samples: BufferLike, db: float = 0.0, sample_rate: int = 44100, clip: bool = False
|
|
162
|
+
) -> Buffer:
|
|
163
|
+
"""Apply gain in decibels to audio samples."""
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
def normalize(
|
|
167
|
+
samples: BufferLike, target: float = 1.0, sample_rate: int = 44100
|
|
168
|
+
) -> Buffer:
|
|
169
|
+
"""Normalize audio to target peak level."""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
def normalize_db(
|
|
173
|
+
samples: BufferLike, target_db: float = 0.0, sample_rate: int = 44100
|
|
174
|
+
) -> Buffer:
|
|
175
|
+
"""Normalize audio to target peak level in dB."""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
@overload
|
|
179
|
+
def phase_invert(samples: BufferLike, sample_rate: int = 44100) -> Buffer:
|
|
180
|
+
"""Invert phase of audio samples (high-level)."""
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
@overload
|
|
184
|
+
def phase_invert(buf: Buffer) -> Buffer:
|
|
185
|
+
"""Invert phase of buffer (CDP lib version)."""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
def peak(samples: BufferLike, sample_rate: int = 44100) -> tuple[float, int]:
|
|
189
|
+
"""Find peak level in audio samples. Returns (peak_level, sample_position)."""
|
|
190
|
+
...
|
|
191
|
+
|
|
192
|
+
# =============================================================================
|
|
193
|
+
# File I/O
|
|
194
|
+
# =============================================================================
|
|
195
|
+
|
|
196
|
+
def read_file(path: str) -> Buffer:
|
|
197
|
+
"""Read an audio file into a Buffer."""
|
|
198
|
+
...
|
|
199
|
+
|
|
200
|
+
def write_file(path: str, buf: Buffer, format: str = "float") -> None:
|
|
201
|
+
"""Write a Buffer to an audio file."""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
# =============================================================================
|
|
205
|
+
# Spatial/panning
|
|
206
|
+
# =============================================================================
|
|
207
|
+
|
|
208
|
+
def pan(buf: Buffer, position: float = 0.0) -> Buffer:
|
|
209
|
+
"""Pan a mono buffer to stereo with a static pan position."""
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
def pan_envelope(buf: Buffer, points: list[tuple[float, float]]) -> Buffer:
|
|
213
|
+
"""Pan a mono buffer to stereo with time-varying position."""
|
|
214
|
+
...
|
|
215
|
+
|
|
216
|
+
def mirror(buf: Buffer) -> Buffer:
|
|
217
|
+
"""Mirror (swap) left and right channels of a stereo buffer."""
|
|
218
|
+
...
|
|
219
|
+
|
|
220
|
+
def narrow(buf: Buffer, width: float = 1.0) -> Buffer:
|
|
221
|
+
"""Narrow or widen stereo image."""
|
|
222
|
+
...
|
|
223
|
+
|
|
224
|
+
# =============================================================================
|
|
225
|
+
# Mixing
|
|
226
|
+
# =============================================================================
|
|
227
|
+
|
|
228
|
+
def mix2(a: Buffer, b: Buffer, gain_a: float = 1.0, gain_b: float = 1.0) -> Buffer:
|
|
229
|
+
"""Mix two buffers together with optional gains."""
|
|
230
|
+
...
|
|
231
|
+
|
|
232
|
+
def mix(buffers: list[Buffer], gains: list[float] | None = None) -> Buffer:
|
|
233
|
+
"""Mix multiple buffers together with optional gains."""
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
# =============================================================================
|
|
237
|
+
# Buffer utilities
|
|
238
|
+
# =============================================================================
|
|
239
|
+
|
|
240
|
+
def reverse(buf: Buffer) -> Buffer:
|
|
241
|
+
"""Reverse audio buffer."""
|
|
242
|
+
...
|
|
243
|
+
|
|
244
|
+
def fade_in(buf: Buffer, duration: float, curve: str = "linear") -> Buffer:
|
|
245
|
+
"""Apply fade in to buffer (in-place)."""
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
def fade_out(buf: Buffer, duration: float, curve: str = "linear") -> Buffer:
|
|
249
|
+
"""Apply fade out to buffer (in-place)."""
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
def concat(buffers: list[Buffer]) -> Buffer:
|
|
253
|
+
"""Concatenate multiple buffers into one."""
|
|
254
|
+
...
|
|
255
|
+
|
|
256
|
+
# =============================================================================
|
|
257
|
+
# Channel operations
|
|
258
|
+
# =============================================================================
|
|
259
|
+
|
|
260
|
+
def to_mono(buf: Buffer) -> Buffer:
|
|
261
|
+
"""Convert multi-channel buffer to mono by averaging all channels."""
|
|
262
|
+
...
|
|
263
|
+
|
|
264
|
+
def to_stereo(buf: Buffer) -> Buffer:
|
|
265
|
+
"""Convert mono buffer to stereo by duplicating the channel."""
|
|
266
|
+
...
|
|
267
|
+
|
|
268
|
+
def extract_channel(buf: Buffer, channel: int) -> Buffer:
|
|
269
|
+
"""Extract a single channel from a multi-channel buffer."""
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
def merge_channels(left: Buffer, right: Buffer) -> Buffer:
|
|
273
|
+
"""Merge two mono buffers into a stereo buffer."""
|
|
274
|
+
...
|
|
275
|
+
|
|
276
|
+
def split_channels(buf: Buffer) -> list[Buffer]:
|
|
277
|
+
"""Split a multi-channel buffer into separate mono buffers."""
|
|
278
|
+
...
|
|
279
|
+
|
|
280
|
+
def interleave(buffers: list[Buffer]) -> Buffer:
|
|
281
|
+
"""Interleave multiple mono buffers into a multi-channel buffer."""
|
|
282
|
+
...
|
|
283
|
+
|
|
284
|
+
# =============================================================================
|
|
285
|
+
# Spectral processing
|
|
286
|
+
# =============================================================================
|
|
287
|
+
|
|
288
|
+
def time_stretch(
|
|
289
|
+
buf: Buffer, factor: float, fft_size: int = 1024, overlap: int = 3
|
|
290
|
+
) -> Buffer:
|
|
291
|
+
"""Time-stretch audio without changing pitch."""
|
|
292
|
+
...
|
|
293
|
+
|
|
294
|
+
def spectral_blur(buf: Buffer, blur_time: float, fft_size: int = 1024) -> Buffer:
|
|
295
|
+
"""Blur/smear the spectrum over time."""
|
|
296
|
+
...
|
|
297
|
+
|
|
298
|
+
def modify_speed(buf: Buffer, speed_factor: float) -> Buffer:
|
|
299
|
+
"""Change playback speed (affects both time and pitch)."""
|
|
300
|
+
...
|
|
301
|
+
|
|
302
|
+
def pitch_shift(buf: Buffer, semitones: float, fft_size: int = 1024) -> Buffer:
|
|
303
|
+
"""Shift pitch without changing duration."""
|
|
304
|
+
...
|
|
305
|
+
|
|
306
|
+
def spectral_shift(buf: Buffer, shift_hz: float, fft_size: int = 1024) -> Buffer:
|
|
307
|
+
"""Shift spectrum up or down by a fixed frequency."""
|
|
308
|
+
...
|
|
309
|
+
|
|
310
|
+
def spectral_stretch(
|
|
311
|
+
buf: Buffer, max_stretch: float, fft_size: int = 1024, mode: int = 0
|
|
312
|
+
) -> Buffer:
|
|
313
|
+
"""Stretch or compress the spectrum."""
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
def filter_lowpass(
|
|
317
|
+
buf: Buffer, cutoff_freq: float, fft_size: int = 1024, rolloff: float = 1.0
|
|
318
|
+
) -> Buffer:
|
|
319
|
+
"""Apply low-pass filter."""
|
|
320
|
+
...
|
|
321
|
+
|
|
322
|
+
def filter_highpass(
|
|
323
|
+
buf: Buffer, cutoff_freq: float, fft_size: int = 1024, rolloff: float = 1.0
|
|
324
|
+
) -> Buffer:
|
|
325
|
+
"""Apply high-pass filter."""
|
|
326
|
+
...
|
|
327
|
+
|
|
328
|
+
def filter_bandpass(
|
|
329
|
+
buf: Buffer,
|
|
330
|
+
low_freq: float,
|
|
331
|
+
high_freq: float,
|
|
332
|
+
fft_size: int = 1024,
|
|
333
|
+
rolloff: float = 1.0,
|
|
334
|
+
) -> Buffer:
|
|
335
|
+
"""Apply band-pass filter."""
|
|
336
|
+
...
|
|
337
|
+
|
|
338
|
+
def filter_notch(
|
|
339
|
+
buf: Buffer,
|
|
340
|
+
center_freq: float,
|
|
341
|
+
width_hz: float,
|
|
342
|
+
fft_size: int = 1024,
|
|
343
|
+
depth: float = 1.0,
|
|
344
|
+
) -> Buffer:
|
|
345
|
+
"""Apply notch (band-reject) filter."""
|
|
346
|
+
...
|
|
347
|
+
|
|
348
|
+
# =============================================================================
|
|
349
|
+
# Dynamics and effects
|
|
350
|
+
# =============================================================================
|
|
351
|
+
|
|
352
|
+
def gate(
|
|
353
|
+
buf: Buffer,
|
|
354
|
+
threshold_db: float,
|
|
355
|
+
attack_ms: float = 1.0,
|
|
356
|
+
hold_ms: float = 50.0,
|
|
357
|
+
release_ms: float = 50.0,
|
|
358
|
+
) -> Buffer:
|
|
359
|
+
"""Apply noise gate."""
|
|
360
|
+
...
|
|
361
|
+
|
|
362
|
+
def bitcrush(buf: Buffer, bit_depth: int = 8, downsample: int = 1) -> Buffer:
|
|
363
|
+
"""Apply bit depth reduction and downsampling."""
|
|
364
|
+
...
|
|
365
|
+
|
|
366
|
+
def ring_mod(buf: Buffer, freq: float, mix: float = 1.0) -> Buffer:
|
|
367
|
+
"""Apply ring modulation."""
|
|
368
|
+
...
|
|
369
|
+
|
|
370
|
+
def delay(
|
|
371
|
+
buf: Buffer, delay_ms: float, feedback: float = 0.3, mix: float = 0.5
|
|
372
|
+
) -> Buffer:
|
|
373
|
+
"""Apply delay effect."""
|
|
374
|
+
...
|
|
375
|
+
|
|
376
|
+
def chorus(
|
|
377
|
+
buf: Buffer, rate: float = 1.5, depth_ms: float = 5.0, mix: float = 0.5
|
|
378
|
+
) -> Buffer:
|
|
379
|
+
"""Apply chorus effect."""
|
|
380
|
+
...
|
|
381
|
+
|
|
382
|
+
def flanger(
|
|
383
|
+
buf: Buffer,
|
|
384
|
+
rate: float = 0.5,
|
|
385
|
+
depth_ms: float = 3.0,
|
|
386
|
+
feedback: float = 0.7,
|
|
387
|
+
mix: float = 0.5,
|
|
388
|
+
) -> Buffer:
|
|
389
|
+
"""Apply flanger effect."""
|
|
390
|
+
...
|
|
391
|
+
|
|
392
|
+
# =============================================================================
|
|
393
|
+
# EQ and dynamics
|
|
394
|
+
# =============================================================================
|
|
395
|
+
|
|
396
|
+
def eq_parametric(
|
|
397
|
+
buf: Buffer, center_freq: float, gain_db: float, q: float = 1.0
|
|
398
|
+
) -> Buffer:
|
|
399
|
+
"""Apply parametric EQ band."""
|
|
400
|
+
...
|
|
401
|
+
|
|
402
|
+
def envelope_follow(
|
|
403
|
+
buf: Buffer, attack_ms: float = 10.0, release_ms: float = 100.0, mode: str = "peak"
|
|
404
|
+
) -> Buffer:
|
|
405
|
+
"""Extract amplitude envelope from audio."""
|
|
406
|
+
...
|
|
407
|
+
|
|
408
|
+
def envelope_apply(buf: Buffer, envelope: Buffer, depth: float = 1.0) -> Buffer:
|
|
409
|
+
"""Apply envelope to audio."""
|
|
410
|
+
...
|
|
411
|
+
|
|
412
|
+
def compressor(
|
|
413
|
+
buf: Buffer,
|
|
414
|
+
threshold_db: float = -20.0,
|
|
415
|
+
ratio: float = 4.0,
|
|
416
|
+
attack_ms: float = 10.0,
|
|
417
|
+
release_ms: float = 100.0,
|
|
418
|
+
makeup_db: float = 0.0,
|
|
419
|
+
) -> Buffer:
|
|
420
|
+
"""Apply dynamic range compression."""
|
|
421
|
+
...
|
|
422
|
+
|
|
423
|
+
def limiter(
|
|
424
|
+
buf: Buffer,
|
|
425
|
+
threshold_db: float = -0.1,
|
|
426
|
+
attack_ms: float = 0.1,
|
|
427
|
+
release_ms: float = 50.0,
|
|
428
|
+
) -> Buffer:
|
|
429
|
+
"""Apply peak limiting."""
|
|
430
|
+
...
|
|
431
|
+
|
|
432
|
+
# =============================================================================
|
|
433
|
+
# Envelope operations
|
|
434
|
+
# =============================================================================
|
|
435
|
+
|
|
436
|
+
def dovetail(
|
|
437
|
+
buf: Buffer, fade_in_dur: float, fade_out_dur: float, fade_type: int = 0
|
|
438
|
+
) -> Buffer:
|
|
439
|
+
"""Apply dovetail fades."""
|
|
440
|
+
...
|
|
441
|
+
|
|
442
|
+
def tremolo(buf: Buffer, freq: float, depth: float, gain: float = 1.0) -> Buffer:
|
|
443
|
+
"""Apply tremolo effect."""
|
|
444
|
+
...
|
|
445
|
+
|
|
446
|
+
def attack(buf: Buffer, attack_gain: float, attack_time: float) -> Buffer:
|
|
447
|
+
"""Modify attack transient."""
|
|
448
|
+
...
|
|
449
|
+
|
|
450
|
+
# =============================================================================
|
|
451
|
+
# Distortion operations
|
|
452
|
+
# =============================================================================
|
|
453
|
+
|
|
454
|
+
def distort_overload(buf: Buffer, clip_level: float, depth: float = 0.5) -> Buffer:
|
|
455
|
+
"""Apply overload/saturation distortion."""
|
|
456
|
+
...
|
|
457
|
+
|
|
458
|
+
def distort_reverse(buf: Buffer, cycle_count: int) -> Buffer:
|
|
459
|
+
"""Apply reverse distortion effect."""
|
|
460
|
+
...
|
|
461
|
+
|
|
462
|
+
def distort_fractal(buf: Buffer, scaling: float, loudness: float = 1.0) -> Buffer:
|
|
463
|
+
"""Apply fractal distortion."""
|
|
464
|
+
...
|
|
465
|
+
|
|
466
|
+
def distort_shuffle(buf: Buffer, chunk_count: int, seed: int = 0) -> Buffer:
|
|
467
|
+
"""Apply shuffle distortion."""
|
|
468
|
+
...
|
|
469
|
+
|
|
470
|
+
def distort_cut(
|
|
471
|
+
buf: Buffer, cycle_count: int = 4, cycle_step: int = 4, decay: float = 0.9
|
|
472
|
+
) -> Buffer:
|
|
473
|
+
"""Waveset cut with decaying envelope."""
|
|
474
|
+
...
|
|
475
|
+
|
|
476
|
+
def distort_mark(
|
|
477
|
+
buf: Buffer, markers: Sequence[float], unit_ms: float = 10.0, interp_ms: float = 5.0
|
|
478
|
+
) -> Buffer:
|
|
479
|
+
"""Interpolate wavesets at time markers."""
|
|
480
|
+
...
|
|
481
|
+
|
|
482
|
+
def distort_repeat(
|
|
483
|
+
buf: Buffer, multiplier: int = 2, cycle_count: int = 1, skip: int = 0
|
|
484
|
+
) -> Buffer:
|
|
485
|
+
"""Time-stretch by repeating wavecycles."""
|
|
486
|
+
...
|
|
487
|
+
|
|
488
|
+
def distort_shift(
|
|
489
|
+
buf: Buffer, group_size: int = 1, shift: int = 1, mode: int = 0
|
|
490
|
+
) -> Buffer:
|
|
491
|
+
"""Shift/swap half-wavecycle groups."""
|
|
492
|
+
...
|
|
493
|
+
|
|
494
|
+
def distort_warp(
|
|
495
|
+
buf: Buffer, warp: float = 0.001, mode: int = 0, waveset_count: int = 1
|
|
496
|
+
) -> Buffer:
|
|
497
|
+
"""Progressive warp distortion with sample folding."""
|
|
498
|
+
...
|
|
499
|
+
|
|
500
|
+
# =============================================================================
|
|
501
|
+
# Reverb
|
|
502
|
+
# =============================================================================
|
|
503
|
+
|
|
504
|
+
def reverb(
|
|
505
|
+
buf: Buffer,
|
|
506
|
+
mix: float = 0.5,
|
|
507
|
+
decay_time: float = 2.0,
|
|
508
|
+
damping: float = 0.5,
|
|
509
|
+
room_size: float = 0.5,
|
|
510
|
+
) -> Buffer:
|
|
511
|
+
"""Apply reverb effect."""
|
|
512
|
+
...
|
|
513
|
+
|
|
514
|
+
# =============================================================================
|
|
515
|
+
# Granular operations
|
|
516
|
+
# =============================================================================
|
|
517
|
+
|
|
518
|
+
def brassage(
|
|
519
|
+
buf: Buffer,
|
|
520
|
+
velocity: float = 1.0,
|
|
521
|
+
density: float = 1.0,
|
|
522
|
+
grainsize_ms: float = 50.0,
|
|
523
|
+
pitch_shift: float = 0.0,
|
|
524
|
+
scatter: float = 0.0,
|
|
525
|
+
) -> Buffer:
|
|
526
|
+
"""Apply granular brassage."""
|
|
527
|
+
...
|
|
528
|
+
|
|
529
|
+
def freeze(
|
|
530
|
+
buf: Buffer,
|
|
531
|
+
start_time: float,
|
|
532
|
+
end_time: float,
|
|
533
|
+
grainsize_ms: float = 50.0,
|
|
534
|
+
density: float = 1.0,
|
|
535
|
+
duration: float = 5.0,
|
|
536
|
+
) -> Buffer:
|
|
537
|
+
"""Granular freeze at position."""
|
|
538
|
+
...
|
|
539
|
+
|
|
540
|
+
def grain_cloud(
|
|
541
|
+
buf: Buffer,
|
|
542
|
+
gate: float = 0.1,
|
|
543
|
+
grainsize_ms: float = 50.0,
|
|
544
|
+
density: float = 1.0,
|
|
545
|
+
duration: float = 5.0,
|
|
546
|
+
) -> Buffer:
|
|
547
|
+
"""Grain cloud generation from amplitude-detected grains."""
|
|
548
|
+
...
|
|
549
|
+
|
|
550
|
+
def grain_extend(
|
|
551
|
+
buf: Buffer, grainsize_ms: float = 15.0, trough: float = 0.3, repeats: int = 2
|
|
552
|
+
) -> Buffer:
|
|
553
|
+
"""Extend duration using grain repetition."""
|
|
554
|
+
...
|
|
555
|
+
|
|
556
|
+
def texture_simple(
|
|
557
|
+
buf: Buffer,
|
|
558
|
+
duration: float = 5.0,
|
|
559
|
+
density: float = 5.0,
|
|
560
|
+
grainsize_ms: float = 50.0,
|
|
561
|
+
scatter: float = 0.5,
|
|
562
|
+
) -> Buffer:
|
|
563
|
+
"""Simple texture synthesis."""
|
|
564
|
+
...
|
|
565
|
+
|
|
566
|
+
def texture_multi(
|
|
567
|
+
buf: Buffer,
|
|
568
|
+
duration: float = 5.0,
|
|
569
|
+
density: float = 2.0,
|
|
570
|
+
grainsize_ms: float = 50.0,
|
|
571
|
+
num_layers: int = 4,
|
|
572
|
+
pitch_spread: float = 0.0,
|
|
573
|
+
) -> Buffer:
|
|
574
|
+
"""Multi-layer grouped texture synthesis."""
|
|
575
|
+
...
|
|
576
|
+
|
|
577
|
+
# =============================================================================
|
|
578
|
+
# Extended granular operations
|
|
579
|
+
# =============================================================================
|
|
580
|
+
|
|
581
|
+
def grain_reorder(
|
|
582
|
+
buf: Buffer,
|
|
583
|
+
order: list[int] | None = None,
|
|
584
|
+
gate: float = 0.1,
|
|
585
|
+
grainsize_ms: float = 50.0,
|
|
586
|
+
) -> Buffer:
|
|
587
|
+
"""Reorder detected grains."""
|
|
588
|
+
...
|
|
589
|
+
|
|
590
|
+
def grain_rerhythm(
|
|
591
|
+
buf: Buffer,
|
|
592
|
+
times: list[float] | None = None,
|
|
593
|
+
ratios: list[float] | None = None,
|
|
594
|
+
gate: float = 0.1,
|
|
595
|
+
grainsize_ms: float = 50.0,
|
|
596
|
+
) -> Buffer:
|
|
597
|
+
"""Change timing/rhythm of grains."""
|
|
598
|
+
...
|
|
599
|
+
|
|
600
|
+
def grain_reverse(buf: Buffer, gate: float = 0.1, grainsize_ms: float = 50.0) -> Buffer:
|
|
601
|
+
"""Reverse individual grains in place."""
|
|
602
|
+
...
|
|
603
|
+
|
|
604
|
+
def grain_timewarp(
|
|
605
|
+
buf: Buffer,
|
|
606
|
+
stretch: float = 1.0,
|
|
607
|
+
stretch_curve: list[tuple[float, float]] | None = None,
|
|
608
|
+
gate: float = 0.1,
|
|
609
|
+
grainsize_ms: float = 50.0,
|
|
610
|
+
) -> Buffer:
|
|
611
|
+
"""Time-stretch/compress grain spacing."""
|
|
612
|
+
...
|
|
613
|
+
|
|
614
|
+
def grain_repitch(
|
|
615
|
+
buf: Buffer,
|
|
616
|
+
pitch_semitones: float = 0.0,
|
|
617
|
+
pitch_curve: list[tuple[float, float]] | None = None,
|
|
618
|
+
gate: float = 0.1,
|
|
619
|
+
grainsize_ms: float = 50.0,
|
|
620
|
+
) -> Buffer:
|
|
621
|
+
"""Pitch-shift grains with interpolation."""
|
|
622
|
+
...
|
|
623
|
+
|
|
624
|
+
def grain_position(
|
|
625
|
+
buf: Buffer,
|
|
626
|
+
positions: list[float] | None = None,
|
|
627
|
+
duration: float = 0.0,
|
|
628
|
+
gate: float = 0.1,
|
|
629
|
+
grainsize_ms: float = 50.0,
|
|
630
|
+
) -> Buffer:
|
|
631
|
+
"""Reposition grains in stereo field."""
|
|
632
|
+
...
|
|
633
|
+
|
|
634
|
+
def grain_omit(
|
|
635
|
+
buf: Buffer,
|
|
636
|
+
keep: int = 1,
|
|
637
|
+
out_of: int = 2,
|
|
638
|
+
gate: float = 0.1,
|
|
639
|
+
grainsize_ms: float = 50.0,
|
|
640
|
+
) -> Buffer:
|
|
641
|
+
"""Probabilistically omit grains."""
|
|
642
|
+
...
|
|
643
|
+
|
|
644
|
+
def grain_duplicate(
|
|
645
|
+
buf: Buffer, repeats: int = 2, gate: float = 0.1, grainsize_ms: float = 50.0
|
|
646
|
+
) -> Buffer:
|
|
647
|
+
"""Duplicate grains with variations."""
|
|
648
|
+
...
|
|
649
|
+
|
|
650
|
+
# =============================================================================
|
|
651
|
+
# Spectral operations
|
|
652
|
+
# =============================================================================
|
|
653
|
+
|
|
654
|
+
def spectral_focus(
|
|
655
|
+
buf: Buffer,
|
|
656
|
+
center_freq: float = 1000.0,
|
|
657
|
+
bandwidth: float = 500.0,
|
|
658
|
+
gain: float = 2.0,
|
|
659
|
+
fft_size: int = 1024,
|
|
660
|
+
) -> Buffer:
|
|
661
|
+
"""Focus on frequency region."""
|
|
662
|
+
...
|
|
663
|
+
|
|
664
|
+
def spectral_hilite(
|
|
665
|
+
buf: Buffer, threshold_db: float = -20.0, gain: float = 2.0, fft_size: int = 1024
|
|
666
|
+
) -> Buffer:
|
|
667
|
+
"""Highlight frequency region."""
|
|
668
|
+
...
|
|
669
|
+
|
|
670
|
+
def spectral_fold(
|
|
671
|
+
buf: Buffer, fold_freq: float = 2000.0, fft_size: int = 1024
|
|
672
|
+
) -> Buffer:
|
|
673
|
+
"""Fold spectrum around frequency."""
|
|
674
|
+
...
|
|
675
|
+
|
|
676
|
+
def spectral_clean(
|
|
677
|
+
buf: Buffer, threshold_db: float = -40.0, fft_size: int = 1024
|
|
678
|
+
) -> Buffer:
|
|
679
|
+
"""Remove spectral noise."""
|
|
680
|
+
...
|
|
681
|
+
|
|
682
|
+
# =============================================================================
|
|
683
|
+
# Experimental/Chaos
|
|
684
|
+
# =============================================================================
|
|
685
|
+
|
|
686
|
+
def strange(
|
|
687
|
+
buf: Buffer, chaos_amount: float = 0.5, rate: float = 2.0, mode: int = 0
|
|
688
|
+
) -> Buffer:
|
|
689
|
+
"""Strange attractor transformation."""
|
|
690
|
+
...
|
|
691
|
+
|
|
692
|
+
def brownian(
|
|
693
|
+
buf: Buffer, step_size: float = 0.1, smoothing: float = 0.9, mode: int = 0
|
|
694
|
+
) -> Buffer:
|
|
695
|
+
"""Brownian motion transformation."""
|
|
696
|
+
...
|
|
697
|
+
|
|
698
|
+
def crystal(
|
|
699
|
+
buf: Buffer, density: float = 50.0, decay: float = 0.5, pitch_variance: float = 0.1
|
|
700
|
+
) -> Buffer:
|
|
701
|
+
"""Crystal growth patterns."""
|
|
702
|
+
...
|
|
703
|
+
|
|
704
|
+
def fractal(
|
|
705
|
+
buf: Buffer, depth: int = 3, pitch_ratio: float = 0.5, amp_decay: float = 0.7
|
|
706
|
+
) -> Buffer:
|
|
707
|
+
"""Fractal transformation."""
|
|
708
|
+
...
|
|
709
|
+
|
|
710
|
+
def quirk(
|
|
711
|
+
buf: Buffer, probability: float = 0.3, intensity: float = 0.5, seed: int = 0
|
|
712
|
+
) -> Buffer:
|
|
713
|
+
"""Quirky transformation."""
|
|
714
|
+
...
|
|
715
|
+
|
|
716
|
+
def chirikov(
|
|
717
|
+
buf: Buffer, k_param: float = 2.0, mod_depth: float = 0.5, rate: float = 10.0
|
|
718
|
+
) -> Buffer:
|
|
719
|
+
"""Chirikov map transformation."""
|
|
720
|
+
...
|
|
721
|
+
|
|
722
|
+
def cantor(
|
|
723
|
+
buf: Buffer, depth: int = 4, duty_cycle: float = 0.5, fade_ms: float = 5.0
|
|
724
|
+
) -> Buffer:
|
|
725
|
+
"""Cantor set transformation."""
|
|
726
|
+
...
|
|
727
|
+
|
|
728
|
+
def cascade(
|
|
729
|
+
buf: Buffer,
|
|
730
|
+
num_echoes: int = 6,
|
|
731
|
+
delay_ms: float = 100.0,
|
|
732
|
+
decay: float = 0.7,
|
|
733
|
+
pitch_shift: float = -2.0,
|
|
734
|
+
) -> Buffer:
|
|
735
|
+
"""Cascade transformation."""
|
|
736
|
+
...
|
|
737
|
+
|
|
738
|
+
def fracture(
|
|
739
|
+
buf: Buffer, fragment_ms: float = 50.0, gap_ratio: float = 0.5, scatter: float = 0.3
|
|
740
|
+
) -> Buffer:
|
|
741
|
+
"""Fracture transformation."""
|
|
742
|
+
...
|
|
743
|
+
|
|
744
|
+
def tesselate(
|
|
745
|
+
buf: Buffer, tile_ms: float = 50.0, pattern: int = 1, overlap: float = 0.5
|
|
746
|
+
) -> Buffer:
|
|
747
|
+
"""Tesselation transformation."""
|
|
748
|
+
...
|
|
749
|
+
|
|
750
|
+
# =============================================================================
|
|
751
|
+
# Morphing/Cross-synthesis
|
|
752
|
+
# =============================================================================
|
|
753
|
+
|
|
754
|
+
def morph(
|
|
755
|
+
buf1: Buffer, buf2: Buffer, amount: float = 0.5, fft_size: int = 1024
|
|
756
|
+
) -> Buffer:
|
|
757
|
+
"""Spectral morph between sounds."""
|
|
758
|
+
...
|
|
759
|
+
|
|
760
|
+
def morph_glide(buf1: Buffer, buf2: Buffer, fft_size: int = 1024) -> Buffer:
|
|
761
|
+
"""Gliding morph over time."""
|
|
762
|
+
...
|
|
763
|
+
|
|
764
|
+
def cross_synth(
|
|
765
|
+
buf1: Buffer, buf2: Buffer, fft_size: int = 1024, mode: int = 0
|
|
766
|
+
) -> Buffer:
|
|
767
|
+
"""Cross-synthesis (vocoder-like)."""
|
|
768
|
+
...
|
|
769
|
+
|
|
770
|
+
def morph_glide_native(buf1: Buffer, buf2: Buffer, fft_size: int = 1024) -> Buffer:
|
|
771
|
+
"""Native CDP specglide wrapper."""
|
|
772
|
+
...
|
|
773
|
+
|
|
774
|
+
def morph_bridge_native(
|
|
775
|
+
buf1: Buffer, buf2: Buffer, fft_size: int = 1024, interp_count: int = 8
|
|
776
|
+
) -> Buffer:
|
|
777
|
+
"""Native CDP specbridge wrapper."""
|
|
778
|
+
...
|
|
779
|
+
|
|
780
|
+
def morph_native(
|
|
781
|
+
buf1: Buffer, buf2: Buffer, fft_size: int = 1024, interp_count: int = 8
|
|
782
|
+
) -> Buffer:
|
|
783
|
+
"""Native CDP specmorph wrapper."""
|
|
784
|
+
...
|
|
785
|
+
|
|
786
|
+
# =============================================================================
|
|
787
|
+
# Analysis
|
|
788
|
+
# =============================================================================
|
|
789
|
+
|
|
790
|
+
def pitch(
|
|
791
|
+
buf: Buffer,
|
|
792
|
+
min_freq: float = 50.0,
|
|
793
|
+
max_freq: float = 2000.0,
|
|
794
|
+
frame_size: int = 2048,
|
|
795
|
+
hop_size: int = 512,
|
|
796
|
+
) -> list[tuple[float, float]]:
|
|
797
|
+
"""Extract pitch data."""
|
|
798
|
+
...
|
|
799
|
+
|
|
800
|
+
def formants(
|
|
801
|
+
buf: Buffer, lpc_order: int = 12, frame_size: int = 1024, hop_size: int = 256
|
|
802
|
+
) -> list[list[tuple[float, float]]]:
|
|
803
|
+
"""Extract formant data."""
|
|
804
|
+
...
|
|
805
|
+
|
|
806
|
+
def get_partials(
|
|
807
|
+
buf: Buffer,
|
|
808
|
+
min_amp_db: float = -60.0,
|
|
809
|
+
max_partials: int = 100,
|
|
810
|
+
fft_size: int = 4096,
|
|
811
|
+
) -> list[tuple[float, float, float]]:
|
|
812
|
+
"""Extract partial/harmonic data."""
|
|
813
|
+
...
|
|
814
|
+
|
|
815
|
+
# =============================================================================
|
|
816
|
+
# Playback/Time manipulation
|
|
817
|
+
# =============================================================================
|
|
818
|
+
|
|
819
|
+
def zigzag(buf: Buffer, times: Sequence[float], splice_ms: float = 15.0) -> Buffer:
|
|
820
|
+
"""Alternating forward/backward playback through time points."""
|
|
821
|
+
...
|
|
822
|
+
|
|
823
|
+
def iterate(
|
|
824
|
+
buf: Buffer,
|
|
825
|
+
repeats: int = 4,
|
|
826
|
+
delay: float = 0.5,
|
|
827
|
+
pitch_shift: float = 0.0,
|
|
828
|
+
gain_decay: float = 0.9,
|
|
829
|
+
) -> Buffer:
|
|
830
|
+
"""Repeat audio with pitch shift and gain decay variations."""
|
|
831
|
+
...
|
|
832
|
+
|
|
833
|
+
def stutter(
|
|
834
|
+
buf: Buffer,
|
|
835
|
+
segment_ms: float = 100.0,
|
|
836
|
+
duration: float = 5.0,
|
|
837
|
+
silence_ratio: float = 0.5,
|
|
838
|
+
) -> Buffer:
|
|
839
|
+
"""Segment-based stuttering with silence inserts."""
|
|
840
|
+
...
|
|
841
|
+
|
|
842
|
+
def bounce(
|
|
843
|
+
buf: Buffer,
|
|
844
|
+
bounces: int = 8,
|
|
845
|
+
initial_delay: float = 0.5,
|
|
846
|
+
decay: float = 0.7,
|
|
847
|
+
pitch_drop: float = 0.0,
|
|
848
|
+
) -> Buffer:
|
|
849
|
+
"""Bouncing ball effect with accelerating repeats."""
|
|
850
|
+
...
|
|
851
|
+
|
|
852
|
+
def drunk(
|
|
853
|
+
buf: Buffer,
|
|
854
|
+
duration: float = 5.0,
|
|
855
|
+
step_ms: float = 100.0,
|
|
856
|
+
max_step: float = 0.1,
|
|
857
|
+
splice_ms: float = 10.0,
|
|
858
|
+
) -> Buffer:
|
|
859
|
+
"""Random 'drunk walk' navigation through audio."""
|
|
860
|
+
...
|
|
861
|
+
|
|
862
|
+
def loop(
|
|
863
|
+
buf: Buffer,
|
|
864
|
+
start: float = 0.0,
|
|
865
|
+
length_ms: float = 500.0,
|
|
866
|
+
repeats: int = 4,
|
|
867
|
+
crossfade_ms: float = 10.0,
|
|
868
|
+
) -> Buffer:
|
|
869
|
+
"""Loop a section with crossfades and variations."""
|
|
870
|
+
...
|
|
871
|
+
|
|
872
|
+
def retime(
|
|
873
|
+
buf: Buffer, ratio: float = 1.0, grain_ms: float = 50.0, overlap: float = 0.5
|
|
874
|
+
) -> Buffer:
|
|
875
|
+
"""Time-domain time stretch/compress (TDOLA)."""
|
|
876
|
+
...
|
|
877
|
+
|
|
878
|
+
def scramble(buf: Buffer, mode: int = 0, group_size: int = 2, seed: int = 0) -> Buffer:
|
|
879
|
+
"""Reorder wavesets (shuffle, reverse, by size/level)."""
|
|
880
|
+
...
|
|
881
|
+
|
|
882
|
+
def splinter(
|
|
883
|
+
buf: Buffer,
|
|
884
|
+
start: float = 0.0,
|
|
885
|
+
duration_ms: float = 50.0,
|
|
886
|
+
repeats: int = 20,
|
|
887
|
+
shrink: float = 0.9,
|
|
888
|
+
) -> Buffer:
|
|
889
|
+
"""Fragmenting effect with shrinking repeats."""
|
|
890
|
+
...
|
|
891
|
+
|
|
892
|
+
# =============================================================================
|
|
893
|
+
# Spatial effects
|
|
894
|
+
# =============================================================================
|
|
895
|
+
|
|
896
|
+
def spin(
|
|
897
|
+
buf: Buffer, rate: float = 1.0, doppler: float = 0.0, depth: float = 1.0
|
|
898
|
+
) -> Buffer:
|
|
899
|
+
"""Rotate audio around stereo field with optional doppler."""
|
|
900
|
+
...
|
|
901
|
+
|
|
902
|
+
def rotor(
|
|
903
|
+
buf: Buffer,
|
|
904
|
+
pitch_rate: float = 1.0,
|
|
905
|
+
pitch_depth: float = 2.0,
|
|
906
|
+
amp_rate: float = 1.5,
|
|
907
|
+
amp_depth: float = 0.5,
|
|
908
|
+
) -> Buffer:
|
|
909
|
+
"""Dual-rotation modulation (pitch + amplitude interference)."""
|
|
910
|
+
...
|
|
911
|
+
|
|
912
|
+
def flutter(
|
|
913
|
+
buf: Buffer, frequency: float = 4.0, depth: float = 1.0, phase: float = 0.0
|
|
914
|
+
) -> Buffer:
|
|
915
|
+
"""Spatial tremolo (loudness modulation alternating L/R)."""
|
|
916
|
+
...
|
|
917
|
+
|
|
918
|
+
# =============================================================================
|
|
919
|
+
# Synthesis
|
|
920
|
+
# =============================================================================
|
|
921
|
+
|
|
922
|
+
def synth_wave(
|
|
923
|
+
waveform: int = ...,
|
|
924
|
+
frequency: float = 440.0,
|
|
925
|
+
amplitude: float = 0.8,
|
|
926
|
+
duration: float = 1.0,
|
|
927
|
+
sample_rate: int = 44100,
|
|
928
|
+
) -> Buffer:
|
|
929
|
+
"""Generate waveforms (sine, square, saw, ramp, triangle)."""
|
|
930
|
+
...
|
|
931
|
+
|
|
932
|
+
def synth_noise(
|
|
933
|
+
pink: int = 0,
|
|
934
|
+
amplitude: float = 0.8,
|
|
935
|
+
duration: float = 1.0,
|
|
936
|
+
sample_rate: int = 44100,
|
|
937
|
+
) -> Buffer:
|
|
938
|
+
"""Generate white or pink noise."""
|
|
939
|
+
...
|
|
940
|
+
|
|
941
|
+
def synth_click(
|
|
942
|
+
tempo: float = 120.0,
|
|
943
|
+
beats_per_bar: int = 4,
|
|
944
|
+
duration: float = 10.0,
|
|
945
|
+
sample_rate: int = 44100,
|
|
946
|
+
) -> Buffer:
|
|
947
|
+
"""Generate click/metronome track."""
|
|
948
|
+
...
|
|
949
|
+
|
|
950
|
+
def synth_chord(
|
|
951
|
+
midi_notes: Sequence[int],
|
|
952
|
+
amplitude: float = 0.8,
|
|
953
|
+
duration: float = 1.0,
|
|
954
|
+
sample_rate: int = 44100,
|
|
955
|
+
) -> Buffer:
|
|
956
|
+
"""Synthesize chord from MIDI note list."""
|
|
957
|
+
...
|
|
958
|
+
|
|
959
|
+
# =============================================================================
|
|
960
|
+
# Pitch-synchronous operations (PSOW)
|
|
961
|
+
# =============================================================================
|
|
962
|
+
|
|
963
|
+
def psow_stretch(
|
|
964
|
+
buf: Buffer, stretch_factor: float = 1.0, grain_count: int = 1
|
|
965
|
+
) -> Buffer:
|
|
966
|
+
"""Time-stretch while preserving pitch (PSOLA)."""
|
|
967
|
+
...
|
|
968
|
+
|
|
969
|
+
def psow_grab(
|
|
970
|
+
buf: Buffer, time: float = 0.0, duration: float = 0.0, grain_count: int = 1
|
|
971
|
+
) -> Buffer:
|
|
972
|
+
"""Extract pitch-synchronous grains from position."""
|
|
973
|
+
...
|
|
974
|
+
|
|
975
|
+
def psow_dupl(buf: Buffer, repeat_count: int = 2, grain_count: int = 1) -> Buffer:
|
|
976
|
+
"""Duplicate grains for time-stretching."""
|
|
977
|
+
...
|
|
978
|
+
|
|
979
|
+
def psow_interp(grain1: Buffer, grain2: Buffer, interp_count: int = 8) -> Buffer:
|
|
980
|
+
"""Interpolate between two grains."""
|
|
981
|
+
...
|
|
982
|
+
|
|
983
|
+
# =============================================================================
|
|
984
|
+
# FOF extraction and synthesis (FOFEX)
|
|
985
|
+
# =============================================================================
|
|
986
|
+
|
|
987
|
+
def fofex_extract(
|
|
988
|
+
buf: Buffer, time: float, fof_count: int = 1, window: bool = True
|
|
989
|
+
) -> Buffer:
|
|
990
|
+
"""Extract single FOF (pitch-synchronous grain) at time."""
|
|
991
|
+
...
|
|
992
|
+
|
|
993
|
+
def fofex_extract_all(
|
|
994
|
+
buf: Buffer, fof_count: int = 1, min_level_db: float = 0.0, window: bool = True
|
|
995
|
+
) -> Buffer:
|
|
996
|
+
"""Extract all FOFs to uniform-length bank."""
|
|
997
|
+
...
|
|
998
|
+
|
|
999
|
+
def fofex_synth(
|
|
1000
|
+
fof_bank: Buffer, duration: float, frequency: float, amplitude: float = 1.0
|
|
1001
|
+
) -> Buffer:
|
|
1002
|
+
"""Synthesize audio from FOFs at target pitch."""
|
|
1003
|
+
...
|
|
1004
|
+
|
|
1005
|
+
def fofex_repitch(
|
|
1006
|
+
buf: Buffer, pitch_shift: float, preserve_formants: bool = True
|
|
1007
|
+
) -> Buffer:
|
|
1008
|
+
"""Repitch audio with optional formant preservation."""
|
|
1009
|
+
...
|
|
1010
|
+
|
|
1011
|
+
# =============================================================================
|
|
1012
|
+
# Hover/Constrict/Phase
|
|
1013
|
+
# =============================================================================
|
|
1014
|
+
|
|
1015
|
+
def hover(
|
|
1016
|
+
buf: Buffer,
|
|
1017
|
+
frequency: float = 440.0,
|
|
1018
|
+
location: float = 0.5,
|
|
1019
|
+
duration: float = 5.0,
|
|
1020
|
+
grainsize_ms: float = 50.0,
|
|
1021
|
+
) -> Buffer:
|
|
1022
|
+
"""Zigzag reading at specified frequency for hovering pitch effect."""
|
|
1023
|
+
...
|
|
1024
|
+
|
|
1025
|
+
def constrict(buf: Buffer, constriction: float = 50.0) -> Buffer:
|
|
1026
|
+
"""Shorten or remove silent sections."""
|
|
1027
|
+
...
|
|
1028
|
+
|
|
1029
|
+
def phase_stereo(buf: Buffer, transfer: float = 1.0) -> Buffer:
|
|
1030
|
+
"""Enhance stereo separation via phase subtraction."""
|
|
1031
|
+
...
|
|
1032
|
+
|
|
1033
|
+
# =============================================================================
|
|
1034
|
+
# Granular texture
|
|
1035
|
+
# =============================================================================
|
|
1036
|
+
|
|
1037
|
+
def wrappage(
|
|
1038
|
+
buf: Buffer,
|
|
1039
|
+
grain_size: float = 50.0,
|
|
1040
|
+
density: float = 1.0,
|
|
1041
|
+
velocity: float = 1.0,
|
|
1042
|
+
pitch: float = 0.0,
|
|
1043
|
+
spread: float = 1.0,
|
|
1044
|
+
jitter: float = 0.1,
|
|
1045
|
+
splice_ms: float = 5.0,
|
|
1046
|
+
duration: float = 0.0,
|
|
1047
|
+
) -> Buffer:
|
|
1048
|
+
"""Granular texture with stereo spatial distribution."""
|
|
1049
|
+
...
|