Files
2025-07-18 02:19:56 +01:00

172 lines
6.2 KiB
Python

import torch
import numpy as np
from scipy.signal import butter, sosfiltfilt
class AudioPostProcessor:
"""
Advanced Audio Post-Processing Node for ComfyUI
Applies professional-grade effects chain to audio
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
"effect": ([
"gospel_choir",
"trap_808",
"vocal_doubler",
"vinyl_emulation",
"radio_effect",
"hall_reverb",
"tape_saturation"
], {"default": "gospel_choir"}),
"intensity": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.05}),
"wet_dry": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.05}),
}
}
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("processed_audio",)
FUNCTION = "process"
CATEGORY = "audio/effects"
def process(self, audio, effect, intensity, wet_dry):
# Convert to numpy array
if audio.dim() == 3:
audio = audio.squeeze(0)
audio_np = audio.numpy()
sample_rate = 48000
# Process each channel
processed = np.zeros_like(audio_np)
for c in range(audio_np.shape[0]):
dry_signal = audio_np[c]
wet_signal = self.apply_effect(dry_signal, sample_rate, effect, intensity)
# Apply wet/dry mix
processed[c] = (1 - wet_dry) * dry_signal + wet_dry * wet_signal
return (processed.unsqueeze(0),)
def apply_effect(self, audio, sample_rate, effect, intensity):
"""Apply selected effect to audio"""
if effect == "gospel_choir":
return self.gospel_choir(audio, sample_rate, intensity)
elif effect == "trap_808":
return self.trap_808(audio, sample_rate, intensity)
elif effect == "vocal_doubler":
return self.vocal_doubler(audio, sample_rate, intensity)
elif effect == "vinyl_emulation":
return self.vinyl_emulation(audio, sample_rate, intensity)
elif effect == "radio_effect":
return self.radio_effect(audio, sample_rate, intensity)
elif effect == "hall_reverb":
return self.hall_reverb(audio, sample_rate, intensity)
elif effect == "tape_saturation":
return self.tape_saturation(audio, sample_rate, intensity)
return audio
def gospel_choir(self, audio, sample_rate, intensity):
"""Add gospel choir harmonies"""
# Create harmonies
harmony1 = self.pitch_shift(audio, sample_rate, 3) # Minor third
harmony2 = self.pitch_shift(audio, sample_rate, 7) # Fifth
harmony3 = self.pitch_shift(audio, sample_rate, 10) # Minor seventh
# Blend harmonies
choir = 0.5 * harmony1 + 0.3 * harmony2 + 0.2 * harmony3
# Apply church reverb
choir = self.apply_reverb(choir, sample_rate, 2.5, 0.6)
return (1 - intensity) * audio + intensity * choir
def trap_808(self, audio, sample_rate, intensity):
"""Add trap-style 808 bass"""
# Detect fundamental frequency
fundamental = self.detect_fundamental(audio, sample_rate)
# Generate 808 sine wave
t = np.arange(len(audio)) / sample_rate
sine_wave = np.sin(2 * np.pi * fundamental * t)
# Add pitch slide
slide = np.exp(-t * 2) * fundamental * 0.5
sine_wave = np.sin(2 * np.pi * (fundamental + slide) * t)
# Apply distortion
sine_wave = np.tanh(sine_wave * 3)
# Mix with original
return audio + intensity * sine_wave * 0.3
def vocal_doubler(self, audio, sample_rate, intensity):
"""Create vocal doubling effect"""
# Short delay
delay_samples = int(0.03 * sample_rate) # 30ms delay
delayed = np.roll(audio, delay_samples)
delayed[:delay_samples] = 0
# Pitch modulation
modulated = self.pitch_shift(delayed, sample_rate, 0.5 * intensity)
# Stereo spread
left = 0.7 * audio + 0.3 * modulated
right = 0.3 * audio + 0.7 * modulated
return np.stack([left, right]) if audio.ndim > 1 else left
def vinyl_emulation(self, audio, sample_rate, intensity):
"""Add vinyl record characteristics"""
# Apply low-pass filter
sos = butter(2, 10000, 'lowpass', fs=sample_rate, output='sos')
audio = sosfiltfilt(sos, audio)
# Add crackle
if intensity > 0.3:
crackle = np.random.uniform(-0.02, 0.02, len(audio))
audio += intensity * crackle * 0.5
# Add wow & flutter
phase = np.cumsum(1 + 0.005 * np.sin(2 * np.pi * 0.5 * np.arange(len(audio)) / sample_rate))
audio = np.interp(np.arange(len(audio)), phase, audio)
return audio
def pitch_shift(self, audio, sample_rate, semitones):
"""Simple pitch shifting"""
# Using Fourier method for simplicity (real implementations use phase vocoding)
n = len(audio)
freqs = np.fft.rfftfreq(n, d=1/sample_rate)
fft = np.fft.rfft(audio)
shifted_freqs = freqs * (2 ** (semitones / 12))
# Interpolate complex spectrum
new_fft = np.interp(freqs, shifted_freqs, fft, left=0, right=0)
return np.fft.irfft(new_fft, n=n).real
def detect_fundamental(self, audio, sample_rate):
"""Detect fundamental frequency using autocorrelation"""
# Normalize audio
audio = audio - np.mean(audio)
audio = audio / np.max(np.abs(audio))
# Autocorrelation
corr = np.correlate(audio, audio, mode='full')
corr = corr[len(corr)//2:]
# Find first peak after zero
peaks = np.where((corr[1:-1] > corr[:-2]) & (corr[1:-1] > corr[2:]))[0] + 1
if len(peaks) > 0:
return sample_rate / peaks[0]
return 100 # Default to 100Hz
# For ComfyUI registration
NODE_CLASS_MAPPINGS = {
"AudioPostProcessor": AudioPostProcessor
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AudioPostProcessor": "🎚️ Audio Post-Processor"
}