noisetool 1.0.0__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.
- noise/__init__.py +36 -0
- noise/__main__.py +4 -0
- noise/analysis.py +176 -0
- noise/cli.py +1439 -0
- noise/completion.py +65 -0
- noise/config.py +212 -0
- noise/effects.py +292 -0
- noise/eqviz.py +69 -0
- noise/formats.py +66 -0
- noise/generator.py +284 -0
- noise/interactive.py +214 -0
- noise/lufs.py +154 -0
- noise/preview.py +87 -0
- noise/py.typed +0 -0
- noise/ui.py +92 -0
- noise/utils.py +39 -0
- noisetool-1.0.0.dist-info/METADATA +497 -0
- noisetool-1.0.0.dist-info/RECORD +22 -0
- noisetool-1.0.0.dist-info/WHEEL +5 -0
- noisetool-1.0.0.dist-info/entry_points.txt +2 -0
- noisetool-1.0.0.dist-info/licenses/LICENSE +21 -0
- noisetool-1.0.0.dist-info/top_level.txt +1 -0
noise/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from noise.generator import (
|
|
2
|
+
generate_blue_noise,
|
|
3
|
+
generate_brown_noise,
|
|
4
|
+
generate_grey_noise,
|
|
5
|
+
generate_pink_noise,
|
|
6
|
+
generate_violet_noise,
|
|
7
|
+
generate_white_noise,
|
|
8
|
+
mix_noise,
|
|
9
|
+
)
|
|
10
|
+
from noise.lufs import (
|
|
11
|
+
measure_loudness,
|
|
12
|
+
normalize_loudness,
|
|
13
|
+
)
|
|
14
|
+
from noise.utils import (
|
|
15
|
+
SAMPLE_RATE,
|
|
16
|
+
save_flac,
|
|
17
|
+
save_wav,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.0"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"generate_white_noise",
|
|
24
|
+
"generate_pink_noise",
|
|
25
|
+
"generate_brown_noise",
|
|
26
|
+
"generate_blue_noise",
|
|
27
|
+
"generate_violet_noise",
|
|
28
|
+
"generate_grey_noise",
|
|
29
|
+
"mix_noise",
|
|
30
|
+
"measure_loudness",
|
|
31
|
+
"normalize_loudness",
|
|
32
|
+
"SAMPLE_RATE",
|
|
33
|
+
"save_wav",
|
|
34
|
+
"save_flac",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
noise/__main__.py
ADDED
noise/analysis.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import asdict, dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AudioStats:
|
|
13
|
+
duration_s: float
|
|
14
|
+
n_samples: int
|
|
15
|
+
n_channels: int
|
|
16
|
+
sample_rate: int
|
|
17
|
+
peak: float
|
|
18
|
+
peak_db: float
|
|
19
|
+
rms: float
|
|
20
|
+
rms_db: float
|
|
21
|
+
crest_factor: float
|
|
22
|
+
dc_offset: float
|
|
23
|
+
bit_depth: int = 24
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict[str, Any]:
|
|
26
|
+
return asdict(self)
|
|
27
|
+
|
|
28
|
+
def to_json(self, indent: int = 2) -> str:
|
|
29
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
30
|
+
|
|
31
|
+
def to_csv(self, delimiter: str = ",") -> str:
|
|
32
|
+
"""Export stats as CSV string."""
|
|
33
|
+
import io
|
|
34
|
+
|
|
35
|
+
buf = io.StringIO()
|
|
36
|
+
buf.write(delimiter.join(["property", "value"]) + "\n")
|
|
37
|
+
for prop, val in self.to_table():
|
|
38
|
+
buf.write(delimiter.join([prop, val]) + "\n")
|
|
39
|
+
return buf.getvalue()
|
|
40
|
+
|
|
41
|
+
def to_table(self) -> list[tuple[str, str]]:
|
|
42
|
+
return [
|
|
43
|
+
("Duration", f"{self.duration_s:.2f}s"),
|
|
44
|
+
("Samples", f"{self.n_samples:,}"),
|
|
45
|
+
("Channels", str(self.n_channels)),
|
|
46
|
+
("Sample Rate", f"{self.sample_rate} Hz"),
|
|
47
|
+
("Bit Depth", str(self.bit_depth)),
|
|
48
|
+
("Peak", f"{self.peak:.6f}"),
|
|
49
|
+
("Peak (dBFS)", f"{self.peak_db:.2f}"),
|
|
50
|
+
("RMS", f"{self.rms:.6f}"),
|
|
51
|
+
("RMS (dBFS)", f"{self.rms_db:.2f}"),
|
|
52
|
+
("Crest Factor", f"{self.crest_factor:.2f} dB"),
|
|
53
|
+
("DC Offset", f"{self.dc_offset:.8f}"),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def compute_stats(data: np.ndarray, sample_rate: int) -> AudioStats:
|
|
58
|
+
"""Compute comprehensive audio statistics.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
data: Audio array, shape (n_samples, n_channels).
|
|
62
|
+
sample_rate: Sample rate in Hz.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
AudioStats dataclass with all computed values.
|
|
66
|
+
"""
|
|
67
|
+
if data.ndim == 1:
|
|
68
|
+
data = data.reshape(-1, 1)
|
|
69
|
+
|
|
70
|
+
n_samples, n_channels = data.shape
|
|
71
|
+
duration = n_samples / sample_rate
|
|
72
|
+
peak = float(np.max(np.abs(data)))
|
|
73
|
+
peak_db = 20.0 * np.log10(max(peak, 1e-10))
|
|
74
|
+
rms = float(np.sqrt(np.mean(data**2)))
|
|
75
|
+
rms_db = 20.0 * np.log10(max(rms, 1e-10))
|
|
76
|
+
crest = peak_db - rms_db
|
|
77
|
+
dc = float(np.mean(data))
|
|
78
|
+
|
|
79
|
+
return AudioStats(
|
|
80
|
+
duration_s=duration,
|
|
81
|
+
n_samples=n_samples,
|
|
82
|
+
n_channels=n_channels,
|
|
83
|
+
sample_rate=sample_rate,
|
|
84
|
+
peak=peak,
|
|
85
|
+
peak_db=peak_db,
|
|
86
|
+
rms=rms,
|
|
87
|
+
rms_db=rms_db,
|
|
88
|
+
crest_factor=crest,
|
|
89
|
+
dc_offset=dc,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ascii_spectrum(data: np.ndarray, sample_rate: int, width: int = 60, height: int = 10) -> str:
|
|
94
|
+
"""Generate an ASCII spectrum (frequency-domain) visualization.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
data: Audio array, shape (n_samples, n_channels). Mono preferred.
|
|
98
|
+
sample_rate: Sample rate in Hz.
|
|
99
|
+
width: Character width of the output.
|
|
100
|
+
height: Character height (vertical resolution).
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A string containing the ASCII spectrum plot.
|
|
104
|
+
"""
|
|
105
|
+
samples = data[:, 0] if data.ndim > 1 else data
|
|
106
|
+
|
|
107
|
+
n = len(samples)
|
|
108
|
+
if n < 4:
|
|
109
|
+
return "[insufficient data for spectrum]"
|
|
110
|
+
|
|
111
|
+
window = np.hanning(n)
|
|
112
|
+
spectrum = np.abs(np.fft.rfft(samples * window))
|
|
113
|
+
spectrum_db = 20.0 * np.log10(spectrum / np.max(spectrum) + 1e-10)
|
|
114
|
+
spectrum_db = np.maximum(spectrum_db, -height * 3)
|
|
115
|
+
|
|
116
|
+
np.fft.rfftfreq(n, d=1.0 / sample_rate)
|
|
117
|
+
|
|
118
|
+
block_size = max(1, len(spectrum_db) // width)
|
|
119
|
+
binned = np.array(
|
|
120
|
+
[
|
|
121
|
+
np.max(spectrum_db[i : i + block_size])
|
|
122
|
+
for i in range(0, len(spectrum_db) - block_size + 1, block_size)
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
nyquist = sample_rate / 2
|
|
127
|
+
min_val = -height * 3
|
|
128
|
+
max_val = 0.0
|
|
129
|
+
lines: list[list[str]] = [[" "] * len(binned) for _ in range(height)]
|
|
130
|
+
|
|
131
|
+
for x, val in enumerate(binned):
|
|
132
|
+
normalized_pos = int((val - min_val) / (max_val - min_val) * (height - 1))
|
|
133
|
+
normalized_pos = max(0, min(height - 1, normalized_pos))
|
|
134
|
+
for y in range(normalized_pos, height):
|
|
135
|
+
if y >= 0 and y < height:
|
|
136
|
+
lines[y][x] = "\u2588"
|
|
137
|
+
|
|
138
|
+
# Add frequency labels
|
|
139
|
+
if len(binned) > 10:
|
|
140
|
+
labels = [
|
|
141
|
+
(0, "0"),
|
|
142
|
+
(len(binned) // 4, f"{nyquist / 4:.0f}Hz"),
|
|
143
|
+
(len(binned) // 2, f"{nyquist / 2:.0f}Hz"),
|
|
144
|
+
(3 * len(binned) // 4, f"{3 * nyquist / 4:.0f}Hz"),
|
|
145
|
+
]
|
|
146
|
+
for pos, label in labels:
|
|
147
|
+
if pos < len(binned):
|
|
148
|
+
for j, ch in enumerate(label):
|
|
149
|
+
if pos + j < len(binned):
|
|
150
|
+
lines[0][pos + j] = ch
|
|
151
|
+
|
|
152
|
+
return "\n".join("".join(line) for line in lines)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def save_csv_stats(data: np.ndarray, sample_rate: int, path: Path) -> Path:
|
|
156
|
+
"""Compute stats and save as CSV."""
|
|
157
|
+
import csv
|
|
158
|
+
|
|
159
|
+
stats = compute_stats(data, sample_rate)
|
|
160
|
+
with open(path, "w", newline="") as f:
|
|
161
|
+
writer = csv.writer(f)
|
|
162
|
+
writer.writerow(["property", "value"])
|
|
163
|
+
for prop, val in stats.to_table():
|
|
164
|
+
writer.writerow([prop, val])
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def save_json_stats(data: np.ndarray, sample_rate: int, path: Path) -> Path:
|
|
169
|
+
"""Compute stats and save as JSON.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Path to the saved JSON file.
|
|
173
|
+
"""
|
|
174
|
+
stats = compute_stats(data, sample_rate)
|
|
175
|
+
path.write_text(stats.to_json(), encoding="utf-8")
|
|
176
|
+
return path
|