nakas commited on
Commit
25169f8
1 Parent(s): e45f3b3

Upload add_reverb_to_file.py

Browse files
Files changed (1) hide show
  1. add_reverb_to_file.py +152 -0
add_reverb_to_file.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Add reverb to an audio file using Pedalboard.
3
+
4
+ The audio file is read in chunks, using nearly no memory.
5
+ This should be one of the fastest possible ways to add reverb to a file
6
+ while also using as little memory as possible.
7
+
8
+ On my laptop, this runs about 58x faster than real-time
9
+ (i.e.: processes a 60-second file in ~1 second.)
10
+
11
+ Requirements: `pip install PySoundFile tqdm pedalboard`
12
+ Note that PySoundFile requires a working libsndfile installation.
13
+ """
14
+
15
+ import argparse
16
+ import os
17
+ import sys
18
+ import warnings
19
+
20
+ import numpy as np
21
+ import soundfile as sf
22
+ from tqdm import tqdm
23
+ from tqdm.std import TqdmWarning
24
+
25
+ from pedalboard import Reverb
26
+
27
+ BUFFER_SIZE_SAMPLES = 1024 * 16
28
+ NOISE_FLOOR = 1e-4
29
+
30
+
31
+ def get_num_frames(f: sf.SoundFile) -> int:
32
+ # On some platforms and formats, f.frames == -1L.
33
+ # Check for this bug and work around it:
34
+ if f.frames > 2 ** 32:
35
+ f.seek(0)
36
+ last_position = f.tell()
37
+ while True:
38
+ # Seek through the file in chunks, returning
39
+ # if the file pointer stops advancing.
40
+ f.seek(1024 * 1024 * 1024, sf.SEEK_CUR)
41
+ new_position = f.tell()
42
+ if new_position == last_position:
43
+ f.seek(0)
44
+ return new_position
45
+ else:
46
+ last_position = new_position
47
+ else:
48
+ return f.frames
49
+
50
+
51
+ def main():
52
+ warnings.filterwarnings("ignore", category=TqdmWarning)
53
+
54
+ parser = argparse.ArgumentParser(description="Add reverb to an audio file.")
55
+ parser.add_argument("input_file", help="The input file to add reverb to.")
56
+ parser.add_argument(
57
+ "--output-file",
58
+ help=(
59
+ "The name of the output file to write to. If not provided, {input_file}.reverb.wav will"
60
+ " be used."
61
+ ),
62
+ default=None,
63
+ )
64
+
65
+ # Instantiate the Reverb object early so we can read its defaults for the argparser --help:
66
+ reverb = Reverb()
67
+
68
+ parser.add_argument("--room-size", type=float, default=reverb.room_size)
69
+ parser.add_argument("--damping", type=float, default=reverb.damping)
70
+ parser.add_argument("--wet-level", type=float, default=reverb.wet_level)
71
+ parser.add_argument("--dry-level", type=float, default=reverb.dry_level)
72
+ parser.add_argument("--width", type=float, default=reverb.width)
73
+ parser.add_argument("--freeze-mode", type=float, default=reverb.freeze_mode)
74
+
75
+ parser.add_argument(
76
+ "-y",
77
+ "--overwrite",
78
+ action="store_true",
79
+ help="If passed, overwrite the output file if it already exists.",
80
+ )
81
+
82
+ parser.add_argument(
83
+ "--cut-reverb-tail",
84
+ action="store_true",
85
+ help=(
86
+ "If passed, remove the reverb tail to the end of the file. "
87
+ "The output file will be identical in length to the input file."
88
+ ),
89
+ )
90
+ args = parser.parse_args()
91
+
92
+ for arg in ('room_size', 'damping', 'wet_level', 'dry_level', 'width', 'freeze_mode'):
93
+ setattr(reverb, arg, getattr(args, arg))
94
+
95
+ if not args.output_file:
96
+ args.output_file = args.input_file + ".reverb.wav"
97
+
98
+ sys.stderr.write(f"Opening {args.input_file}...\n")
99
+
100
+ with sf.SoundFile(args.input_file) as input_file:
101
+ sys.stderr.write(f"Writing to {args.output_file}...\n")
102
+ if os.path.isfile(args.output_file) and not args.overwrite:
103
+ raise ValueError(
104
+ f"Output file {args.output_file} already exists! (Pass -y to overwrite.)"
105
+ )
106
+ with sf.SoundFile(
107
+ args.output_file,
108
+ 'w',
109
+ samplerate=input_file.samplerate,
110
+ channels=input_file.channels,
111
+ ) as output_file:
112
+ length = get_num_frames(input_file)
113
+ length_seconds = length / input_file.samplerate
114
+ sys.stderr.write(f"Adding reverb to {length_seconds:.2f} seconds of audio...\n")
115
+ with tqdm(
116
+ total=length_seconds,
117
+ desc="Adding reverb...",
118
+ bar_format=(
119
+ "{percentage:.0f}%|{bar}| {n:.2f}/{total:.2f} seconds processed"
120
+ " [{elapsed}<{remaining}, {rate:.2f}x]"
121
+ ),
122
+ # Avoid a formatting error that occurs if
123
+ # TQDM tries to print before we've processed a block
124
+ delay=1000,
125
+ ) as t:
126
+ for dry_chunk in input_file.blocks(BUFFER_SIZE_SAMPLES, frames=length):
127
+ # Actually call Pedalboard here:
128
+ # (reset=False is necessary to allow the reverb tail to
129
+ # continue from one chunk to the next.)
130
+ effected_chunk = reverb.process(
131
+ dry_chunk, sample_rate=input_file.samplerate, reset=False
132
+ )
133
+ # print(effected_chunk.shape, np.amax(np.abs(effected_chunk)))
134
+ output_file.write(effected_chunk)
135
+ t.update(len(dry_chunk) / input_file.samplerate)
136
+ t.refresh()
137
+ if not args.cut_reverb_tail:
138
+ while True:
139
+ # Pull audio from the effect until there's nothing left:
140
+ effected_chunk = reverb.process(
141
+ np.zeros((BUFFER_SIZE_SAMPLES, input_file.channels), np.float32),
142
+ sample_rate=input_file.samplerate,
143
+ reset=False,
144
+ )
145
+ if np.amax(np.abs(effected_chunk)) < NOISE_FLOOR:
146
+ break
147
+ output_file.write(effected_chunk)
148
+ sys.stderr.write("Done!\n")
149
+
150
+
151
+ if __name__ == "__main__":
152
+ main()