acids-transforms 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.
@@ -0,0 +1,667 @@
1
+ import torch
2
+ import math
3
+ from enum import Enum
4
+ from .stft import realtime_frames, overlap_add_frames, STFT, RealtimeSTFT
5
+ from .base import AudioTransform, InversionEnumType
6
+ from .sinebank import SineBank
7
+ from ..utils.tracing import opaque_op, is_fx_tracing, select_implementation
8
+ from ..utils.heapq import heappop, heappush
9
+ from ..utils.misc import frame, reshape_batches
10
+ from typing import Tuple, Dict, Union, List
11
+
12
+
13
+ __all__ = ['DGT', 'RealtimeDGT']
14
+
15
+ MAX_NFFT = 16384
16
+ eps = torch.finfo(torch.get_default_dtype()).eps
17
+
18
+
19
+ # PGHI (Prusa et al., 2017) : phases integrated from their time / frequency derivatives, estimated from the magnitude
20
+ # with a gaussian window. Integration follows a heap of magnitudes, which depends on values : while tracing, pghi and
21
+ # pghi_realtime are opaque operators, or pure approximations, depending on the backend (see utils.tracing).
22
+
23
+ def _phasegrad_offline(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int) -> Tuple[torch.Tensor, torch.Tensor]:
24
+ fmul = gamma/(hop_length * n_fft)
25
+ Y = torch.empty((mag.shape[0]+2, mag.shape[1]+2)).to(mag)
26
+ Y[1:-1, 1:-1] = torch.log(mag)
27
+ Y[0, :] = Y[1, :]
28
+ Y[-1, :] = Y[-2, :]
29
+ Y[:, 0] = Y[:, 1]
30
+ Y[:, -1] = Y[:, -2]
31
+ dxdw = (Y[1:-1, 2:]-Y[1:-1, :-2])/2
32
+ dxdt = (Y[2:, 1:-1]-Y[:-2, 1:-1])/2
33
+ # dxdt = (3 * Y[..., 2:, 1:-1] - 4 * Y[..., 1:-1, 1:-1] + Y[..., 0:-2, 1:-1])/2
34
+ fgradw = dxdw/fmul + (2*torch.pi*hop_length/n_fft) * \
35
+ torch.arange(int(n_fft/2)+1).unsqueeze(0).to(mag)
36
+ tgradw = -fmul*dxdt + torch.pi
37
+ return (tgradw, fgradw)
38
+
39
+
40
+ def _hgi_offline(X: torch.Tensor, tgradw: torch.Tensor, fgradw: torch.Tensor, abstol: float, tol: float) -> torch.Tensor:
41
+ spectrogram = X
42
+ phase = torch.zeros_like(spectrogram)
43
+ M2 = spectrogram.shape[0]
44
+ N = spectrogram.shape[1]
45
+ max_val = spectrogram.max() # Find maximum value to start integration
46
+ max_pos_f = torch.nonzero(spectrogram == max_val)[0]
47
+ magnitude_heap = [(-max_val, (max_pos_f[0], max_pos_f[1]))]
48
+ spectrogram[max_pos_f[0], max_pos_f[1]] = abstol
49
+ spectrogram = torch.where(
50
+ spectrogram < max_val * tol, torch.full_like(spectrogram, abstol), spectrogram)
51
+ while max_val > abstol:
52
+ while len(magnitude_heap) > 0: # Integrate around maximum value until reaching silence
53
+ max_val, max_pos = heappop(magnitude_heap)
54
+ col = max_pos[0]
55
+ row = max_pos[1]
56
+ N_pos = col+1, row
57
+ S_pos = col-1, row
58
+ E_pos = col, row+1
59
+ W_pos = col, row-1
60
+ if max_pos[0] < M2-1 and spectrogram[N_pos[0], N_pos[1]] > abstol:
61
+ phase[N_pos[0], N_pos[1]] = phase[max_pos[0], max_pos[1]] + \
62
+ (fgradw[max_pos[0], max_pos[1]] +
63
+ fgradw[N_pos[0], N_pos[1]])/2
64
+ heappush(magnitude_heap,
65
+ (-spectrogram[N_pos[0], N_pos[1]], N_pos))
66
+ spectrogram[N_pos[0], N_pos[1]] = abstol
67
+ if max_pos[0] > 0 and spectrogram[S_pos[0], S_pos[1]] > abstol:
68
+ phase[S_pos[0], S_pos[1]] = phase[max_pos[0], max_pos[1]] - \
69
+ (fgradw[max_pos[0], max_pos[1]] +
70
+ fgradw[S_pos[0], S_pos[1]])/2
71
+ heappush(magnitude_heap,
72
+ (-spectrogram[S_pos[0], S_pos[1]], S_pos))
73
+ spectrogram[S_pos[0], S_pos[1]] = abstol
74
+ if max_pos[1] < N-1 and spectrogram[E_pos[0], E_pos[1]] > abstol:
75
+ phase[E_pos[0], E_pos[1]] = phase[max_pos[0], max_pos[1]] + \
76
+ (tgradw[max_pos[0], max_pos[1]] +
77
+ tgradw[E_pos[0], E_pos[1]])/2
78
+ heappush(magnitude_heap,
79
+ (-spectrogram[E_pos[0], E_pos[1]], E_pos))
80
+ spectrogram[E_pos[0], E_pos[1]] = abstol
81
+ if max_pos[1] > 0 and spectrogram[W_pos[0], W_pos[1]] > abstol:
82
+ phase[W_pos[0], W_pos[1]] = phase[max_pos[0], max_pos[1]] - \
83
+ (tgradw[max_pos[0], max_pos[1]] +
84
+ tgradw[W_pos[0], W_pos[1]])/2
85
+ heappush(magnitude_heap,
86
+ (-spectrogram[W_pos[0], W_pos[1]], W_pos))
87
+ spectrogram[W_pos[0], W_pos[1]] = abstol
88
+ max_val = spectrogram.max()
89
+ max_pos_f = torch.nonzero(spectrogram == max_val)[0]
90
+ heappush(magnitude_heap, (-max_val, (max_pos_f[0], max_pos_f[1])))
91
+ spectrogram[max_pos_f[0], max_pos_f[1]] = abstol
92
+ return phase
93
+
94
+
95
+ def _phasegrad_realtime(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int) -> Tuple[torch.Tensor, torch.Tensor]:
96
+ """
97
+ taken from
98
+ https://github.com/andimarafioti/tifresi/blob/676db371d5c472a5f3199506bf3863367a2ecde4/tifresi/phase/modGabPhaseGrad.py#L77
99
+ a : length of time shift
100
+ g : window function
101
+ M : number of frequency channels
102
+ """
103
+ #if gamma is None: gamma = 2*torch.pi*((-n_fft**2/(8*torch.log(0.01)))**.5)**2
104
+ fmul = gamma/(hop_length * n_fft)
105
+ Y = torch.empty((mag.shape[0], mag.shape[1]+2,
106
+ mag.shape[2]+2), dtype=mag.dtype, device=mag.device)
107
+ Y[:, 1:-1, 1:-1] = torch.log(mag)
108
+ Y[:, :, 0] = Y[:, :, 1]
109
+ Y[:, :, -1] = Y[:, :, -2]
110
+ # time edges (read by dxdt) : left uninitialized, they gave random nans in pghi
111
+ Y[:, 0, :] = Y[:, 1, :]
112
+ Y[:, -1, :] = Y[:, -2, :]
113
+ dxdw = (Y[:, 1:-1, 2:] - Y[:, 1:-1, :-2])/2
114
+ dxdt = (3 * Y[..., 2:, 1:-1] - 4 * Y[..., 1:-1, 1:-1] + Y[..., 0:-2, 1:-1])/2
115
+ fgradw = dxdw/fmul + (2*torch.pi*hop_length/n_fft) * (torch.arange(math.floor(n_fft/2)+1, device=mag.device))
116
+ tgradw = -fmul * dxdt + torch.pi
117
+ return (tgradw, fgradw)
118
+
119
+
120
+ def _hgi_realtime(spectrogram: torch.Tensor, previous_phase: torch.Tensor, tgradw: torch.Tensor, fgradw: torch.Tensor, tol: float, eps: float) -> torch.Tensor:
121
+ abstol = torch.clamp(tol * spectrogram.max(), eps, None)
122
+ # Random init phase when X < abstol
123
+ phase = torch.zeros_like(spectrogram)
124
+ phase[1] = previous_phase
125
+ phase[2:] = torch.where(spectrogram[2:] > abstol, torch.zeros_like(
126
+ spectrogram[2:]), torch.randn_like(spectrogram[2:]))
127
+
128
+ # pad gradients for convenient indices
129
+ pad = torch.zeros(2, tgradw.shape[1], device=tgradw.device, dtype=tgradw.dtype)
130
+ tgradw = torch.cat([pad, tgradw], 0)
131
+ fgradw = torch.cat([pad, fgradw], 0)
132
+ spectrogram_history = spectrogram.clone()
133
+
134
+ for f in range(2, spectrogram.shape[0]):
135
+ # Create I set
136
+ max_val = spectrogram[f].max()
137
+ if max_val <= abstol:
138
+ continue
139
+
140
+ # Init heap
141
+ # zero_tensor = torch.zeros(1).long()
142
+ # one_tensor = torch.ones(1).long()
143
+ zero_tensor = torch.full((1,), f-1).long()
144
+ one_tensor = torch.full((1,), f).long()
145
+ max_val = spectrogram[f].max()
146
+
147
+ max_pos_f = torch.nonzero(spectrogram[f] == max_val)
148
+ magnitude_heap = [(-max_val, (one_tensor, max_pos_f[0].unsqueeze(0)))]
149
+ for item in torch.nonzero(spectrogram_history[f-1] > abstol):
150
+ heappush(
151
+ magnitude_heap, (-spectrogram_history[f-1, item[0]], (zero_tensor, item[0].unsqueeze(0))))
152
+ if len(magnitude_heap) == 0:
153
+ continue
154
+ while max_val > abstol:
155
+ while len(magnitude_heap) > 0:
156
+ max_val, max_pos = heappop(magnitude_heap)
157
+ if max_pos[0] == f-1:
158
+ E_pos = (max_pos[0]+1, max_pos[1])
159
+ if spectrogram[E_pos[0], E_pos[1]] > abstol:
160
+ phase[E_pos[0], E_pos[1]] = phase[max_pos[0], max_pos[1]] + \
161
+ 0.5 * (tgradw[max_pos[0], max_pos[1]] + tgradw[E_pos[0], E_pos[1]])
162
+ heappush(
163
+ magnitude_heap, (-spectrogram[E_pos[0], E_pos[1]], (E_pos[0], E_pos[1])))
164
+ spectrogram[E_pos[0], E_pos[1]] = abstol
165
+ if max_pos[0] == f:
166
+ if max_pos[1] + 1 < spectrogram.shape[1]:
167
+ N_pos = (max_pos[0], max_pos[1]+1)
168
+ if spectrogram[N_pos[0], N_pos[1]] > abstol:
169
+ phase[N_pos[0], N_pos[1]] = phase[max_pos[0], max_pos[1]] + \
170
+ 0.5 * (fgradw[max_pos[0], max_pos[1]]+fgradw[N_pos[0], N_pos[1]])
171
+ heappush(
172
+ magnitude_heap, (-spectrogram[N_pos[0], N_pos[1]], (N_pos[0], N_pos[1])))
173
+ spectrogram[N_pos[0], N_pos[1]] = abstol
174
+ if max_pos[1] - 1 > 0:
175
+ S_pos = (max_pos[0], max_pos[1]-1)
176
+ if spectrogram[S_pos[0], S_pos[1]] > abstol:
177
+ phase[S_pos[0], S_pos[1]] = phase[max_pos[0], max_pos[1]] - \
178
+ 0.5 * (fgradw[max_pos[0], max_pos[1]]+fgradw[S_pos[0], S_pos[1]])
179
+ heappush(
180
+ magnitude_heap, (-spectrogram[S_pos[0], S_pos[1]], (S_pos[0], S_pos[1])))
181
+ spectrogram[S_pos[0], S_pos[1]] = abstol
182
+ max_val = spectrogram[f].max()
183
+ max_pos_f = torch.nonzero(spectrogram[f] == max_val)
184
+ heappush(magnitude_heap, (-max_val,
185
+ (one_tensor, max_pos_f[0].unsqueeze(0))))
186
+ spectrogram[f, max_pos_f[0]] = abstol
187
+ return phase[2:]
188
+
189
+
190
+ def _pghi(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
191
+ """phases (B, n_frames, n_bins) of magnitudes (B, n_frames, n_bins), integrated over the whole spectrogram."""
192
+ phases = []
193
+ for n in range(mag.size(0)):
194
+ m = torch.clamp(mag[n].clone(), eps, None)
195
+ tgradw, fgradw = _phasegrad_offline(m, gamma, hop_length, n_fft)
196
+ phases.append(_hgi_offline(m, tgradw, fgradw, eps, tolerance))
197
+ return torch.stack(phases)
198
+
199
+
200
+ def _pghi_realtime(mag: torch.Tensor, mag_buffer: torch.Tensor, phase_buffer: torch.Tensor, gamma: float,
201
+ hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
202
+ """phases (B, n_frames, n_bins) of magnitudes (B, n_frames, n_bins), integrated from the two previous frames
203
+ (magnitudes mag_buffer (B, 2, n_bins)) and the phase of the last one (phase_buffer (B, n_bins))."""
204
+ mag = torch.clamp(torch.cat([mag_buffer.to(mag), mag], -2), eps, None)
205
+ tgradw, fgradw = _phasegrad_realtime(mag, gamma, hop_length, n_fft)
206
+ phases = []
207
+ for i in range(mag.size(0)):
208
+ phases.append(_hgi_realtime(mag[i], phase_buffer[i].to(mag), tgradw[i], fgradw[i], tolerance, eps))
209
+ return torch.stack(phases)
210
+
211
+
212
+ # -- pure implementation : plain aten operations, without data-dependent control flow (traceable by make_fx)
213
+
214
+ def _phasegrad_pure(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int) -> Tuple[torch.Tensor, torch.Tensor]:
215
+ """phase derivatives (B, n_frames, n_bins) of log-magnitudes (B, n_frames, n_bins), as _phasegrad_offline : phase
216
+ increments from a frame to the next one, and from a bin to the next one."""
217
+ log_mag = torch.log(mag)
218
+ log_f = torch.cat([log_mag[..., :1], log_mag, log_mag[..., -1:]], -1)
219
+ log_t = torch.cat([log_mag[:, :1], log_mag, log_mag[:, -1:]], 1)
220
+ d_freq = (log_f[..., 2:] - log_f[..., :-2]) / 2
221
+ d_time = (log_t[:, 2:] - log_t[:, :-2]) / 2
222
+ fmul = gamma / (hop_length * n_fft)
223
+ bins = torch.arange(mag.size(-1), dtype=mag.dtype, device=mag.device)
224
+ along_frames = d_freq / fmul + (2 * math.pi * hop_length / n_fft) * bins
225
+ along_bins = -fmul * d_time + math.pi
226
+ return along_frames, along_bins
227
+
228
+
229
+ def _integrate_pure(mag: torch.Tensor, along_frames: torch.Tensor, along_bins: torch.Tensor,
230
+ first_phase: torch.Tensor, tolerance: float) -> torch.Tensor:
231
+ """phases (B, n_frames - 1, n_bins) of the frames following the first one (of phase first_phase (B, n_bins)) :
232
+ integrated over frames for each bin, then locked in frequency around the peaks of each frame (bins take the phase
233
+ of their nearest peak, integrated over bins). Bins below tolerance * max(mag) get random phases."""
234
+ increments = 0.5 * (along_frames[:, 1:] + along_frames[:, :-1])
235
+ # wrapped increments, summed in double precision, keep phases precise over long signals
236
+ increments = torch.remainder(increments + math.pi, 2 * math.pi) - math.pi
237
+ time_phase = first_phase.unsqueeze(1).double() + increments.double().cumsum(1)
238
+ mag, along_bins = mag[:, 1:], along_bins[:, 1:]
239
+ # phase locking around peaks
240
+ left, center, right = mag[..., :-2], mag[..., 1:-1], mag[..., 2:]
241
+ threshold = mag.amax((-2, -1), keepdim=True) * tolerance
242
+ is_peak = torch.nn.functional.pad(((center > left) & (center >= right) & (center > threshold)).to(torch.uint8),
243
+ [1, 1]).to(torch.bool)
244
+ n_bins = mag.size(-1)
245
+ index = torch.arange(n_bins, device=mag.device).expand(is_peak.shape)
246
+ none = torch.full_like(index, 4 * n_bins)
247
+ nearest_left = torch.where(is_peak, index, -none).cummax(-1).values
248
+ nearest_right = torch.where(is_peak, index, none).flip(-1).cummin(-1).values.flip(-1)
249
+ nearest = torch.where(index - nearest_left <= nearest_right - index, nearest_left, nearest_right).clamp(0, n_bins - 1)
250
+ bin_phase = torch.cat([torch.zeros_like(along_bins[..., :1]),
251
+ (0.5 * (along_bins[..., 1:] + along_bins[..., :-1])).double().cumsum(-1).to(along_bins)], -1)
252
+ locked = time_phase.gather(-1, nearest) + (bin_phase - bin_phase.gather(-1, nearest)).double()
253
+ phase = torch.where(is_peak.any(-1, keepdim=True), locked, time_phase)
254
+ phase = (torch.remainder(phase + math.pi, 2 * math.pi) - math.pi).to(mag.dtype)
255
+ return torch.where(mag > threshold, phase, 2 * math.pi * torch.rand_like(phase))
256
+
257
+
258
+ def _pghi_pure(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
259
+ """traceable approximation of _pghi : phases integrated over frames for each bin (cumulative sums), and locked in
260
+ frequency around spectral peaks, instead of the heap integration of PGHI."""
261
+ mag = mag.clamp(min=eps)
262
+ along_frames, along_bins = _phasegrad_pure(mag, gamma, hop_length, n_fft)
263
+ # the first frame is integrated from a zero phase, as a frame before the signal
264
+ zero = torch.zeros_like(mag[:, :1])
265
+ return _integrate_pure(torch.cat([zero + eps, mag], 1), torch.cat([along_frames[:, :1], along_frames], 1),
266
+ torch.cat([along_bins[:, :1], along_bins], 1), zero[:, 0], tolerance)
267
+
268
+
269
+ def _pghi_realtime_pure(mag: torch.Tensor, mag_buffer: torch.Tensor, phase_buffer: torch.Tensor, gamma: float,
270
+ hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
271
+ """traceable approximation of _pghi_realtime (see _pghi_pure), continued from the last frame (magnitudes
272
+ mag_buffer (B, 2, n_bins), phase of the last one phase_buffer (B, n_bins))."""
273
+ full = torch.cat([mag_buffer.to(mag), mag], -2).clamp(min=eps)
274
+ # gamma of the gaussian window of the DGT (as DGT._get_gamma) : the gamma of RealtimeDGT (used by _pghi_realtime)
275
+ # gives wrong phase derivatives
276
+ gamma = 2 * math.pi * (-n_fft ** 2 / (8 * math.log(0.01)))
277
+ along_frames, along_bins = _phasegrad_pure(full, gamma, hop_length, n_fft)
278
+ return _integrate_pure(full[:, 1:], along_frames[:, 1:], along_bins[:, 1:], phase_buffer.to(mag), tolerance)
279
+
280
+
281
+ # -- opaque operators for make_fx, wrapping the exact implementations (see utils.tracing)
282
+
283
+ def _pghi_fake(mag, gamma, hop_length, n_fft, eps, tolerance):
284
+ return torch.empty_like(mag)
285
+
286
+
287
+ def _pghi_realtime_fake(mag, mag_buffer, phase_buffer, gamma, hop_length, n_fft, eps, tolerance):
288
+ return torch.empty_like(mag)
289
+
290
+
291
+ _pghi_op = opaque_op("acids_transforms::pghi", _pghi_fake)(_pghi)
292
+ _pghi_realtime_op = opaque_op("acids_transforms::pghi_realtime", _pghi_realtime_fake)(_pghi_realtime)
293
+
294
+
295
+ @torch.jit.unused
296
+ def _pghi_eager(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
297
+ return select_implementation(_pghi, _pghi_pure, _pghi_op)(mag, gamma, hop_length, n_fft, eps, tolerance)
298
+
299
+
300
+ @torch.jit.unused
301
+ def _pghi_realtime_eager(mag: torch.Tensor, mag_buffer: torch.Tensor, phase_buffer: torch.Tensor, gamma: float,
302
+ hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
303
+ implementation = select_implementation(_pghi_realtime, _pghi_realtime_pure, _pghi_realtime_op)
304
+ return implementation(mag, mag_buffer, phase_buffer, gamma, hop_length, n_fft, eps, tolerance)
305
+
306
+
307
+ def pghi(mag: torch.Tensor, gamma: float, hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
308
+ """PGHI phases of magnitudes (B, n_frames, n_bins). Exact in eager mode and with TorchScript ; while tracing, an
309
+ opaque operator or a pure approximation, depending on the backend (see utils.tracing)."""
310
+ if torch.jit.is_scripting():
311
+ return _pghi(mag, gamma, hop_length, n_fft, eps, tolerance)
312
+ return _pghi_eager(mag, gamma, hop_length, n_fft, eps, tolerance)
313
+
314
+
315
+ def pghi_realtime(mag: torch.Tensor, mag_buffer: torch.Tensor, phase_buffer: torch.Tensor, gamma: float,
316
+ hop_length: int, n_fft: int, eps: float, tolerance: float) -> torch.Tensor:
317
+ """frame-wise PGHI phases of magnitudes (B, n_frames, n_bins), continued from the previous frames. Exact in eager
318
+ mode and with TorchScript ; while tracing, an opaque operator or a pure approximation (see utils.tracing)."""
319
+ if torch.jit.is_scripting():
320
+ return _pghi_realtime(mag, mag_buffer, phase_buffer, gamma, hop_length, n_fft, eps, tolerance)
321
+ return _pghi_realtime_eager(mag, mag_buffer, phase_buffer, gamma, hop_length, n_fft, eps, tolerance)
322
+
323
+
324
+ class DGT_INVERSION_MODES(Enum):
325
+ KEEP_INPUT = 0
326
+ GRIFFIN_LIM = 1
327
+ PGHI = 2
328
+ RANDOM = 3
329
+
330
+
331
+ class DGT(STFT):
332
+
333
+ def __repr__(self):
334
+ repr_str = f"DGT(n_fft={self.n_fft}, hop_length={self.hop_length}, " \
335
+ f"inversion_mode = {self.inversion_mode})"
336
+ return repr_str
337
+
338
+ def __init__(self, sr: int = 44100, n_fft: int = 1024, hop_length: int = 256, dtype: torch.dtype = None,
339
+ inversion_mode: InversionEnumType = "pghi", tolerance: float = 1.e-2):
340
+ AudioTransform.__init__(self, sr)
341
+ dtype = dtype or torch.get_default_dtype()
342
+ self.register_buffer("window", torch.zeros(MAX_NFFT))
343
+ self.register_buffer("inv_window", torch.zeros(MAX_NFFT))
344
+ self.register_buffer("eps", torch.tensor(
345
+ torch.finfo(dtype).eps, dtype=dtype))
346
+ self.register_buffer("phase_buffer", torch.zeros(0))
347
+ self.register_buffer("tolerance", torch.tensor(tolerance))
348
+ self.sinebank = SineBank()
349
+ if (n_fft is not None):
350
+ assert hop_length is not None, "n_fft and hop_length must be given together"
351
+ if (hop_length is not None):
352
+ assert n_fft is not None, "n_fft and hop_length must be given together"
353
+ if (n_fft is not None) and (hop_length is not None):
354
+ self.set_params(n_fft, hop_length)
355
+ if inversion_mode in type(self).get_inversion_modes():
356
+ self.inversion_mode = inversion_mode
357
+ else:
358
+ raise ValueError("Inversion mode %s not known" % inversion_mode)
359
+
360
+ def realtime(self):
361
+ inversion_mode = self.inversion_mode if self.inversion_mode in RealtimeDGT.get_inversion_modes() else "pghi"
362
+ return RealtimeDGT(sr=self.sr, n_fft=self.n_fft, hop_length=self.hop_length, inversion_mode = inversion_mode)
363
+
364
+ @property
365
+ def ratio(self):
366
+ return self.hop_length
367
+
368
+ @torch.jit.export
369
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
370
+ x, batch_shape = reshape_batches(x, -1)
371
+ window = self.window[:self.n_fft]
372
+ x_dgt = torch.stft(x, n_fft=int(self.n_fft), hop_length=int(self.hop_length), window=window, return_complex=True, onesided=True).transpose(-2, -1)
373
+ self._replace_phase_buffer(x_dgt.angle())
374
+ return x_dgt.reshape(batch_shape + x_dgt.shape[-2:])
375
+
376
+ @torch.jit.export
377
+ def forward_with_time(self, x: torch.Tensor, time: torch.Tensor):
378
+ transform = self.forward(x)
379
+ n_chunks = transform.size(-2)
380
+ shifts = torch.arange(n_chunks, device=transform.device) * self.hop_length / self.sr
381
+ new_shape = [t for t in transform.shape[:-2]]
382
+ new_shape.append(n_chunks)
383
+ new_strides = [0] * (x.ndim-1)
384
+ new_strides.append(1)
385
+ shifts = shifts.as_strided(new_shape, new_strides)
386
+ new_time = shifts + time.unsqueeze(-1)
387
+ return transform, new_time
388
+
389
+ @torch.jit.export
390
+ def invert(self, x: torch.Tensor, inversion_mode: InversionEnumType = None) -> torch.Tensor:
391
+ x, batch_shape = reshape_batches(x, -2)
392
+ if not torch.is_complex(x):
393
+ x_inv = self.invert_without_phase(x, inversion_mode)
394
+ else:
395
+ # torch.istft divides by the overlap of squared windows : with the analysis window, it gives the dual window
396
+ window = self.window[:self.n_fft]
397
+ x_inv = torch.istft(x.transpose(-2, -1), n_fft=self.n_fft, hop_length=self.hop_length, window=window, onesided=True)
398
+ return x_inv.reshape(batch_shape + x_inv.shape[-1:])
399
+
400
+ @torch.jit.export
401
+ def set_params(self, n_fft: int, hop_length: int) -> None:
402
+ self.n_fft = int(n_fft)
403
+ self.hop_length = int(hop_length)
404
+ self.window.zero_()
405
+ self.inv_window.zero_()
406
+ self.window[:self.n_fft] = self._get_window()
407
+ self.inv_window[:self.n_fft] = self._get_dual_window()
408
+ self.gamma = self._get_gamma()
409
+
410
+ def _get_gamma(self) -> float:
411
+ return 2*torch.pi*((-self.n_fft**2/(8*math.log(0.01)))**.5)**2
412
+
413
+ def _get_window(self) -> torch.FloatTensor:
414
+ lambda_ = (-self.n_fft**2/(8*math.log(0.01)))**.5
415
+ n = torch.arange(0, 2*self.n_fft+1) - (2*self.n_fft) / 2
416
+ w = torch.exp(-n**2 / (2 * (lambda_ * 2)**2))
417
+ return w[1:2*self.n_fft+1:2]
418
+
419
+ def _get_dual_window(self) -> torch.Tensor:
420
+ return self._canonical_dual_window()
421
+
422
+ def _canonical_dual_window(self) -> torch.Tensor:
423
+ gsynth = torch.zeros(self.n_fft)
424
+ for l in range(self.n_fft):
425
+ denom = 0
426
+ for n in range(int(-self.n_fft//self.hop_length), int(self.n_fft//self.hop_length + 1)):
427
+ dl = l - n*self.hop_length
428
+ if dl >= 0 and dl < self.n_fft:
429
+ denom += self.window[dl]**2
430
+ gsynth[l] = self.window[l]/denom
431
+ return gsynth
432
+
433
+ def invert_without_phase(self, x: torch.Tensor, inversion_mode: InversionEnumType = None) -> torch.Tensor:
434
+ # analysis window : torch.istft computes the dual window (see invert)
435
+ window = self.window[:self.n_fft]
436
+ phase = torch.tensor(0)
437
+ if inversion_mode is None:
438
+ inversion_mode = self.inversion_mode
439
+ if (inversion_mode == "keep_input"):
440
+ phase = self._get_phase_buffer(x)
441
+ if phase.shape[0] == 0:
442
+ phase = torch.pi * 2 * torch.rand_like(x)
443
+ elif (inversion_mode == "griffin_lim"):
444
+ return self.griffin_lim(x)
445
+ elif (inversion_mode == "pghi"):
446
+ phase = self.pghi(x, float(self.tolerance))
447
+ elif (inversion_mode == "random"):
448
+ phase = torch.pi * 2 * torch.rand_like(x)
449
+ elif (inversion_mode == "sinebank"):
450
+ return torch.istft(self.get_sinebank_inversion(x, False).transpose(-2, -1), n_fft=int(self.n_fft),
451
+ hop_length=int(self.hop_length), window=window, onesided=True)
452
+ else:
453
+ raise ValueError("inversion mode %s not valid." %
454
+ self.inversion_mode)
455
+ x = x * torch.exp(phase * torch.full(phase.size(),
456
+ 1j, device=phase.device))
457
+ return torch.istft(x.transpose(-2, -1), n_fft=int(self.n_fft), hop_length=int(self.hop_length), window=window, onesided=True)
458
+
459
+ def get_sinebank_inversion(self, magnitude: torch.Tensor, keep_state: bool = True) -> torch.Tensor:
460
+ """complex spectrum of the sinusoidal resynthesis of magnitude (see SineBank)."""
461
+ return self.sinebank(magnitude, self.window[:self.n_fft], self.n_fft, self.hop_length, self.sr, keep_state)
462
+
463
+ def pghi(self, mag: torch.Tensor, tolerance: float = 1.e-4) -> torch.Tensor:
464
+ """PGHI phases of magnitudes (..., n_frames, n_bins) (traced by make_fx as a single node)."""
465
+ mag, batch_shape = reshape_batches(mag, -2)
466
+ phase = pghi(mag, self.gamma, self.hop_length, self.n_fft, float(self.eps), tolerance)
467
+ return phase.reshape(batch_shape + phase.shape[-2:])
468
+
469
+ @staticmethod
470
+ def get_inversion_modes():
471
+ return ["pghi", "griffin_lim", "random", "keep_input", "sinebank"]
472
+
473
+ # keep_input depends on the last forward call (PGHI and sine bank are traced as opaque operators)
474
+ untracable_inversion_modes = ["keep_input"]
475
+
476
+ class RealtimeDGT(DGT):
477
+ def __init__(self, sr: int = 44100, n_fft=1024, hop_length=256, dtype=None, batch_size: Union[int, List[int]] = 2, inversion_mode: InversionEnumType = "pghi"):
478
+ super().__init__(sr=sr, n_fft=n_fft, hop_length=hop_length, dtype=dtype, inversion_mode=inversion_mode)
479
+ if isinstance(batch_size, int):
480
+ self.batch_size = [batch_size]
481
+ else:
482
+ self.batch_size = batch_size
483
+
484
+ self.register_buffer(
485
+ 'hgi_mag_buffer', torch.zeros(*self.batch_size, 2, n_fft//2+1))
486
+ self.register_buffer("hgi_phase_buffer",
487
+ torch.zeros(*self.batch_size, n_fft//2+1))
488
+
489
+ def __repr__(self):
490
+ repr_str = f"RealtimeDGT(n_fft={self.n_fft}, hop_length={self.hop_length}" \
491
+ f"inversion_mode = {self.inversion_mode})"
492
+ return repr_str
493
+
494
+ @staticmethod
495
+ def get_inversion_modes():
496
+ return ['random', 'pghi', 'keep_input', 'sinebank']
497
+
498
+ @classmethod
499
+ def _tracing_input(cls, transform: AudioTransform, x: torch.Tensor) -> torch.Tensor:
500
+ """frame-wise transforms take frames (..., n_frames, n_fft), as given by OverlapAdd."""
501
+ return realtime_frames(x, transform.n_fft, transform.hop_length)
502
+
503
+ @classmethod
504
+ def _tracing_output(cls, transform: AudioTransform, y: torch.Tensor) -> torch.Tensor:
505
+ return overlap_add_frames(y, transform.hop_length)
506
+
507
+ @classmethod
508
+ def _before_tracing(cls, transform: AudioTransform, x: torch.Tensor) -> None:
509
+ # PGHI buffers are reset when the batch shape changes : resetting them inside a trace would store symbolic
510
+ # shapes in the module, that leak into later traces
511
+ transform.reset(list(x.shape[:-2]))
512
+
513
+ def _get_dual_window(self) -> torch.Tensor:
514
+ # synthesis frames are overlap-added by OverlapAdd, that compensates a gain of n_fft / hop_length
515
+ return self._canonical_dual_window() * (float(self.n_fft) / self.hop_length)
516
+
517
+ @torch.jit.export
518
+ def get_batch_size(self) -> List[int]:
519
+ return [int(b) for b in self.batch_size]
520
+
521
+ @torch.jit.export
522
+ def set_batch_size(self, batch_size: Union[int, List[int]]):
523
+ self.reset(batch_size)
524
+
525
+ @torch.jit.export
526
+ def batch_size(self) -> List[int]:
527
+ return self.hgi_mag_buffer.shape[:-2]
528
+
529
+ @torch.jit.export
530
+ def reset(self, batch_size: Union[int, List[int]]) -> None:
531
+ if isinstance(batch_size, int):
532
+ self.batch_size = [batch_size]
533
+ else:
534
+ self.batch_size = torch.Size(batch_size)
535
+ device = self.hgi_mag_buffer.device
536
+ self.hgi_mag_buffer = torch.zeros(torch.Size(self.batch_size) + torch.Size([2, int(self.n_fft)//2+1]), device=device)
537
+ self.hgi_phase_buffer = torch.zeros(torch.Size(self.batch_size) + torch.Size([int(self.n_fft)//2+1]), device=device)
538
+ self.sinebank.reset()
539
+
540
+ @torch.jit.export
541
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
542
+ window = self.window[:self.n_fft]
543
+ x_dgt = torch.fft.rfft(x * window)
544
+ self._replace_phase_buffer(x_dgt.angle())
545
+ return x_dgt
546
+
547
+ @torch.jit.export
548
+ def forward_with_time(self, x: torch.Tensor, time: torch.Tensor):
549
+ return self(x), time
550
+
551
+ @torch.jit.export
552
+ def invert(self, x: torch.Tensor, inversion_mode: InversionEnumType = None) -> torch.Tensor:
553
+ if not torch.is_complex(x):
554
+ x_rec = self.invert_without_phase(x, inversion_mode)
555
+ return x_rec
556
+ else:
557
+ inv_window = self.inv_window[:self.n_fft]
558
+ return torch.fft.irfft(x) * inv_window
559
+
560
+ def invert_without_phase(self, x: torch.Tensor, inversion_mode: InversionEnumType = None) -> torch.Tensor:
561
+ batch_size = x.shape[:-2]
562
+ if batch_size != self.batch_size:
563
+ self.reset(batch_size)
564
+ window = self.inv_window[:self.n_fft]
565
+ phase = torch.tensor(0)
566
+ if inversion_mode is None:
567
+ inversion_mode = self.inversion_mode
568
+ if (inversion_mode == "keep_input"):
569
+ phase = self._get_phase_buffer(x)
570
+ if phase.shape[0] == 0:
571
+ phase = torch.pi * 2 * torch.rand_like(x)
572
+ elif (inversion_mode == "pghi"):
573
+ phase = self.pghi(x, float(self.tolerance))
574
+ elif (inversion_mode == "random"):
575
+ phase = torch.pi * 2 * torch.rand_like(x)
576
+ elif (inversion_mode == "sinebank"):
577
+ # phases of partials are continued from the last call
578
+ x = self.get_sinebank_inversion(x)
579
+ self.update_buffers(x)
580
+ return torch.fft.irfft(x) * window
581
+ else:
582
+ raise ValueError("inversion mode %s not valid." %
583
+ self.inversion_mode)
584
+ x = x * torch.exp(phase * torch.full(phase.shape,
585
+ 1j, device=phase.device))
586
+ self.update_buffers(x)
587
+ return torch.fft.irfft(x) * window
588
+
589
+ def update_buffers(self, x):
590
+ # traced tensors must not be stored in the module (they would leak in later calls)
591
+ if not torch.jit.is_scripting():
592
+ if is_fx_tracing():
593
+ return
594
+ if x.shape[-2] > 1:
595
+ self.hgi_mag_buffer = x[..., -2:, :].abs()
596
+ else:
597
+ self.hgi_mag_buffer = torch.stack(
598
+ [self.hgi_mag_buffer[..., 1, :], x[..., -1, :].abs()], -2)
599
+ self.hgi_phase_buffer = x[..., -1, :].angle()
600
+
601
+ def pghi(self, mag: torch.Tensor, tolerance: float = 1e-6) -> torch.Tensor:
602
+ """PGHI phases of magnitudes (..., n_frames, n_bins), continued from the previous frames (traced by make_fx as
603
+ a single node)."""
604
+ mag, batch_shape = reshape_batches(mag, -2)
605
+ hgi_mag_buffer, _ = reshape_batches(self.hgi_mag_buffer, -2)
606
+ hgi_phase_buffer, _ = reshape_batches(self.hgi_phase_buffer, -1)
607
+ phases = pghi_realtime(mag, hgi_mag_buffer, hgi_phase_buffer, self.gamma, self.hop_length, self.n_fft,
608
+ float(self.eps), tolerance)
609
+ return phases.reshape(batch_shape + phases.shape[1:])
610
+
611
+ def _get_gamma(self) -> float:
612
+ return (-self.n_fft**2/(8*math.log(0.01)))**.5
613
+ # return 2*torch.pi*((-self.n_fft**2/(8*math.log(0.01)))**.5)**2
614
+ # return - (torch.pi / 4) * (self.n_fft**2 / math.log(self.window[0])
615
+
616
+ def test_forward(self, x: torch.Tensor, time: torch.Tensor = None):
617
+ x = frame(x, self.n_fft, self.hop_length, -1)
618
+ transform = []
619
+ for i in range(x.shape[-2]):
620
+ transform.append(self(x[..., i, :]))
621
+ transform = torch.stack(transform, -2)
622
+ if time is None:
623
+ return transform
624
+ else:
625
+ return transform, None
626
+
627
+ # Tests
628
+ def test_inversion(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
629
+ from .oadd import OverlapAdd
630
+ self.reset(x.shape[:-1])
631
+ n_fft = self.n_fft
632
+ chunk_size = n_fft * 4
633
+ hop_length = self.hop_length
634
+ oadd = OverlapAdd(n_fft, hop_length)
635
+ outs = {}
636
+ x_framed = x.split(chunk_size, -1)
637
+ # test direct inversion
638
+ outs["direct"] = []
639
+ for n in range(len(x_framed)):
640
+ x_in = oadd(x_framed[n])
641
+ x_t = self(x_in)
642
+ x_i = oadd.invert(self.invert(x_t))
643
+ outs['direct'].append(x_i)
644
+ outs['direct'] = torch.cat(outs['direct'], -1)
645
+ # test spectrogram inversion modes
646
+ for inv_mode in self.get_inversion_modes():
647
+ self.inversion_mode = inv_mode
648
+ oadd = OverlapAdd(n_fft, hop_length)
649
+ outs[inv_mode] = []
650
+ for n in range(len(x_framed)):
651
+ x_in = oadd(x_framed[n])
652
+ x_t = self(x_in)
653
+ x_i = oadd.invert(self.invert(x_t.abs(), inversion_mode=inv_mode))
654
+ outs[inv_mode].append(x_i)
655
+ outs[inv_mode] = torch.cat(outs[inv_mode], -1)
656
+ return outs
657
+
658
+
659
+ @classmethod
660
+ def test_scripted_transform(cls, transform: AudioTransform, invert: bool = True):
661
+ x = torch.zeros(2, 1, transform.n_fft)
662
+ transform.reset(list(x.shape[:-1]))
663
+ x_t = transform(x)
664
+ if cls.invertible:
665
+ x_inv = transform.invert(x_t)
666
+ for inv_type in cls.get_inversion_modes():
667
+ x_inv = transform.invert(x_t.abs(), inversion_mode=inv_type)