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.
- acids_transforms/__init__.py +3 -0
- acids_transforms/transforms/__init__.py +9 -0
- acids_transforms/transforms/base.py +232 -0
- acids_transforms/transforms/dgt.py +667 -0
- acids_transforms/transforms/mel.py +77 -0
- acids_transforms/transforms/misc.py +212 -0
- acids_transforms/transforms/norm.py +99 -0
- acids_transforms/transforms/oadd.py +106 -0
- acids_transforms/transforms/raw.py +316 -0
- acids_transforms/transforms/sinebank.py +279 -0
- acids_transforms/transforms/spectral_repr.py +551 -0
- acids_transforms/transforms/stft.py +788 -0
- acids_transforms/utils/__init__.py +2 -0
- acids_transforms/utils/heapq.py +59 -0
- acids_transforms/utils/misc.py +237 -0
- acids_transforms/utils/tracing.py +82 -0
- acids_transforms-1.0.0.dist-info/METADATA +180 -0
- acids_transforms-1.0.0.dist-info/RECORD +21 -0
- acids_transforms-1.0.0.dist-info/WHEEL +5 -0
- acids_transforms-1.0.0.dist-info/licenses/LICENSE +674 -0
- acids_transforms-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -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)
|