File size: 7,302 Bytes
df5dab1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

import numpy as np
from . import main

def open_audio(path:str = None, lib:str = 'auto', normalize = True) -> tuple:
    """Opens audio from path, returns (audio, samplerate) tuple.
    
    Audio is returned as an array with normal volume range between -1, 1.
    
    Example of returned audio: 
    
    [
        [0.35, -0.25, ... -0.15, -0.15], 
    
        [0.31, -0.21, ... -0.11, -0.07]
    ]"""

    if path is None:
        from tkinter.filedialog import askopenfilename
        path = askopenfilename(title='select song', filetypes=[("mp3", ".mp3"),("wav", ".wav"),("flac", ".flac"),("ogg", ".ogg"),("wma", ".wma")])
    
    path=path.replace('\\', '/')

    if lib=='pedalboard.io':
        import pedalboard.io
        with pedalboard.io.AudioFile(path) as f:
            audio = f.read(f.frames)
            sr = f.samplerate
    
    elif lib=='librosa':
        import librosa
        audio, sr = librosa.load(path, sr=None, mono=False)
    
    elif lib=='soundfile':
        import soundfile
        audio, sr = soundfile.read(path)
        audio=audio.T
    
    elif lib=='madmom':
        import madmom
        audio, sr = madmom.io.audio.load_audio_file(path, dtype=float)
        audio=audio.T
    
    # elif lib=='pydub':
    #     from pydub import AudioSegment
    #     song=AudioSegment.from_file(filename)
    #     audio = song.get_array_of_samples()
    #     samplerate=song.frame_rate
    #     print(audio)
    #     print(filename)

    elif lib=='auto':
        for i in ('madmom', 'soundfile', 'librosa', 'pedalboard.io'):
            try: 
                audio,sr=open_audio(path, i)
                break
            except Exception as e:
                print(f'open_audio with {i}: {e}')
    
    if len(audio)>16: audio=np.array([audio, audio], copy=False)
    if normalize is True: 
        audio = np.clip(audio, -1, 1)
        audio = audio*(1/np.max(np.abs(audio)))
    return audio.astype(np.float32),sr
    
def _sr(sr):
    try: return int(sr)
    except (ValueError, TypeError): assert False, f"Audio is an array, but `sr` argument is not valid. If audio is an array, you have to provide samplerate as an integer in the `sr` argument. Currently sr = {sr} of type {type(sr)}"

def write_audio(audio:np.ndarray, sr:int, output:str, lib:str='auto', libs=('pedalboard.io', 'soundfile'), log = True):
        """"writes audio to path specified by output. Path should end with file extension, for example `folder/audio.mp3`"""
        if log is True: print(f'Writing {output}...', end=' ')
        assert _iterable(audio), f"audio should be an array/iterable object, but it is {type(audio)}"
        sr = _sr(sr)
        if not isinstance(audio, np.ndarray): audio = np.array(audio, copy=False)
        if lib=='pedalboard.io':
            #print(audio)
            import pedalboard.io
            with pedalboard.io.AudioFile(output, 'w', sr, audio.shape[0]) as f:
                f.write(audio)
        elif lib=='soundfile':
            audio=audio.T
            import soundfile
            soundfile.write(output, audio, sr)
            del audio
        elif lib=='auto':
            for i in libs:
                try: 
                    write_audio(audio=audio, sr=sr, output=output, lib=i, log = False)
                    break
                except Exception as e:
                    print(e)
            else: assert False, 'Failed to write audio, chances are there is something wrong with it...'
        if log is True: print(f'Done!')

def _iterable(a):
    try:
        _ = iter(a)
        return True
    except TypeError: return False

def _load(audio, sr:int = None, lib:str = 'auto', channels:int = 2, transpose3D:bool = False) -> tuple:
    """Automatically converts audio from path or any format to [[...],[...]] array. Returns (audio, samplerate) tuple."""
    # path
    if isinstance(audio, str): return(open_audio(path=audio, lib=lib))
    # array
    if _iterable(audio):
        if isinstance(audio, main.song):
            if sr is None: sr = audio.sr
            audio = audio.audio
        # sr is provided in a tuple
        if sr is None and len(audio) == 2:
            if not _iterable(audio[0]):
                sr = audio[0]
                audio = audio[1]
            elif not _iterable(audio[1]):
                sr = audio[1]
                audio = audio[0]
        if not isinstance(audio, np.ndarray): audio = np.array(audio, copy=False)
        sr = _sr(sr)
        if _iterable(audio[0]):
            # image
            if _iterable(audio[0][0]):
                audio2 = []
                if transpose3D is True: audio = audio.T
                for i in audio:
                    audio2.extend(_load(audio=i, sr=sr, lib=lib, channels=channels, transpose3D=transpose3D)[0])
                return audio2, sr
            # transposed
            if len(audio) > 16:
                audio = audio.T
                return _load(audio=audio, sr=sr, lib=lib, channels=channels, transpose3D=transpose3D)
            # multi channel
            elif isinstance(channels, int):
                if len(audio) >= channels:
                    return audio[:channels], sr
                # masked mono
                else: return np.array([audio[0] for _ in range(channels)], copy=False), sr
            else: return audio, sr
        else: 
            # mono
            return (np.array([audio for _ in range(channels)], copy=False) if channels is not None else audio), sr
    # unknown
    else: assert False, f"Audio should be either a string with path, an array/iterable object, or a song object, but it is {type(audio)}"

def _tosong(audio, sr=None):
    if isinstance(audio, main.song): return audio
    else: 
        audio, sr = _load(audio = audio, sr = sr)
        return main.song(audio=audio, sr = sr)

def _outputfilename(path:str = None, filename:str = None, suffix:str = None, ext:str = None):
    """If path has file extension, returns `path + suffix + ext`. Else returns `path + filename + suffix + .ext`. If nothing is specified, returns `output.mp3`"""
    if ext is not None:
        if not ext.startswith('.'): ext = '.'+ext
    if path is None: path = ''
    if path.endswith('/') or path.endswith('\\'): path=path[:-1]
    if '.' in path:
        path = path.split('.')
        if path[-1].lower() in ['mp3', 'wav', 'flac', 'ogg', 'wma', 'aac', 'ac3', 'aiff']:
            if ext is not None:
                path[-1] = ext
            if suffix is not None: path[len(path)-2]+=suffix
            return ''.join(path)
        else: path = ''.join(path)
    if filename is not None:
        filename = filename.replace('\\','/').split('/')[-1]
        if '.' in filename:
            filename = filename.split('.')
            if filename[-1].lower() in ['mp3', 'wav', 'flac', 'ogg', 'wma', 'aac', 'ac3', 'aiff']:
                if ext is not None:
                    filename[-1] = ext
                if suffix is not None: filename.insert(len(filename)-1, suffix)
            else: filename += [ext]
            filename = ''.join(filename)
            return f'{path}/{filename}' if path != '' else filename
        return f'{(path + "/") * (path != "")}{filename}{suffix if suffix is not None else ""}.{ext if ext is not None else "mp3"}'
    else: return f'{path}/output.mp3'