File size: 6,338 Bytes
1a3ce68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import soundfile as sf
import torchaudio

def read_wav_segment(file_path, start=None, end=None, dtype="float32"):
    """
    Reads a specific segment from a .wav file efficiently.

    Args:
        file_path (str): Path to the .wav file.
        start (int): Start frame index.
        end (int): End frame index.

    Returns:
        numpy.ndarray: Audio data for the specified segment.
        int: Sample rate of the audio file.
    """
    # Open the .wav file
    if start is None or end is None:
        data, samplerate = sf.read(file_path, dtype=dtype)
    else:
        with sf.SoundFile(file_path) as audio_file:
            # Read only the required frames
            audio_file.seek(start)
            data = audio_file.read(frames=end-start, dtype=dtype)
            samplerate = audio_file.samplerate

    return data, samplerate

def get_audio_length(file_path):
    """
    Retrieves the length of an audio file in seconds and frames.

    Args:
        file_path (str): Path to the audio file.

    Returns:
        float: Length of the audio file in seconds.
        int: Total number of frames in the audio file.
        int: Sample rate of the audio file.
    """
    with sf.SoundFile(file_path) as audio_file:
        total_frames = len(audio_file)  # Total number of frames
        samplerate = audio_file.samplerate  # Sample rate
        duration = total_frames / samplerate  # Duration in seconds

    return duration, total_frames, samplerate

def taxonomy2track(input_class, num_instr=8):

    assert num_instr==8, "num_instr should be 8 for this function, the rest is not implemented yet"

    if input_class is None:
        return 'unknown'  
    if num_instr == 8:
        mapping = {0000: 'other', 1100: 'drums', 1200: 'drums', 1300: 'other', 2000: 'bass', 3000: 'guitar', 4100: 'piano', 4200: 'piano', 4300: 'piano', 4400: 'other', 4500: 'other', 4600: 'other', 4700: 'other', 4900: 'other', 5000: 'brass', 6100: 'strings', 6210: 'brass', 6220: 'brass', 8100: 'guitar', 8200: 'brass', 9000: 'vocals'}
    else:
        raise NotImplementedError()
    
    code_length = len(str(input_class))
    if code_length < 4:
        #pad zeros to the right to make it 4 digits
        input_class = int(str(input_class) + "0" * (4 - code_length))
 
    class_str = str(input_class)
    for i in range(len(class_str), 0, -1):
        general_class = int(class_str[:i] + "0" * (len(class_str) - i))
        if general_class in mapping:
            return mapping[general_class]  
       
    try:
        raise ValueError(f"No mapping found for input class {input_class} with num_instr {num_instr}")
    except ValueError as e:
        print(f"Error: {e}")
        return "other"  # Return a default value if no mapping is found

import torch
def efficient_roll(x, shift, dims=-1):
    """
    Efficiently roll tensor elements along a dimension without creating a full copy.
    
    Args:
        x: Input tensor
        shift: Number of places to roll (negative for left roll)
        dim: Dimension along which to roll
    
    Returns:
        Rolled tensor view where possible, minimal copy where necessary
    """
    if shift == 0:
        return x
    
    # Get the size of the dimension
    dim_size = x.size(dims)
    
    # Handle shift larger than dimension size
    shift = shift % dim_size
    if shift < 0:
        shift += dim_size
    
    # Create indices for the roll
    indices = torch.cat([torch.arange(dim_size-shift, dim_size), 
                         torch.arange(0, dim_size-shift)])
    
    # Use index_select for the roll
    return torch.index_select(x, dims, indices)

#import loudness
import numpy as np

def apply_loud_normalization(x, lufs=-23, sample_rate=44100,device=None):
    """
    x shaPe: (batch_size, channels, time)
    """

    in_shape= x.shape
    if x.ndim != 3:
        x=x.view(-1, in_shape[-2], in_shape[-1])  # Ensure x is 3D

    B, C, T = x.shape

    if device is None:
        device = x.device

    x_out = torch.zeros_like(x)
    #for b in range(B):
    #    x_i=x[b].cpu().numpy().T
    #    lufs_in=loudness.integrated_loudness(x_i, sample_rate)

    #    delta_loudness= lufs - lufs_in
    #    gain=np.power(10, delta_loudness / 20)  # Convert dB to linear gain

    #    x_out[b] = torch.tensor(x_i.T * gain, device=device)

    x=x.view(B* C,1, T)  # Ensure x is 3D

    loudness=torchaudio.functional.loudness(x+1e-5, sample_rate=sample_rate)
    delta_loudness = lufs - loudness
    gain= torch.pow(10, delta_loudness / 20)  # Convert dB to linear gain
    if gain.isnan().any():
        print("NaN detected in gain, setting to -30 dB")
        gain = torch.nan_to_num(gain, nan=-30.0)

    x_out = x * gain.view(B * C, 1, 1)  # Apply gain to each channel

    
    x_out = x_out.view(in_shape)

    return x_out



from utils.feature_extractors.dsp_features import compute_log_rms_gated_max, compute_crest_factor, compute_stereo_width, compute_stereo_imbalance, compute_log_spread

def apply_RMS_normalization(x, RMS_norm=-25, device=None, use_gate=False):
        if device is None:
            device = x.device

        RMS= torch.tensor(RMS_norm, device=device).view(1, 1, 1).repeat(x.shape[0],1,1)  # Use fixed RMS for evaluation

        x_RMS_ref=20*torch.log10(torch.sqrt(torch.mean(x**2, dim=(-1), keepdim=True).mean(dim=-2, keepdim=True)))
        if use_gate:
            x_RMS = compute_log_rms_gated_max(x).unsqueeze(-1)
        else:
            x_RMS=20*torch.log10(torch.sqrt(torch.mean(x**2, dim=(-1), keepdim=True).mean(dim=-2, keepdim=True)))
        

        gain= RMS - x_RMS
        gain_linear = 10 ** (gain / 20 + 1e-6)  # Convert dB gain to linear scale, adding a small value to avoid division by zero
        x=x* gain_linear

        return x


import pyloudnorm as pyln   

def loudness_normalize(audio, target_loudness=-23.0, sample_rate=44100):
    """
    Normalize the loudness of the audio to a target level.
    """

    pylnmeter = pyln.Meter(sample_rate)  # Create a meter for 44100 Hz sampling rate

    audio= np.array(audio, dtype=np.float32).T
    loudness = pylnmeter.integrated_loudness(audio)


    # loudness normalize audio to -12 dB LUFS
    loudness_normalized_audio = pyln.normalize.loudness(audio, loudness, -14.0)

    return  torch.tensor(loudness_normalized_audio.T, dtype=torch.float32)