cysox 0.1.4__cp311-cp311-macosx_11_0_arm64.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/__init__.py +61 -0
- cysox/__init__.pyi +223 -0
- cysox/__main__.py +102 -0
- cysox/audio.py +528 -0
- cysox/fx/__init__.py +113 -0
- cysox/fx/base.py +168 -0
- cysox/fx/convert.py +128 -0
- cysox/fx/eq.py +94 -0
- cysox/fx/filter.py +131 -0
- cysox/fx/reverb.py +215 -0
- cysox/fx/time.py +248 -0
- cysox/fx/volume.py +98 -0
- cysox/sox.cpython-311-darwin.so +0 -0
- cysox/utils.py +47 -0
- cysox/utils.pyi +21 -0
- cysox-0.1.4.dist-info/METADATA +339 -0
- cysox-0.1.4.dist-info/RECORD +21 -0
- cysox-0.1.4.dist-info/WHEEL +6 -0
- cysox-0.1.4.dist-info/entry_points.txt +2 -0
- cysox-0.1.4.dist-info/licenses/LICENSE +21 -0
- cysox-0.1.4.dist-info/top_level.txt +1 -0
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
|
+
...
|