125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Author: C0ffymachyne
|
|
License: GPLv3
|
|
Version: 1.0.0
|
|
|
|
Description:
|
|
Mixdown node for pad synths
|
|
"""
|
|
|
|
import torch
|
|
|
|
from typing import Tuple, List, Dict, Union, Any
|
|
import torchaudio
|
|
|
|
from ..core.io import audio_to_comfy_3d
|
|
from ..core.loudness import lufs_normalization
|
|
|
|
|
|
class SignalProcessingMixdown:
|
|
@classmethod
|
|
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
|
return {
|
|
"required": {
|
|
"audio_inputs": ("AUDIO_LIST", {"default": []}),
|
|
},
|
|
"optional": {
|
|
"gain_factors": (
|
|
"FLOAT_LIST",
|
|
{"default": [], "min": 0.0, "max": 2.0, "step": 0.1},
|
|
),
|
|
# If empty, default to [1.0] * num_audios
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("AUDIO",)
|
|
RETURN_NAMES = ("mixed_audio",)
|
|
CATEGORY = "Signal Processing"
|
|
FUNCTION = "process"
|
|
|
|
def process(
|
|
self,
|
|
audio_inputs: List[Dict[str, Union[torch.Tensor, int]]],
|
|
gain_factors: List[float] = [],
|
|
) -> Tuple[Dict[str, Union[torch.Tensor, int]]]:
|
|
"""
|
|
Mix down multiple audio inputs into a single audio output
|
|
with optional individual volume controls.
|
|
|
|
Parameters:
|
|
audio_inputs (List[Dict]): List of audio inputs,
|
|
each containing 'waveform' and 'sample_rate'.
|
|
output_normalization (float): Normalization factor for the mixed audio (0.0 to 1.0).
|
|
gain_factors (List[float], optional): List of gain factors for each audio input.
|
|
|
|
Returns:
|
|
Tuple[Dict[str, torch.Tensor], int]: Mixed audio with waveform and sample rate.
|
|
"""
|
|
|
|
if not audio_inputs:
|
|
raise ValueError("No audio inputs provided for mixing.")
|
|
|
|
num_audios = len(audio_inputs)
|
|
|
|
# Handle gain_factors
|
|
if not gain_factors:
|
|
gain_factors = [1.0] * num_audios
|
|
elif len(gain_factors) != num_audios:
|
|
raise ValueError(
|
|
f"Number of gain factors ({len(gain_factors)}) \
|
|
does not match number of audio inputs ({num_audios})."
|
|
)
|
|
|
|
# Extract sample rates and verify consistency
|
|
sample_rates: List[int] = [audio["sample_rate"] for audio in audio_inputs]
|
|
target_sample_rate = sample_rates[0]
|
|
|
|
for idx, sr in enumerate(sample_rates):
|
|
if sr != target_sample_rate:
|
|
resampler = torchaudio.transforms.Resample(
|
|
orig_freq=sr, new_freq=target_sample_rate
|
|
)
|
|
|
|
_waveform: torch.Tensor = audio_inputs[idx]["waveform"]
|
|
resampler.to(
|
|
device=_waveform.device,
|
|
dtype=_waveform.dtype,
|
|
)
|
|
audio_inputs[idx]["waveform"] = resampler(_waveform)
|
|
audio_inputs[idx]["sample_rate"] = target_sample_rate
|
|
|
|
# Determine the maximum length among all audio inputs
|
|
lengthsw: List[torch.Tensor] = [audio["waveform"] for audio in audio_inputs]
|
|
lengths: List[int] = [wave.shape[-1] for wave in lengthsw]
|
|
max_length = max(lengths)
|
|
|
|
# Pad or truncate each audio to match the maximum length and apply gain
|
|
for idx, audio in enumerate(audio_inputs):
|
|
waveform: torch.Tensor = audio["waveform"]
|
|
current_length = waveform.shape[-1]
|
|
gain = gain_factors[idx]
|
|
|
|
if current_length < max_length:
|
|
padding = max_length - current_length
|
|
# Pad with zeros (silence) at the end
|
|
waveform = torch.nn.functional.pad(waveform, (0, padding))
|
|
elif current_length > max_length:
|
|
# Truncate the waveform to max_length
|
|
waveform = waveform[:, :, :max_length]
|
|
|
|
# Apply gain
|
|
waveform = waveform * gain
|
|
|
|
audio["waveform"] = waveform
|
|
|
|
# Sum all waveforms to create the mix
|
|
mixed_waveform: torch.Tensor = torch.zeros_like(audio_inputs[0]["waveform"])
|
|
for idx, audio in enumerate(audio_inputs):
|
|
mixed_waveform += audio["waveform"]
|
|
|
|
mixed_waveform = lufs_normalization(mixed_waveform, target_sample_rate)
|
|
|
|
return audio_to_comfy_3d(mixed_waveform, target_sample_rate)
|