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 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
@@ -0,0 +1,4 @@
1
+ from noise.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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