cysox 0.1.5__cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_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.
cysox/fx/reverb.py ADDED
@@ -0,0 +1,215 @@
1
+ """Reverb and spatial effects."""
2
+
3
+ from typing import List
4
+
5
+ from .base import Effect
6
+
7
+
8
+ class Reverb(Effect):
9
+ """Add reverberation effect.
10
+
11
+ Args:
12
+ reverberance: Reverb time as percentage 0-100 (default: 50).
13
+ hf_damping: High frequency damping percentage 0-100 (default: 50).
14
+ room_scale: Room size percentage 0-100 (default: 100).
15
+ stereo_depth: Stereo spread percentage 0-100 (default: 100).
16
+ pre_delay: Pre-delay in milliseconds (default: 0).
17
+ wet_gain: Wet signal gain in dB (default: 0).
18
+ wet_only: Output only wet signal, no dry (default: False).
19
+
20
+ Example:
21
+ >>> fx.Reverb() # Default reverb
22
+ >>> fx.Reverb(reverberance=80) # Long reverb
23
+ >>> fx.Reverb(room_scale=50, pre_delay=20) # Small room with pre-delay
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ reverberance: float = 50,
29
+ hf_damping: float = 50,
30
+ room_scale: float = 100,
31
+ stereo_depth: float = 100,
32
+ pre_delay: float = 0,
33
+ wet_gain: float = 0,
34
+ *,
35
+ wet_only: bool = False
36
+ ):
37
+ self.reverberance = reverberance
38
+ self.hf_damping = hf_damping
39
+ self.room_scale = room_scale
40
+ self.stereo_depth = stereo_depth
41
+ self.pre_delay = pre_delay
42
+ self.wet_gain = wet_gain
43
+ self.wet_only = wet_only
44
+
45
+ @property
46
+ def name(self) -> str:
47
+ return "reverb"
48
+
49
+ def to_args(self) -> List[str]:
50
+ args = []
51
+ if self.wet_only:
52
+ args.append("-w")
53
+ args.extend([
54
+ str(self.reverberance),
55
+ str(self.hf_damping),
56
+ str(self.room_scale),
57
+ str(self.stereo_depth),
58
+ str(self.pre_delay),
59
+ str(self.wet_gain),
60
+ ])
61
+ return args
62
+
63
+
64
+ class Echo(Effect):
65
+ """Add echo effect.
66
+
67
+ Args:
68
+ gain_in: Input gain (0-1, default: 0.8).
69
+ gain_out: Output gain (0-1, default: 0.9).
70
+ delays: List of delay times in milliseconds.
71
+ decays: List of decay values (0-1) for each delay.
72
+
73
+ Example:
74
+ >>> fx.Echo(delays=[100], decays=[0.5]) # Single echo
75
+ >>> fx.Echo(delays=[100, 200], decays=[0.6, 0.3]) # Multi-tap
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ delays: List[float],
81
+ decays: List[float],
82
+ *,
83
+ gain_in: float = 0.8,
84
+ gain_out: float = 0.9
85
+ ):
86
+ if len(delays) != len(decays):
87
+ raise ValueError("delays and decays must have same length")
88
+ self.gain_in = gain_in
89
+ self.gain_out = gain_out
90
+ self.delays = delays
91
+ self.decays = decays
92
+
93
+ @property
94
+ def name(self) -> str:
95
+ return "echo"
96
+
97
+ def to_args(self) -> List[str]:
98
+ args = [str(self.gain_in), str(self.gain_out)]
99
+ for delay, decay in zip(self.delays, self.decays):
100
+ args.extend([str(delay), str(decay)])
101
+ return args
102
+
103
+
104
+ class Chorus(Effect):
105
+ """Add chorus effect.
106
+
107
+ Args:
108
+ gain_in: Input gain (0-1, default: 0.7).
109
+ gain_out: Output gain (0-1, default: 0.9).
110
+ delay: Base delay in milliseconds (default: 55).
111
+ decay: Decay factor (default: 0.4).
112
+ speed: Modulation speed in Hz (default: 0.25).
113
+ depth: Modulation depth in milliseconds (default: 2).
114
+ shape: Modulation shape 's' (sine) or 't' (triangle) (default: 's').
115
+
116
+ Example:
117
+ >>> fx.Chorus() # Default chorus
118
+ >>> fx.Chorus(depth=4, speed=0.5) # Deeper, faster modulation
119
+ """
120
+
121
+ def __init__(
122
+ self,
123
+ *,
124
+ gain_in: float = 0.7,
125
+ gain_out: float = 0.9,
126
+ delay: float = 55,
127
+ decay: float = 0.4,
128
+ speed: float = 0.25,
129
+ depth: float = 2,
130
+ shape: str = "s"
131
+ ):
132
+ if shape not in ("s", "t"):
133
+ raise ValueError("shape must be 's' (sine) or 't' (triangle)")
134
+ self.gain_in = gain_in
135
+ self.gain_out = gain_out
136
+ self.delay = delay
137
+ self.decay = decay
138
+ self.speed = speed
139
+ self.depth = depth
140
+ self.shape = shape
141
+
142
+ @property
143
+ def name(self) -> str:
144
+ return "chorus"
145
+
146
+ def to_args(self) -> List[str]:
147
+ return [
148
+ str(self.gain_in),
149
+ str(self.gain_out),
150
+ str(self.delay),
151
+ str(self.decay),
152
+ str(self.speed),
153
+ str(self.depth),
154
+ f"-{self.shape}",
155
+ ]
156
+
157
+
158
+ class Flanger(Effect):
159
+ """Add flanger effect.
160
+
161
+ Args:
162
+ delay: Base delay in milliseconds (default: 0).
163
+ depth: Modulation depth in milliseconds (default: 2).
164
+ regen: Regeneration/feedback percentage -95 to 95 (default: 0).
165
+ width: Wet/dry mix percentage (default: 71).
166
+ speed: Modulation speed in Hz (default: 0.5).
167
+ shape: Modulation shape 'sine' or 'triangle' (default: 'sine').
168
+ phase: Phase offset percentage for stereo (default: 25).
169
+ interp: Interpolation 'linear' or 'quadratic' (default: 'linear').
170
+
171
+ Example:
172
+ >>> fx.Flanger() # Default flanger
173
+ >>> fx.Flanger(depth=5, speed=0.3, regen=50) # Deeper effect
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ *,
179
+ delay: float = 0,
180
+ depth: float = 2,
181
+ regen: float = 0,
182
+ width: float = 71,
183
+ speed: float = 0.5,
184
+ shape: str = "sine",
185
+ phase: float = 25,
186
+ interp: str = "linear"
187
+ ):
188
+ if shape not in ("sine", "triangle"):
189
+ raise ValueError("shape must be 'sine' or 'triangle'")
190
+ if interp not in ("linear", "quadratic"):
191
+ raise ValueError("interp must be 'linear' or 'quadratic'")
192
+ self.delay = delay
193
+ self.depth = depth
194
+ self.regen = regen
195
+ self.width = width
196
+ self.speed = speed
197
+ self.shape = shape
198
+ self.phase = phase
199
+ self.interp = interp
200
+
201
+ @property
202
+ def name(self) -> str:
203
+ return "flanger"
204
+
205
+ def to_args(self) -> List[str]:
206
+ return [
207
+ str(self.delay),
208
+ str(self.depth),
209
+ str(self.regen),
210
+ str(self.width),
211
+ str(self.speed),
212
+ self.shape,
213
+ str(self.phase),
214
+ self.interp,
215
+ ]
cysox/fx/time.py ADDED
@@ -0,0 +1,248 @@
1
+ """Time-based effects (trim, pad, speed, tempo, etc.)."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from .base import Effect
6
+
7
+
8
+ class Trim(Effect):
9
+ """Extract a portion of the audio.
10
+
11
+ Specify either `end` or `duration`, not both.
12
+
13
+ Args:
14
+ start: Start time in seconds (default: 0).
15
+ end: End time in seconds (mutually exclusive with duration).
16
+ duration: Duration in seconds (mutually exclusive with end).
17
+
18
+ Example:
19
+ >>> fx.Trim(start=1.5, end=10.0) # From 1.5s to 10s
20
+ >>> fx.Trim(start=5.0) # From 5s to end
21
+ >>> fx.Trim(end=30.0) # First 30 seconds
22
+ >>> fx.Trim(start=0, duration=10) # First 10 seconds
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ start: float = 0,
28
+ end: Optional[float] = None,
29
+ *,
30
+ duration: Optional[float] = None
31
+ ):
32
+ if end is not None and duration is not None:
33
+ raise ValueError("Cannot specify both 'end' and 'duration'")
34
+ self.start = start
35
+ self.end = end
36
+ self.duration = duration
37
+
38
+ @property
39
+ def name(self) -> str:
40
+ return "trim"
41
+
42
+ def to_args(self) -> List[str]:
43
+ args = [str(self.start)]
44
+ if self.duration is not None:
45
+ args.append(str(self.duration))
46
+ elif self.end is not None:
47
+ args.append(f"={self.end}")
48
+ return args
49
+
50
+
51
+ class Pad(Effect):
52
+ """Add silence to the beginning and/or end.
53
+
54
+ Args:
55
+ before: Seconds of silence to add at the beginning (default: 0).
56
+ after: Seconds of silence to add at the end (default: 0).
57
+
58
+ Example:
59
+ >>> fx.Pad(before=1.0) # Add 1s silence at start
60
+ >>> fx.Pad(after=2.0) # Add 2s silence at end
61
+ >>> fx.Pad(before=0.5, after=1.0) # Add both
62
+ """
63
+
64
+ def __init__(self, before: float = 0, after: float = 0):
65
+ self.before = before
66
+ self.after = after
67
+
68
+ @property
69
+ def name(self) -> str:
70
+ return "pad"
71
+
72
+ def to_args(self) -> List[str]:
73
+ return [str(self.before), str(self.after)]
74
+
75
+
76
+ class Speed(Effect):
77
+ """Change playback speed (affects pitch).
78
+
79
+ Args:
80
+ factor: Speed multiplier. 2.0 = double speed (higher pitch),
81
+ 0.5 = half speed (lower pitch).
82
+
83
+ Example:
84
+ >>> fx.Speed(factor=2.0) # Double speed, octave up
85
+ >>> fx.Speed(factor=0.5) # Half speed, octave down
86
+ """
87
+
88
+ def __init__(self, factor: float):
89
+ if factor <= 0:
90
+ raise ValueError("factor must be positive")
91
+ self.factor = factor
92
+
93
+ @property
94
+ def name(self) -> str:
95
+ return "speed"
96
+
97
+ def to_args(self) -> List[str]:
98
+ return [str(self.factor)]
99
+
100
+
101
+ class Tempo(Effect):
102
+ """Change tempo without affecting pitch.
103
+
104
+ Args:
105
+ factor: Tempo multiplier. 2.0 = double tempo, 0.5 = half tempo.
106
+ audio_type: Optimize for 'm' (music), 's' (speech), or 'l' (linear).
107
+ Default: None (auto-detect).
108
+ quick: Use quicker, lower-quality algorithm (default: False).
109
+
110
+ Example:
111
+ >>> fx.Tempo(factor=1.5) # 50% faster
112
+ >>> fx.Tempo(factor=0.8, audio_type='s') # Slower speech
113
+ """
114
+
115
+ def __init__(
116
+ self,
117
+ factor: float,
118
+ *,
119
+ audio_type: Optional[str] = None,
120
+ quick: bool = False
121
+ ):
122
+ if factor <= 0:
123
+ raise ValueError("factor must be positive")
124
+ if audio_type is not None and audio_type not in ("m", "s", "l"):
125
+ raise ValueError("audio_type must be 'm', 's', or 'l'")
126
+ self.factor = factor
127
+ self.audio_type = audio_type
128
+ self.quick = quick
129
+
130
+ @property
131
+ def name(self) -> str:
132
+ return "tempo"
133
+
134
+ def to_args(self) -> List[str]:
135
+ args = []
136
+ if self.quick:
137
+ args.append("-q")
138
+ if self.audio_type:
139
+ args.append(f"-{self.audio_type}")
140
+ args.append(str(self.factor))
141
+ return args
142
+
143
+
144
+ class Pitch(Effect):
145
+ """Change pitch without affecting tempo.
146
+
147
+ Args:
148
+ cents: Pitch shift in cents (100 cents = 1 semitone).
149
+ quick: Use quicker, lower-quality algorithm (default: False).
150
+
151
+ Example:
152
+ >>> fx.Pitch(cents=100) # Up one semitone
153
+ >>> fx.Pitch(cents=-200) # Down two semitones
154
+ """
155
+
156
+ def __init__(self, cents: float, *, quick: bool = False):
157
+ self.cents = cents
158
+ self.quick = quick
159
+
160
+ @property
161
+ def name(self) -> str:
162
+ return "pitch"
163
+
164
+ def to_args(self) -> List[str]:
165
+ args = []
166
+ if self.quick:
167
+ args.append("-q")
168
+ args.append(str(self.cents))
169
+ return args
170
+
171
+
172
+ class Reverse(Effect):
173
+ """Reverse the audio.
174
+
175
+ Example:
176
+ >>> fx.Reverse()
177
+ """
178
+
179
+ @property
180
+ def name(self) -> str:
181
+ return "reverse"
182
+
183
+ def to_args(self) -> List[str]:
184
+ return []
185
+
186
+
187
+ class Fade(Effect):
188
+ """Apply fade in and/or fade out.
189
+
190
+ Args:
191
+ fade_in: Fade-in duration in seconds (default: 0).
192
+ fade_out: Fade-out duration in seconds (default: 0).
193
+ type: Fade curve type - 'q' (quarter sine), 'h' (half sine),
194
+ 't' (linear), 'l' (logarithmic), 'p' (parabola) (default: 't').
195
+
196
+ Example:
197
+ >>> fx.Fade(fade_in=0.5) # 0.5s fade in
198
+ >>> fx.Fade(fade_out=2.0) # 2s fade out
199
+ >>> fx.Fade(fade_in=1.0, fade_out=1.0, type='l') # Log fades
200
+ """
201
+
202
+ def __init__(
203
+ self,
204
+ fade_in: float = 0,
205
+ fade_out: float = 0,
206
+ *,
207
+ type: str = "t"
208
+ ):
209
+ if type not in ("q", "h", "t", "l", "p"):
210
+ raise ValueError("type must be 'q', 'h', 't', 'l', or 'p'")
211
+ self.fade_in = fade_in
212
+ self.fade_out = fade_out
213
+ self.type = type
214
+
215
+ @property
216
+ def name(self) -> str:
217
+ return "fade"
218
+
219
+ def to_args(self) -> List[str]:
220
+ # sox fade type fade_in [stop_time [fade_out]]
221
+ args = [self.type, str(self.fade_in)]
222
+ if self.fade_out > 0:
223
+ # Using 0 for stop_time means end of file
224
+ args.extend(["0", str(self.fade_out)])
225
+ return args
226
+
227
+
228
+ class Repeat(Effect):
229
+ """Repeat the audio.
230
+
231
+ Args:
232
+ count: Number of times to repeat (in addition to original).
233
+
234
+ Example:
235
+ >>> fx.Repeat(count=2) # Play 3 times total
236
+ """
237
+
238
+ def __init__(self, count: int):
239
+ if count < 1:
240
+ raise ValueError("count must be at least 1")
241
+ self.count = count
242
+
243
+ @property
244
+ def name(self) -> str:
245
+ return "repeat"
246
+
247
+ def to_args(self) -> List[str]:
248
+ return [str(self.count)]
cysox/fx/volume.py ADDED
@@ -0,0 +1,98 @@
1
+ """Volume and gain effects."""
2
+
3
+ from typing import List
4
+
5
+ from .base import Effect
6
+
7
+
8
+ class Volume(Effect):
9
+ """Adjust volume level.
10
+
11
+ Args:
12
+ db: Volume adjustment in decibels. Positive = louder, negative = quieter.
13
+ limiter: Apply limiter to prevent clipping (default: False).
14
+
15
+ Example:
16
+ >>> fx.Volume(db=3) # Increase by 3dB
17
+ >>> fx.Volume(db=-6) # Decrease by 6dB
18
+ >>> fx.Volume(db=6, limiter=True) # Boost with limiting
19
+ """
20
+
21
+ def __init__(self, db: float = 0, *, limiter: bool = False):
22
+ self.db = db
23
+ self.limiter = limiter
24
+
25
+ @property
26
+ def name(self) -> str:
27
+ return "vol"
28
+
29
+ def to_args(self) -> List[str]:
30
+ args = [f"{self.db}dB"]
31
+ if self.limiter:
32
+ args.append("limiter")
33
+ return args
34
+
35
+
36
+ class Gain(Effect):
37
+ """Apply gain with various options.
38
+
39
+ Args:
40
+ db: Gain in decibels.
41
+ normalize: Normalize to 0dBFS before applying gain.
42
+ limiter: Apply limiter to prevent clipping.
43
+ balance: Balance channels (for stereo).
44
+
45
+ Example:
46
+ >>> fx.Gain(db=-3)
47
+ >>> fx.Gain(db=0, normalize=True) # Normalize only
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ db: float = 0,
53
+ *,
54
+ normalize: bool = False,
55
+ limiter: bool = False,
56
+ balance: bool = False
57
+ ):
58
+ self.db = db
59
+ self.normalize = normalize
60
+ self.limiter = limiter
61
+ self.balance = balance
62
+
63
+ @property
64
+ def name(self) -> str:
65
+ return "gain"
66
+
67
+ def to_args(self) -> List[str]:
68
+ args = []
69
+ if self.normalize:
70
+ args.append("-n")
71
+ if self.limiter:
72
+ args.append("-l")
73
+ if self.balance:
74
+ args.append("-B")
75
+ args.append(str(self.db))
76
+ return args
77
+
78
+
79
+ class Normalize(Effect):
80
+ """Normalize audio to a target level.
81
+
82
+ Args:
83
+ level: Target level in dB (default: -1 dBFS).
84
+
85
+ Example:
86
+ >>> fx.Normalize() # Normalize to -1 dBFS
87
+ >>> fx.Normalize(level=-3) # Normalize to -3 dBFS
88
+ """
89
+
90
+ def __init__(self, level: float = -1):
91
+ self.level = level
92
+
93
+ @property
94
+ def name(self) -> str:
95
+ return "norm"
96
+
97
+ def to_args(self) -> List[str]:
98
+ return [str(self.level)]
Binary file
cysox/utils.py ADDED
@@ -0,0 +1,47 @@
1
+ # Version utility functions
2
+
3
+
4
+ def lib_version(major: int, minor: int, patch: int) -> int:
5
+ """Compute a 32-bit integer API version from three 8-bit parts."""
6
+ return ((major) << 16) + ((minor) << 8) + (patch)
7
+
8
+
9
+ def lib_version_code() -> int:
10
+ """Get the current libSoX version code."""
11
+ return lib_version(14, 4, 2)
12
+
13
+
14
+ def int_min(bits: int) -> int:
15
+ """Returns the smallest (negative) value storable in a twos-complement signed
16
+ integer with the specified number of bits, cast to an unsigned integer.
17
+
18
+ for example, SOX_INT_MIN(8) = 0x80, SOX_INT_MIN(16) = 0x8000, etc.
19
+ @param bits Size of value for which to calculate minimum.
20
+ @returns the smallest (negative) value storable in a twos-complement signed
21
+ integer with the specified number of bits, cast to an unsigned integer.
22
+ """
23
+ return 1 << ((bits) - 1)
24
+
25
+
26
+ def int_max(bits: int) -> int:
27
+ """Returns the largest (positive) value storable in a twos-complement signed
28
+ integer with the specified number of bits, cast to an unsigned integer.
29
+
30
+ for example, SOX_INT_MAX(8) = 0x7F, SOX_INT_MAX(16) = 0x7FFF, etc.
31
+ @param bits Size of value for which to calculate maximum.
32
+ @returns the largest (positive) value storable in a twos-complement signed
33
+ integer with the specified number of bits, cast to an unsigned integer.
34
+ """
35
+ return (-1 + 2**32) >> (33 - (bits))
36
+
37
+
38
+ def uint_max(bits: int) -> int:
39
+ """Returns the largest value storable in an unsigned integer with the specified
40
+ number of bits;
41
+
42
+ for example, SOX_UINT_MAX(8) = 0xFF, SOX_UINT_MAX(16) = 0xFFFF, etc.
43
+ @param bits Size of value for which to calculate maximum.
44
+ @returns the largest value storable in an unsigned integer with the specified
45
+ number of bits.
46
+ """
47
+ return int_min(bits) | int_max(bits)
cysox/utils.pyi ADDED
@@ -0,0 +1,21 @@
1
+ # Type stubs for cysox.utils
2
+
3
+ def lib_version(major: int, minor: int, patch: int) -> int:
4
+ """Compute a 32-bit integer API version from three 8-bit parts."""
5
+ ...
6
+
7
+ def lib_version_code() -> int:
8
+ """Get the current libSoX version code."""
9
+ ...
10
+
11
+ def int_min(bits: int) -> int:
12
+ """Returns the smallest (negative) value storable in a twos-complement signed integer."""
13
+ ...
14
+
15
+ def int_max(bits: int) -> int:
16
+ """Returns the largest (positive) value storable in a twos-complement signed integer."""
17
+ ...
18
+
19
+ def uint_max(bits: int) -> int:
20
+ """Returns the largest value storable in an unsigned integer."""
21
+ ...