From eed08ee1f439de830c609f17160544c28b553381 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Mon, 23 Mar 2026 20:26:01 -0400 Subject: [PATCH] Added new parameters to Runware Audio Inference Settings node --- clientlibs/utils.js | 28 ++++++++ modules/audioInferenceInputs.py | 18 ++++- modules/audioSettings.py | 117 ++++++++++++++++++++++++++++++-- 3 files changed, 156 insertions(+), 7 deletions(-) diff --git a/clientlibs/utils.js b/clientlibs/utils.js index 4fc3010..85721b8 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -1144,6 +1144,20 @@ function audioSettingsToggleHandler(settingsNode) { const turboWidget = settingsNode.widgets.find(w => w && w.name === "turbo"); const useTextNormalizationWidget = settingsNode.widgets.find(w => w && w.name === "useTextNormalization"); const textNormalizationWidget = settingsNode.widgets.find(w => w && w.name === "textNormalization"); + const useBpmWidget = settingsNode.widgets.find(w => w && w.name === "useBpm"); + const bpmWidget = settingsNode.widgets.find(w => w && w.name === "bpm"); + const useKeyScaleWidget = settingsNode.widgets.find(w => w && w.name === "useKeyScale"); + const keyScaleWidget = settingsNode.widgets.find(w => w && w.name === "keyScale"); + const useTimeSignatureWidget = settingsNode.widgets.find(w => w && w.name === "useTimeSignature"); + const timeSignatureWidget = settingsNode.widgets.find(w => w && w.name === "timeSignature"); + const useVocalLanguageWidget = settingsNode.widgets.find(w => w && w.name === "useVocalLanguage"); + const vocalLanguageWidget = settingsNode.widgets.find(w => w && w.name === "vocalLanguage"); + const useCoverConditioningScaleWidget = settingsNode.widgets.find(w => w && w.name === "useCoverConditioningScale"); + const coverConditioningScaleWidget = settingsNode.widgets.find(w => w && w.name === "coverConditioningScale"); + const useRepaintingStartWidget = settingsNode.widgets.find(w => w && w.name === "useRepaintingStart"); + const repaintingStartWidget = settingsNode.widgets.find(w => w && w.name === "repaintingStart"); + const useRepaintingEndWidget = settingsNode.widgets.find(w => w && w.name === "useRepaintingEnd"); + const repaintingEndWidget = settingsNode.widgets.find(w => w && w.name === "repaintingEnd"); function toggleWidgetState(useWidget, paramWidget, paramName) { if (!useWidget || !paramWidget) return; @@ -1167,6 +1181,13 @@ function audioSettingsToggleHandler(settingsNode) { if (useLanguageBoostWidget && languageBoostWidget) toggleWidgetState(useLanguageBoostWidget, languageBoostWidget, "languageBoost"); if (useTurboWidget && turboWidget) toggleWidgetState(useTurboWidget, turboWidget, "turbo"); if (useTextNormalizationWidget && textNormalizationWidget) toggleWidgetState(useTextNormalizationWidget, textNormalizationWidget, "textNormalization"); + if (useBpmWidget && bpmWidget) toggleWidgetState(useBpmWidget, bpmWidget, "bpm"); + if (useKeyScaleWidget && keyScaleWidget) toggleWidgetState(useKeyScaleWidget, keyScaleWidget, "keyScale"); + if (useTimeSignatureWidget && timeSignatureWidget) toggleWidgetState(useTimeSignatureWidget, timeSignatureWidget, "timeSignature"); + if (useVocalLanguageWidget && vocalLanguageWidget) toggleWidgetState(useVocalLanguageWidget, vocalLanguageWidget, "vocalLanguage"); + if (useCoverConditioningScaleWidget && coverConditioningScaleWidget) toggleWidgetState(useCoverConditioningScaleWidget, coverConditioningScaleWidget, "coverConditioningScale"); + if (useRepaintingStartWidget && repaintingStartWidget) toggleWidgetState(useRepaintingStartWidget, repaintingStartWidget, "repaintingStart"); + if (useRepaintingEndWidget && repaintingEndWidget) toggleWidgetState(useRepaintingEndWidget, repaintingEndWidget, "repaintingEnd"); } function videoSettingsToggleHandler(settingsNode) { @@ -4088,6 +4109,8 @@ function regionalPromptingRegionsToggleHandler(regionsNode) { function audioInferenceInputsToggleHandler(audioInputsNode) { // Find widgets + const useAudioWidget = audioInputsNode.widgets.find(w => w.name === "useAudio"); + const audioWidget = audioInputsNode.widgets.find(w => w.name === "Audio"); const useVideoWidget = audioInputsNode.widgets.find(w => w.name === "useVideo"); const videoWidget = audioInputsNode.widgets.find(w => w.name === "Video"); const useVideosWidget = audioInputsNode.widgets.find(w => w.name === "useVideos"); @@ -4134,6 +4157,11 @@ function audioInferenceInputsToggleHandler(audioInputsNode) { setTimeout(toggleEnabled, 100); } + // Set up toggle handler for single audio + if (useAudioWidget && audioWidget) { + toggleWidgetState(useAudioWidget, audioWidget, "Audio"); + } + // Set up toggle handler for single video if (useVideoWidget && videoWidget) { toggleWidgetState(useVideoWidget, videoWidget, "Video"); diff --git a/modules/audioInferenceInputs.py b/modules/audioInferenceInputs.py index 7d670b7..524ffca 100644 --- a/modules/audioInferenceInputs.py +++ b/modules/audioInferenceInputs.py @@ -10,6 +10,14 @@ class audioInferenceInputs: @classmethod def INPUT_TYPES(cls): optionalInputs = { + "useAudio": ("BOOLEAN", { + "tooltip": "Enable to include single audio input in API request.", + "default": False, + }), + "Audio": ("STRING", { + "tooltip": "Audio URL or mediaUUID for audio generation. Can be a direct URL or a mediaUUID from Runware Media Upload node. Only used when 'Use Audio' is enabled.", + "default": "", + }), "useVideo": ("BOOLEAN", { "tooltip": "Enable to include single video input in API request.", "default": False, @@ -37,7 +45,7 @@ class audioInferenceInputs: "optional": optionalInputs } - DESCRIPTION = "Configure custom inputs for Runware Audio Inference, including single or multiple video inputs for audio extraction or generation." + DESCRIPTION = "Configure custom inputs for Runware Audio Inference, including optional audio URL/mediaUUID, and single or multiple video inputs for audio extraction or generation." FUNCTION = "createInputs" RETURN_TYPES = ("RUNWAREAUDIOINFERENCEINPUTS",) RETURN_NAMES = ("Audio Inference Inputs",) @@ -45,12 +53,18 @@ class audioInferenceInputs: def createInputs(self, **kwargs) -> tuple[Dict[str, Any]]: """Create audio inference inputs from provided parameters""" + useAudio = kwargs.get("useAudio", False) + audio = kwargs.get("Audio", None) useVideo = kwargs.get("useVideo", False) video = kwargs.get("Video", None) useVideos = kwargs.get("useVideos", False) inputs = {} - + + # Handle single audio (inputs.audio) + if useAudio and audio is not None and audio.strip() != "": + inputs["audio"] = audio.strip() + # Handle single video (inputs.video) if useVideo and video is not None and video.strip() != "": inputs["video"] = video.strip() diff --git a/modules/audioSettings.py b/modules/audioSettings.py index 4f4810c..69e2f22 100644 --- a/modules/audioSettings.py +++ b/modules/audioSettings.py @@ -1,14 +1,22 @@ """ Runware Audio Inference Settings Node -Provides lyrics, guidanceType, languageBoost, turbo, temperature, and textNormalization -settings for Runware Audio Inference. +Provides lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization, +bpm, keyScale, timeSignature, vocalLanguage, coverConditioningScale, repaintingStart, +and repaintingEnd for Runware Audio Inference. """ from typing import Dict, Any +_VOCAL_LANGUAGE_CODES = [ + "ar", "az", "bg", "bn", "ca", "cs", "da", "de", "el", "en", "es", "fa", "fi", "fr", + "he", "hi", "hr", "ht", "hu", "id", "is", "it", "ja", "ko", "la", "lt", "ms", "ne", + "nl", "no", "pa", "pl", "pt", "ro", "ru", "sa", "sk", "sr", "sv", "sw", "ta", "te", + "th", "tl", "tr", "uk", "ur", "vi", "yue", "zh", "unknown", +] + class RunwareAudioSettings: - """Runware Audio Inference Settings Node (lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization).""" + """Runware Audio Inference Settings Node.""" @classmethod def INPUT_TYPES(cls): @@ -41,7 +49,7 @@ class RunwareAudioSettings: "default": False, }), "guidanceType": (["apg", "cfg"], { - "tooltip": "Controls how the guidance value is used. apg = Adversarial Perceptual Guidance, cfg = Classifier-Free Guidance", + "tooltip": "Controls how the guidance value is used. See API GuidanceType: apg | cfg.", "default": "apg", }), "useLanguageBoost": ("BOOLEAN", { @@ -76,6 +84,70 @@ class RunwareAudioSettings: "label_on": "true", "label_off": "false", }), + "useBpm": ("BOOLEAN", { + "tooltip": "Enable to include bpm (tempo) in audio generation settings. If disabled, the model decides automatically.", + "default": False, + }), + "bpm": ("INT", { + "tooltip": "Beats per minute (30–300). If not set via API, the model decides. Common: 60–80 ballad, 90–110 pop, 120–130 house, 140–170 DnB. Only used when 'Use Bpm' is enabled.", + "default": 120, + "min": 30, + "max": 300, + "step": 1, + }), + "useKeyScale": ("BOOLEAN", { + "tooltip": "Enable to include keyScale (musical key and scale). Empty value lets the model decide.", + "default": False, + }), + "keyScale": ("STRING", { + "default": "", + "tooltip": "Musical key and scale. Empty string lets the model decide. Format: '{Note}{Accidental} {Mode}' (Note A–G, Accidental '' | '#' | 'b' | '♯' | '♭', Mode 'major' | 'minor'). Examples: 'C major', 'F# minor', 'B♭ major'.", + }), + "useTimeSignature": ("BOOLEAN", { + "tooltip": "Enable to include timeSignature (beats per measure). If disabled, the model decides automatically.", + "default": False, + }), + "timeSignature": (["", "2", "3", "4", "6"], { + "tooltip": "Time signature: '2' duple, '3' waltz, '4' common time, '6' compound duple. Empty = model decides. Most pop uses '4'. Only used when 'Use Time Signature' is enabled.", + "default": "", + }), + "useVocalLanguage": ("BOOLEAN", { + "tooltip": "Enable to include vocalLanguage (ISO 639-1) for vocal generation.", + "default": False, + }), + "vocalLanguage": (_VOCAL_LANGUAGE_CODES, { + "tooltip": "ISO 639-1 language code for vocals. Use 'unknown' for instrumental or automatic detection. Only used when 'Use Vocal Language' is enabled.", + "default": "en", + }), + "useCoverConditioningScale": ("BOOLEAN", { + "tooltip": "Enable to include coverConditioningScale (source-audio conditioning fraction for cover tasks). Only effective when input audio is provided.", + "default": False, + }), + "coverConditioningScale": ("FLOAT", { + "tooltip": "Fraction of denoising steps using source-audio conditioning (cover task). 1.0 = all steps; 0.5 = first half source then text-only. Only used when 'Use Cover Conditioning Scale' is enabled.", + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.01, + }), + "useRepaintingStart": ("BOOLEAN", { + "tooltip": "Enable to include repaintingStart (seconds). Requires input audio. Sets repaint region with repaintingEnd; negative values prepend (outpaint before start).", + "default": False, + }), + "repaintingStart": ("FLOAT", { + "tooltip": "Start time in seconds for repaint/inpaint/outpaint region. Only used when 'Use Repainting Start' is enabled.", + "default": 0.0, + "step": 0.01, + }), + "useRepaintingEnd": ("BOOLEAN", { + "tooltip": "Enable to include repaintingEnd (seconds). Requires input audio. Values beyond source duration append (outpaint).", + "default": False, + }), + "repaintingEnd": ("FLOAT", { + "tooltip": "End time in seconds for repaint region. Only used when 'Use Repainting End' is enabled.", + "default": 0.0, + "step": 0.01, + }), } } @@ -83,7 +155,7 @@ class RunwareAudioSettings: RETURN_NAMES = ("settings",) FUNCTION = "createSettings" CATEGORY = "Runware/Audio" - DESCRIPTION = "Configure audio generation settings (lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization) for Runware Audio Inference. Connect to Runware Audio Inference node." + DESCRIPTION = "Configure audio generation settings (lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization, bpm, keyScale, timeSignature, vocalLanguage, coverConditioningScale, repaintingStart, repaintingEnd) for Runware Audio Inference. Connect to Runware Audio Inference node." def createSettings(self, **kwargs) -> tuple[Dict[str, Any]]: """Create audio settings dict for API""" @@ -99,6 +171,20 @@ class RunwareAudioSettings: turbo = kwargs.get("turbo", False) use_text_normalization = kwargs.get("useTextNormalization", False) text_normalization = kwargs.get("textNormalization", True) + use_bpm = kwargs.get("useBpm", False) + bpm = kwargs.get("bpm", 120) + use_key_scale = kwargs.get("useKeyScale", False) + key_scale = kwargs.get("keyScale", "") + use_time_signature = kwargs.get("useTimeSignature", False) + time_signature = kwargs.get("timeSignature", "") + use_vocal_language = kwargs.get("useVocalLanguage", False) + vocal_language = kwargs.get("vocalLanguage", "en") + use_cover_conditioning_scale = kwargs.get("useCoverConditioningScale", False) + cover_conditioning_scale = kwargs.get("coverConditioningScale", 1.0) + use_repainting_start = kwargs.get("useRepaintingStart", False) + repainting_start = kwargs.get("repaintingStart", 0.0) + use_repainting_end = kwargs.get("useRepaintingEnd", False) + repainting_end = kwargs.get("repaintingEnd", 0.0) settings: Dict[str, Any] = {} @@ -120,6 +206,27 @@ class RunwareAudioSettings: if use_text_normalization: settings["textNormalization"] = bool(text_normalization) + if use_bpm: + settings["bpm"] = int(bpm) + + if use_key_scale: + settings["keyScale"] = key_scale if key_scale is not None else "" + + if use_time_signature: + settings["timeSignature"] = time_signature + + if use_vocal_language: + settings["vocalLanguage"] = vocal_language + + if use_cover_conditioning_scale: + settings["coverConditioningScale"] = round(float(cover_conditioning_scale), 2) + + if use_repainting_start: + settings["repaintingStart"] = round(float(repainting_start), 2) + + if use_repainting_end: + settings["repaintingEnd"] = round(float(repainting_end), 2) + return (settings,)