Pedalboard_Audio_Reverb / add_reverb_to_file.py
nakas's picture
Upload add_reverb_to_file.py
25169f8
raw
history blame
5.62 kB
"""
Add reverb to an audio file using Pedalboard.
The audio file is read in chunks, using nearly no memory.
This should be one of the fastest possible ways to add reverb to a file
while also using as little memory as possible.
On my laptop, this runs about 58x faster than real-time
(i.e.: processes a 60-second file in ~1 second.)
Requirements: `pip install PySoundFile tqdm pedalboard`
Note that PySoundFile requires a working libsndfile installation.
"""
import argparse
import os
import sys
import warnings
import numpy as np
import soundfile as sf
from tqdm import tqdm
from tqdm.std import TqdmWarning
from pedalboard import Reverb
BUFFER_SIZE_SAMPLES = 1024 * 16
NOISE_FLOOR = 1e-4
def get_num_frames(f: sf.SoundFile) -> int:
# On some platforms and formats, f.frames == -1L.
# Check for this bug and work around it:
if f.frames > 2 ** 32:
f.seek(0)
last_position = f.tell()
while True:
# Seek through the file in chunks, returning
# if the file pointer stops advancing.
f.seek(1024 * 1024 * 1024, sf.SEEK_CUR)
new_position = f.tell()
if new_position == last_position:
f.seek(0)
return new_position
else:
last_position = new_position
else:
return f.frames
def main():
warnings.filterwarnings("ignore", category=TqdmWarning)
parser = argparse.ArgumentParser(description="Add reverb to an audio file.")
parser.add_argument("input_file", help="The input file to add reverb to.")
parser.add_argument(
"--output-file",
help=(
"The name of the output file to write to. If not provided, {input_file}.reverb.wav will"
" be used."
),
default=None,
)
# Instantiate the Reverb object early so we can read its defaults for the argparser --help:
reverb = Reverb()
parser.add_argument("--room-size", type=float, default=reverb.room_size)
parser.add_argument("--damping", type=float, default=reverb.damping)
parser.add_argument("--wet-level", type=float, default=reverb.wet_level)
parser.add_argument("--dry-level", type=float, default=reverb.dry_level)
parser.add_argument("--width", type=float, default=reverb.width)
parser.add_argument("--freeze-mode", type=float, default=reverb.freeze_mode)
parser.add_argument(
"-y",
"--overwrite",
action="store_true",
help="If passed, overwrite the output file if it already exists.",
)
parser.add_argument(
"--cut-reverb-tail",
action="store_true",
help=(
"If passed, remove the reverb tail to the end of the file. "
"The output file will be identical in length to the input file."
),
)
args = parser.parse_args()
for arg in ('room_size', 'damping', 'wet_level', 'dry_level', 'width', 'freeze_mode'):
setattr(reverb, arg, getattr(args, arg))
if not args.output_file:
args.output_file = args.input_file + ".reverb.wav"
sys.stderr.write(f"Opening {args.input_file}...\n")
with sf.SoundFile(args.input_file) as input_file:
sys.stderr.write(f"Writing to {args.output_file}...\n")
if os.path.isfile(args.output_file) and not args.overwrite:
raise ValueError(
f"Output file {args.output_file} already exists! (Pass -y to overwrite.)"
)
with sf.SoundFile(
args.output_file,
'w',
samplerate=input_file.samplerate,
channels=input_file.channels,
) as output_file:
length = get_num_frames(input_file)
length_seconds = length / input_file.samplerate
sys.stderr.write(f"Adding reverb to {length_seconds:.2f} seconds of audio...\n")
with tqdm(
total=length_seconds,
desc="Adding reverb...",
bar_format=(
"{percentage:.0f}%|{bar}| {n:.2f}/{total:.2f} seconds processed"
" [{elapsed}<{remaining}, {rate:.2f}x]"
),
# Avoid a formatting error that occurs if
# TQDM tries to print before we've processed a block
delay=1000,
) as t:
for dry_chunk in input_file.blocks(BUFFER_SIZE_SAMPLES, frames=length):
# Actually call Pedalboard here:
# (reset=False is necessary to allow the reverb tail to
# continue from one chunk to the next.)
effected_chunk = reverb.process(
dry_chunk, sample_rate=input_file.samplerate, reset=False
)
# print(effected_chunk.shape, np.amax(np.abs(effected_chunk)))
output_file.write(effected_chunk)
t.update(len(dry_chunk) / input_file.samplerate)
t.refresh()
if not args.cut_reverb_tail:
while True:
# Pull audio from the effect until there's nothing left:
effected_chunk = reverb.process(
np.zeros((BUFFER_SIZE_SAMPLES, input_file.channels), np.float32),
sample_rate=input_file.samplerate,
reset=False,
)
if np.amax(np.abs(effected_chunk)) < NOISE_FLOOR:
break
output_file.write(effected_chunk)
sys.stderr.write("Done!\n")
if __name__ == "__main__":
main()