Files
c0ffymachyne-ComfyUI_Signal…/effects/SignalProcessingConvolutionReverb.py
T
2024-12-07 20:16:47 -08:00

179 lines
5.9 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Author: C0ffymachyne
License: GPLv3
Version: 1.0.0
Description:
Convolution Reverb
"""
import os
import torch, torchaudio
import torch.nn.functional as F
from pathlib import Path
from ..core.utilities import comfy_root_to_syspath
comfy_root_to_syspath() # add comfy to sys path for dev
from ..core.io import audio_from_comfy_2d, audio_to_comfy_3d, from_disk_as_raw_2d
from ..core.loudness import lufs_normalization, get_loudness
import folder_paths
class SignalProcessingConvolutionReverb():
supported_formats = ['.wav','.mp3','.ogg','.m4a','.flac','.mp4']
this_directory = os.path.dirname(os.path.realpath(__file__))
ir_directory = os.path.join(os.path.split(this_directory)[0],'audio','ir')
@classmethod
def INPUT_TYPES(s):
files, _ = folder_paths.recursive_search(SignalProcessingConvolutionReverb.ir_directory)
ir_files = []
for file in files:
try:
_, ext = os.path.splitext(file)
if ext in SignalProcessingConvolutionReverb.supported_formats:
ir_files.append(file)
except:
pass
return {
"required": {
"impulse_response": ( sorted(ir_files), ),
"audio_input": ("AUDIO", ),
"wet_dry":("FLOAT", {
"default": 0.5,
"min": 0.0,
"max": 1.0,
"step": 0.01,
}),
},
}
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("audio",)
CATEGORY = "Signal Processing"
FUNCTION = "process"
def process(self, impulse_response, audio_input, wet_dry):
try_gpu : bool = True
repeat : bool = True
print(impulse_response)
print(audio_input)
waveform, sample_rate = audio_from_comfy_2d(audio_input,repeat=repeat,try_gpu=try_gpu)
loudness = get_loudness(waveform,sample_rate)
it_filepath = os.path.join(SignalProcessingConvolutionReverb.ir_directory,impulse_response)
ir,ir_sr = from_disk_as_raw_2d(it_filepath,repeat=repeat,try_gpu=try_gpu)
# Resample IR if sampling rates do not match
if ir_sr != sample_rate:
print(f"Resampling impulse response from {ir_sr} Hz to {sample_rate} Hz.")
resampler = torchaudio.transforms.Resample(orig_freq=ir_sr, new_freq=sample_rate).to(ir.device,dtype=waveform.dtype)
ir = resampler(ir)
ir_sr = sample_rate
print('ensure channels')
#if wave is mono and ir is not mono
if waveform.shape[0] == 1 and ir.shape[0] == 2:
ir = ir.mean(dim=0, keepdim=True)
if waveform.shape[0] == 2 and ir.shape[0] == 1:
ir = ir.repeat(2,1)
print('apply reverb')
processed_audio = self.apply_reverb(waveform, sample_rate, ir, wet_dry=wet_dry)
processed_audio = lufs_normalization(processed_audio,sample_rate,loudness)
return audio_to_comfy_3d(processed_audio,sample_rate,cpu=True)
def apply_reverb(self, audio: torch.Tensor, sr: int, ir: torch.Tensor, wet_dry: float = 0.5) -> torch.Tensor:
num_audio_channels, audio_length = audio.shape
num_ir_channels, ir_length = ir.shape
# Normalize IR to prevent amplification
ir = ir / torch.max(torch.abs(ir)) if torch.max(torch.abs(ir)) > 0 else ir
# Initialize list to hold processed channels
processed_channels = []
# Apply convolution per channel
for channel in range(num_audio_channels):
# Get the current audio and IR channel
audio_channel = audio[channel].unsqueeze(0).unsqueeze(0) # Shape: [1, 1, N]
ir_channel = ir[channel].flip(0).unsqueeze(0).unsqueeze(0) # Reverse IR, Shape: [1, 1, M]
# Perform convolution
convolved = F.conv1d(audio_channel, ir_channel, padding=ir_length - 1) # Shape: [1, 1, N + M -1]
# Remove batch and channel dimensions
convolved = convolved.squeeze(0).squeeze(0) # Shape: [N + M -1]
# Trim convolved signal to original audio length
convolved = convolved[:audio_length]
# Normalize convolved signal to prevent clipping
max_val = torch.max(torch.abs(convolved))
if max_val > 0:
convolved = convolved / max_val
# Apply wet/dry mix
dry = (1 - wet_dry)
wet = wet_dry
processed = dry * audio[channel] + wet * convolved
# Prevent clipping by normalizing if necessary
processed_max = torch.max(torch.abs(processed))
if processed_max > 1.0:
processed = processed / processed_max
# Append processed channel
processed_channels.append(processed)
# Stack channels back into a tensor
processed_audio = torch.stack(processed_channels) # Shape: [2, N]
return processed_audio
if __name__ == "__main__":
import torchaudio
from pathlib import Path
from ..core.io import audio_from_comfy_2d, audio_to_comfy_3d, from_disk_as_raw_2d
node = SignalProcessingConvolutionReverb()
types = node.INPUT_TYPES()
samples_path = Path('ComfyUI_SignalProcessing/audio/samples')
impulses_path = Path('ComfyUI_SignalProcessing/audio/ir')
samples = list(samples_path.rglob('*.*'))
impulses = list(impulses_path.rglob('*.*'))
print(samples)
s = samples[1].absolute()
i = impulses[0].absolute()
s,sr= from_disk_as_raw_2d(s)
sound = audio_to_comfy_3d(s,sr)[0]
result = node.process(i,sound,wet_dry=.5)[0]
audio, sr = audio_from_comfy_2d(result)
torchaudio.save('ComfyUI_SignalProcessing/audio/tests/reverb.wav',audio.cpu(),sr)
#set console to comfy ComfyUI-0.2.4/custom_nodes and run below command
#export coffy_local_dev=1
#python3 -m ComfyUI_SingalProcessing.effects.SignalProcessingConvolutionReverb