Files
2024-12-28 11:12:31 -08:00

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)