Merge upstream main: keep audio nodes, upstream README, and add VAE/Latent nodes
This commit is contained in:
@@ -1,297 +1,73 @@
|
||||
# Network Bending for ComfyUI
|
||||
|
||||
A custom node pack for ComfyUI that enables creative manipulation and "bending" of neural network models. Perform various operations on loaded model checkpoints to create unique and experimental effects.
|
||||
A custom node pack for ComfyUI that enables the creative manipulation of generative models. Allows you to corrupt, modify, and blend neural networks.
|
||||
|
||||
## Features
|
||||
|
||||
### 🎛️ Network Bending Node
|
||||
The main node for applying various network bending operations:
|
||||
|
||||
- **Add Noise**: Inject Gaussian noise into model weights
|
||||
- **Scale Weights**: Scale model weights up or down
|
||||
- **Prune Weights**: Remove small weights (sparsification)
|
||||
- **Scale Weights**: Scale model weights up or down to amplify or dampen features
|
||||
- **Prune Weights**: Remove small weights for sparsification
|
||||
- **Randomize Weights**: Replace portions of weights with random values
|
||||
- **Smooth Weights**: Apply spatial smoothing to weight matrices
|
||||
- **Quantize Weights**: Reduce weight precision to discrete levels
|
||||
|
||||
### 🔧 Network Bending Advanced Node
|
||||
Advanced operations for more complex manipulations:
|
||||
|
||||
- **Layer Swap**: Swap weights between similar layers
|
||||
- **Activation Replace**: Replace activation functions
|
||||
- **Weight Transpose**: Transpose weight matrices
|
||||
- **Channel Shuffle**: Randomly shuffle channels in conv layers
|
||||
- **Channel Shuffle**: Randomly shuffle channels in convolutional layers
|
||||
- **Frequency Filter**: Apply frequency domain filtering
|
||||
- **Weight Clustering**: Group similar weights together
|
||||
|
||||
### 🎨 Model Mixer Node
|
||||
Mix two models together with various blending modes:
|
||||
|
||||
- **Linear Interpolation**: Simple weighted average of weights
|
||||
- **Weighted Sum**: Weighted sum with normalization
|
||||
- **Layer-wise Mix**: Different mix ratios for different layers
|
||||
- **Frequency Blend**: Mix models in frequency domain
|
||||
- **Random Mix**: Randomly select weights from either model
|
||||
|
||||
### 🔄 Latent Format Converter Node
|
||||
Convert between audio and image latent formats to enable cross-modal workflows:
|
||||
### Audio Processing Nodes
|
||||
|
||||
- **Audio to Image**: Convert 3D audio latents [B,C,L] to 4D image latents [B,C,H,W]
|
||||
- **Image to Audio**: Convert 4D image latents [B,C,H,W] to 3D audio latents [B,C,L]
|
||||
- **Auto Detect**: Automatically detect format and convert appropriately
|
||||
- **Channel Conversion**: Convert between different channel counts (e.g., 4→64 for SD→StableAudio)
|
||||
- **Multiple Methods**: Choose from reshape, interpolate, fold, or tile methods for both spatial and channel dims
|
||||
The package also includes comprehensive audio processing capabilities for Stable Audio models:
|
||||
|
||||
### 🌀 VAE Network Bending Node
|
||||
Apply network bending operations specifically to VAE models:
|
||||
#### Audio VAE Nodes
|
||||
- **Audio VAE Encode**: Encode audio waveforms into latent space
|
||||
- **Audio VAE Decode**: Decode latent representations back to audio
|
||||
|
||||
- **Add Noise**: Inject noise into encoder/decoder weights
|
||||
- **Scale Weights**: Scale VAE weights for more/less aggressive encoding
|
||||
- **Asymmetric Noise**: Different noise levels for encoder vs decoder
|
||||
- **Channel Corruption**: Corrupt specific channels in the VAE
|
||||
- **Progressive Corruption**: Gradually increase corruption through layers
|
||||
- **Target Components**: Choose encoder, decoder, or both
|
||||
#### Audio Latent Manipulation
|
||||
- **Audio Latent Interpolate**: Smoothly interpolate between audio latents
|
||||
- **Audio Latent Blend**: Blend multiple audio latents with various modes
|
||||
- **Audio Latent Manipulator**: Direct manipulation of audio latent features
|
||||
- **Audio Feature Extractor**: Extract specific features from audio latents
|
||||
|
||||
### 🎯 VAE Mixer Node
|
||||
Mix two VAE models with specialized blending strategies:
|
||||
|
||||
- **Linear Blend**: Blend both encoder and decoder linearly
|
||||
- **Encoder Swap**: Use encoder from one VAE with decoder from another
|
||||
- **Decoder Swap**: Use decoder from one VAE with encoder from another
|
||||
- **Cross Architecture**: Mix encoder/decoder with different ratios
|
||||
- **Layer Shuffle**: Randomly select layers from each VAE
|
||||
|
||||
### 💫 VAE Latent Bending Node
|
||||
Perform operations directly on VAE latent streams:
|
||||
|
||||
- **Add Noise**: Add noise to latent values for texture variations
|
||||
- **Channel Swap**: Swap latent channels for color shifts
|
||||
- **Frequency Filter**: Apply frequency domain filtering
|
||||
- **Spatial Corruption**: Corrupt specific spatial regions
|
||||
- **Value Quantization**: Create posterization effects
|
||||
- **Dimension Warp**: Warp spatial dimensions for distortions
|
||||
|
||||
### 🔮 VAE Channel Manipulator Node
|
||||
Advanced channel-level operations on VAE latents:
|
||||
|
||||
- **Channel Attention**: Apply self-attention across channels
|
||||
- **Cross Channel Mixing**: Mix information between channels
|
||||
- **Channel Dropout**: Randomly drop channels
|
||||
- **Channel Amplification**: Amplify important channels
|
||||
- **Channel Rotation**: Rotate channels in feature space
|
||||
- **Channel Statistics Swap**: Swap statistics with reference latent
|
||||
#### Audio Style Transfer
|
||||
- **Audio Style Transfer**: Transfer style from one audio to another
|
||||
- **Audio Latent Guidance**: Guide generation with reference audio
|
||||
- **Audio Reference Encoder**: Encode reference audio for style transfer
|
||||
|
||||
## Installation
|
||||
|
||||
### Basic Installation
|
||||
|
||||
1. Clone this repository into your ComfyUI `custom_nodes` directory:
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/yourusername/network_bending.git
|
||||
git clone https://github.com/DavidPiazza/network_bending.git
|
||||
```
|
||||
|
||||
2. Install dependencies (if any):
|
||||
```bash
|
||||
cd network_bending
|
||||
pip install -r requirements.txt # if requirements.txt exists
|
||||
```
|
||||
2. Restart ComfyUI
|
||||
|
||||
3. Restart ComfyUI
|
||||
### Audio Network Bending
|
||||
|
||||
## Usage
|
||||
1. **Load an audio model** (e.g., Stable Audio VAE)
|
||||
2. **Add audio processing nodes** from the network_bending/audio category
|
||||
3. **Connect audio inputs** either from file loaders or generated audio
|
||||
4. **Apply bending operations** to the audio latents
|
||||
5. **Decode back to audio** using Audio VAE Decode
|
||||
|
||||
### Basic Network Bending
|
||||
|
||||
1. Load a model checkpoint using the standard ComfyUI loader nodes
|
||||
2. Add a "Network Bending" node to your workflow
|
||||
3. Connect the MODEL output to the Network Bending input
|
||||
4. Configure parameters:
|
||||
- **Operation**: Select the type of bending operation
|
||||
- **Intensity**: Control the strength (0.0 = no effect, 1.0 = maximum)
|
||||
- **Target Layers**: Specify which layers to modify (e.g., "conv", "attention", or "all")
|
||||
- **Seed**: Set for reproducible results (-1 for random)
|
||||
|
||||
### Layer Targeting
|
||||
|
||||
You can target specific layers using patterns:
|
||||
- `all` - Modify all layers
|
||||
- `conv` - Target convolutional layers
|
||||
- `attention` - Target attention layers
|
||||
- `linear` - Target linear/dense layers
|
||||
- `norm` - Target normalization layers
|
||||
- `embedding` - Target embedding layers
|
||||
- Combine patterns with commas: `conv,attention`
|
||||
|
||||
### Model Mixing
|
||||
|
||||
1. Load two model checkpoints
|
||||
2. Add a "Model Mixer" node
|
||||
3. Connect both models to the mixer inputs
|
||||
4. Set the mix mode and ratio
|
||||
5. Connect the output to your generation pipeline
|
||||
|
||||
### Latent Format Conversion
|
||||
|
||||
1. When you encounter dimension mismatch errors between audio and image models
|
||||
2. Add a "Latent Format Converter" node between the latent source and destination
|
||||
3. Set conversion mode (or use auto_detect)
|
||||
4. Choose appropriate reshape method (fold works well for audio→image)
|
||||
5. Adjust target dimensions if converting to image format
|
||||
6. Set target_channels to match your model (4 for SD/SDXL, 64 for StableAudio)
|
||||
7. Use "project" channel mode for best results when converting channels
|
||||
|
||||
### VAE Network Bending
|
||||
|
||||
1. Load a VAE or get one from a checkpoint loader
|
||||
2. Add a "VAE Network Bending" node
|
||||
3. Connect the VAE to the input
|
||||
4. Choose operation and target component (encoder/decoder/both)
|
||||
5. Set intensity and preserve_mean option
|
||||
6. Use the bent VAE for encoding/decoding
|
||||
|
||||
### VAE Latent Manipulation
|
||||
|
||||
1. Encode an image to latent space using VAE
|
||||
2. Add "VAE Latent Bending" or "VAE Channel Manipulator" nodes
|
||||
3. Connect latents through the manipulation nodes
|
||||
4. Experiment with different operations and intensities
|
||||
5. Decode back to image to see the effects
|
||||
|
||||
## Parameters
|
||||
|
||||
### Network Bending Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| operation | Select | Type of bending operation | See operations list |
|
||||
| intensity | Float | Strength of the operation | 0.0 - 1.0 |
|
||||
| target_layers | String | Layer patterns to target | Comma-separated patterns |
|
||||
| seed | Int | Random seed | -1 to 2^63-1 |
|
||||
|
||||
### Model Mixer Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| mix_mode | Select | Blending mode | See mix modes list |
|
||||
| mix_ratio | Float | Blend ratio | 0.0 - 1.0 |
|
||||
|
||||
### Latent Format Converter Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| conversion_mode | Select | Conversion direction | audio_to_image, image_to_audio, auto_detect |
|
||||
| target_height | Int | Height for image format | 8 - 512 (steps of 8) |
|
||||
| target_width | Int | Width for image format | 8 - 512 (steps of 8) |
|
||||
| reshape_method | Select | Method for reshaping | reshape, interpolate, fold, tile |
|
||||
| target_channels | Int | Target channel count | -1 to 256 (-1 keeps original) |
|
||||
| channel_mode | Select | Channel conversion method | project, pad, interpolate, tile |
|
||||
|
||||
### VAE Network Bending Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| operation | Select | VAE bending operation | See VAE operations list |
|
||||
| intensity | Float | Strength of the operation | 0.0 - 1.0 |
|
||||
| target_component | Select | Part of VAE to modify | both, encoder, decoder, latent_layers |
|
||||
| preserve_mean | Boolean | Preserve weight means | True/False |
|
||||
| seed | Int | Random seed | -1 to 2^63-1 |
|
||||
|
||||
### VAE Mixer Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| mix_mode | Select | VAE blending mode | See VAE mix modes list |
|
||||
| mix_ratio | Float | Blend ratio | 0.0 - 1.0 |
|
||||
|
||||
### VAE Latent Bending Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| operation | Select | Latent operation | See latent operations list |
|
||||
| intensity | Float | Operation strength | 0.0 - 1.0 |
|
||||
| channel_specific | Boolean | Target specific channels | True/False |
|
||||
| target_channels | String | Channel indices | Comma-separated (e.g., "0,1,2,3") |
|
||||
|
||||
### VAE Channel Manipulator Node
|
||||
|
||||
| Parameter | Type | Description | Range |
|
||||
|-----------|------|-------------|-------|
|
||||
| operation | Select | Channel operation | See channel operations list |
|
||||
| intensity | Float | Operation strength | 0.0 - 1.0 |
|
||||
| preserve_energy | Boolean | Preserve signal energy | True/False |
|
||||
| reference_latent | Latent | Reference for some ops | Optional latent input |
|
||||
|
||||
## UI Features
|
||||
|
||||
- **Visual Feedback**: Operations display information about modified layers
|
||||
- **Help Buttons**: Click "Layer Patterns Help" or "Operation Info" for detailed information
|
||||
- **Color Coding**: Different node types have distinct colors for easy identification
|
||||
- **Real-time Updates**: See which layers were modified after each operation
|
||||
|
||||
## Examples
|
||||
|
||||
### Subtle Model Variation
|
||||
- Operation: `add_noise`
|
||||
- Intensity: `0.05`
|
||||
- Target Layers: `attention`
|
||||
- Creates subtle variations while preserving model behavior
|
||||
|
||||
### Aggressive Glitch Art
|
||||
- Operation: `randomize_weights`
|
||||
- Intensity: `0.3`
|
||||
- Target Layers: `conv`
|
||||
- Creates glitchy, unpredictable outputs
|
||||
|
||||
### Model Hybridization
|
||||
- Use Model Mixer with `linear_interpolation`
|
||||
- Mix Ratio: `0.5`
|
||||
- Creates a balanced hybrid of two models
|
||||
|
||||
### Cross-Modal Generation
|
||||
- Use Latent Format Converter with `auto_detect`
|
||||
- Reshape Method: `fold` (for audio→image)
|
||||
- Enables using audio latents with image models
|
||||
|
||||
### VAE Glitch Effects
|
||||
- VAE Network Bending: `channel_corruption`
|
||||
- Target Component: `decoder`
|
||||
- Intensity: `0.2`
|
||||
- Creates colorful decoding artifacts
|
||||
|
||||
### Dreamy VAE Processing
|
||||
- VAE Latent Bending: `frequency_filter`
|
||||
- Intensity: `0.4`
|
||||
- Creates soft, dream-like images
|
||||
|
||||
### Style Transfer via VAE
|
||||
- VAE Channel Manipulator: `channel_statistics_swap`
|
||||
- Intensity: `0.7`
|
||||
- Use reference latent from style image
|
||||
- Transfers color characteristics between images
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Start with low intensity values** (0.01-0.1) and increase gradually
|
||||
2. **Target specific layers** for more controlled effects
|
||||
3. **Use seeds** for reproducible results
|
||||
4. **Combine operations** by chaining multiple bending nodes
|
||||
5. **Save interesting results** as new model checkpoints
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **No effect visible**: Increase intensity or check target_layers pattern
|
||||
- **Model breaks completely**: Reduce intensity or target fewer layers
|
||||
- **Out of memory**: Operations clone the model, ensure sufficient VRAM
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
|
||||
#### Cross-Modal Bending
|
||||
Apply image model bending techniques to audio models or vice versa for experimental results.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- ComfyUI team for the excellent framework
|
||||
- Inspired by circuit bending and glitch art techniques
|
||||
This project is licensed under the GNU General Public License v3 (GPL-3.0) - see the LICENSE file for details.
|
||||
|
||||
##
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# Audio Latent Space Manipulation Nodes for Stable Audio
|
||||
|
||||
This collection of nodes enables sophisticated audio manipulation and style transfer by working directly in the latent space of Stable Audio's VAE. This approach allows for fine-grained control over audio generation without relying solely on text prompts.
|
||||
|
||||
## Overview
|
||||
|
||||
The latent space approach offers several advantages:
|
||||
- **Direct audio-to-audio transformations** without text bottlenecks
|
||||
- **Style transfer** between different audio sources
|
||||
- **Hybrid sound generation** by blending multiple audio characteristics
|
||||
- **Feature-based manipulation** using audio analysis
|
||||
- **Real-time guidance** during the diffusion process
|
||||
|
||||
## Node Categories
|
||||
|
||||
### 1. Basic Encoding/Decoding
|
||||
|
||||
#### AudioVAEEncode
|
||||
Encodes audio waveforms into Stable Audio's latent space.
|
||||
- **Inputs**: Audio waveform, VAE model
|
||||
- **Parameters**: Normalize (bool), target sample rate
|
||||
- **Outputs**: Audio latent, latent info
|
||||
|
||||
#### AudioVAEDecode
|
||||
Decodes audio latents back to waveforms.
|
||||
- **Inputs**: Audio latent, VAE model
|
||||
- **Parameters**: Denormalize (bool)
|
||||
- **Outputs**: Audio waveform
|
||||
|
||||
### 2. Latent Manipulation
|
||||
|
||||
#### AudioLatentInterpolate
|
||||
Smoothly interpolate between two audio latents.
|
||||
- **Modes**: Linear, spherical (SLERP), cubic, sine
|
||||
- **Use cases**: Morphing between sounds, creating transitions
|
||||
|
||||
#### AudioLatentBlend
|
||||
Blend multiple audio latents with various blend modes.
|
||||
- **Modes**: Add, multiply, screen, overlay, soft light, difference, exclusion
|
||||
- **Supports**: Up to 4 audio inputs with individual weights
|
||||
|
||||
#### AudioLatentManipulator
|
||||
Apply various transformations to audio latents.
|
||||
- **Operations**:
|
||||
- Frequency shift: Shift frequency content
|
||||
- Temporal stretch: Time-stretch without pitch change
|
||||
- Harmonic emphasis: Enhance harmonic content
|
||||
- Noise injection: Add controlled noise
|
||||
- Dynamic range: Compress/expand dynamics
|
||||
- Spatial transform: Rotate in latent space
|
||||
- Resonance filter: Apply resonant filtering
|
||||
|
||||
### 3. Audio Analysis
|
||||
|
||||
#### AudioFeatureExtractor
|
||||
Extract audio features for guided manipulation.
|
||||
- **Features**: Spectral centroid, rolloff, zero-crossing rate, MFCC, bandwidth, RMS energy, tempo, onset strength
|
||||
- **Use**: Guide latent manipulations based on audio characteristics
|
||||
|
||||
### 4. Style Transfer
|
||||
|
||||
#### AudioStyleTransfer
|
||||
Transfer style characteristics between audio sources.
|
||||
- **Modes**:
|
||||
- Global: Statistics matching
|
||||
- Frequency bands: Band-wise style transfer
|
||||
- Temporal segments: Time-based style transfer
|
||||
- Adaptive: Correlation-based transfer
|
||||
- Neural style: Deep feature matching
|
||||
|
||||
#### AudioReferenceEncoder
|
||||
Encode multiple audio references into a combined representation.
|
||||
- **Combination modes**: Average, weighted, PCA, attention
|
||||
- **Features**: Segment extraction, weighted blending
|
||||
|
||||
### 5. Generation Guidance
|
||||
|
||||
#### AudioLatentGuidance
|
||||
Guide the diffusion process with reference latents.
|
||||
- **Modes**: Additive, multiplicative, attention, gradient
|
||||
- **Control**: Start/end steps, guidance strength
|
||||
|
||||
## Workflow Examples
|
||||
|
||||
### Basic Style Transfer
|
||||
```
|
||||
1. Load content and style audio
|
||||
2. Encode both with AudioVAEEncode
|
||||
3. Apply AudioStyleTransfer
|
||||
4. Decode result with AudioVAEDecode
|
||||
```
|
||||
|
||||
### Hybrid Sound Creation
|
||||
```
|
||||
1. Load multiple audio sources
|
||||
2. Encode all with AudioVAEEncode
|
||||
3. Use AudioLatentBlend to combine
|
||||
4. Apply AudioLatentManipulator for fine-tuning
|
||||
5. Decode final result
|
||||
```
|
||||
|
||||
### Feature-Guided Manipulation
|
||||
```
|
||||
1. Load reference audio
|
||||
2. Extract features with AudioFeatureExtractor
|
||||
3. Encode target audio
|
||||
4. Use AudioLatentManipulator with features
|
||||
5. Decode result
|
||||
```
|
||||
|
||||
### Guided Generation
|
||||
```
|
||||
1. Encode reference audio
|
||||
2. Apply AudioLatentGuidance to model
|
||||
3. Generate new audio with guidance
|
||||
4. Fine-tune with style transfer if needed
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Latent Space Properties
|
||||
- Stable Audio's VAE compresses audio at ~21.5Hz latent rate
|
||||
- Latents preserve both spectral and temporal information
|
||||
- Manipulation in latent space is computationally efficient
|
||||
|
||||
### Best Practices
|
||||
1. **Normalize audio** before encoding for consistent results
|
||||
2. **Match sample rates** between audio sources
|
||||
3. **Use appropriate blend modes** for your use case
|
||||
4. **Experiment with strength parameters** - start low and increase
|
||||
5. **Combine multiple techniques** for complex transformations
|
||||
|
||||
### Performance Considerations
|
||||
- Encoding/decoding is GPU-accelerated
|
||||
- Latent manipulations are memory-efficient
|
||||
- Real-time processing possible with optimization
|
||||
- Batch processing supported for multiple audio files
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Creating Instrument Hybrids
|
||||
Blend characteristics of different instruments:
|
||||
1. Encode each instrument separately
|
||||
2. Use frequency-band style transfer
|
||||
3. Apply harmonic emphasis
|
||||
4. Fine-tune with latent interpolation
|
||||
|
||||
### Environmental Audio Design
|
||||
Create complex soundscapes:
|
||||
1. Encode multiple environmental sounds
|
||||
2. Use temporal segment transfer
|
||||
3. Apply spatial transformations
|
||||
4. Blend with attention-based combination
|
||||
|
||||
### Rhythmic Pattern Transfer
|
||||
Transfer rhythm while preserving timbre:
|
||||
1. Extract tempo/onset features
|
||||
2. Use temporal stretch with feature guidance
|
||||
3. Apply style transfer in temporal segments
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Artifacts in output**: Reduce manipulation strength
|
||||
- **Loss of clarity**: Check normalization settings
|
||||
- **Mismatched lengths**: Ensure proper padding/cropping
|
||||
- **Memory issues**: Process in smaller segments
|
||||
|
||||
### Tips for Quality
|
||||
- Use high-quality source audio (44.1kHz recommended)
|
||||
- Avoid extreme parameter values
|
||||
- Test different interpolation modes
|
||||
- Monitor latent statistics for anomalies
|
||||
|
||||
## Integration with Stable Audio
|
||||
|
||||
These nodes are designed to work seamlessly with Stable Audio's generation pipeline:
|
||||
- Compatible with text conditioning
|
||||
- Can be used before/after generation
|
||||
- Supports timing embeddings
|
||||
- Works with all Stable Audio checkpoints
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned features:
|
||||
- Multi-scale latent manipulation
|
||||
- Learned projection heads for other modalities
|
||||
- Advanced spectral processing
|
||||
- Real-time preview capabilities
|
||||
- Extended feature extraction options
|
||||
|
||||
## Credits
|
||||
|
||||
This implementation is based on research in:
|
||||
- Audio VAE architectures
|
||||
- Style transfer techniques
|
||||
- Latent space manipulation
|
||||
- Audio feature analysis
|
||||
|
||||
For more information and updates, visit the project repository.
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Audio conditioning nodes for Stable Audio in ComfyUI
|
||||
"""
|
||||
|
||||
from .audio_latent_nodes import (
|
||||
AudioVAEEncode,
|
||||
AudioVAEDecode,
|
||||
AudioLatentInterpolate,
|
||||
AudioLatentBlend,
|
||||
AudioFeatureExtractor,
|
||||
AudioLatentManipulator,
|
||||
)
|
||||
|
||||
from .audio_style_transfer import (
|
||||
AudioStyleTransfer,
|
||||
AudioLatentGuidance,
|
||||
AudioReferenceEncoder
|
||||
)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AudioVAEEncode": AudioVAEEncode,
|
||||
"AudioVAEDecode": AudioVAEDecode,
|
||||
"AudioLatentInterpolate": AudioLatentInterpolate,
|
||||
"AudioLatentBlend": AudioLatentBlend,
|
||||
"AudioFeatureExtractor": AudioFeatureExtractor,
|
||||
"AudioLatentManipulator": AudioLatentManipulator,
|
||||
"AudioStyleTransfer": AudioStyleTransfer,
|
||||
"AudioLatentGuidance": AudioLatentGuidance,
|
||||
"AudioReferenceEncoder": AudioReferenceEncoder,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AudioVAEEncode": "Audio VAE Encode",
|
||||
"AudioVAEDecode": "Audio VAE Decode",
|
||||
"AudioLatentInterpolate": "Audio Latent Interpolate",
|
||||
"AudioLatentBlend": "Audio Latent Blend",
|
||||
"AudioFeatureExtractor": "Audio Feature Extractor",
|
||||
"AudioLatentManipulator": "Audio Latent Manipulator",
|
||||
"AudioStyleTransfer": "Audio Style Transfer",
|
||||
"AudioLatentGuidance": "Audio Latent Guidance",
|
||||
"AudioReferenceEncoder": "Audio Reference Encoder",
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -0,0 +1,638 @@
|
||||
"""
|
||||
Audio Latent Space Manipulation Nodes for Stable Audio
|
||||
These nodes enable working directly with audio latents for style transfer and hybrid generation
|
||||
"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from typing import Dict, List, Tuple, Any, Optional
|
||||
import torchaudio
|
||||
import librosa
|
||||
|
||||
# Import the additional nodes from the style transfer module
|
||||
from .audio_style_transfer import (
|
||||
AudioStyleTransfer,
|
||||
AudioLatentGuidance,
|
||||
AudioReferenceEncoder
|
||||
)
|
||||
|
||||
class AudioVAEEncode:
|
||||
"""
|
||||
Encode audio waveforms into Stable Audio latent space
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"audio": ("AUDIO", {"tooltip": "Audio waveform to encode"}),
|
||||
"vae": ("VAE", {"tooltip": "Stable Audio VAE model"}),
|
||||
},
|
||||
"optional": {
|
||||
"normalize": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Normalize audio before encoding"
|
||||
}),
|
||||
"target_sample_rate": ("INT", {
|
||||
"default": 44100,
|
||||
"min": 16000,
|
||||
"max": 48000,
|
||||
"tooltip": "Target sample rate for encoding"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT", "LATENT_INFO")
|
||||
RETURN_NAMES = ("latent", "info")
|
||||
FUNCTION = "encode_audio"
|
||||
CATEGORY = "audio/latent"
|
||||
|
||||
def encode_audio(self, audio, vae, normalize=True, target_sample_rate=44100):
|
||||
# Extract audio tensor and sample rate
|
||||
waveform, sample_rate = audio
|
||||
|
||||
# Resample if necessary
|
||||
if sample_rate != target_sample_rate:
|
||||
resampler = torchaudio.transforms.Resample(sample_rate, target_sample_rate)
|
||||
waveform = resampler(waveform)
|
||||
|
||||
# Normalize audio
|
||||
if normalize:
|
||||
waveform = waveform / torch.max(torch.abs(waveform))
|
||||
|
||||
# Ensure correct shape for VAE (batch, channels, samples)
|
||||
if waveform.dim() == 2:
|
||||
waveform = waveform.unsqueeze(0)
|
||||
|
||||
# Encode with VAE
|
||||
with torch.no_grad():
|
||||
# Move to same device as VAE
|
||||
device = next(vae.parameters()).device
|
||||
waveform = waveform.to(device)
|
||||
|
||||
# Encode
|
||||
latent = vae.encode(waveform)
|
||||
|
||||
# Get latent statistics
|
||||
latent_mean = latent.mean()
|
||||
latent_std = latent.std()
|
||||
|
||||
# Create info dictionary
|
||||
info = {
|
||||
"original_shape": waveform.shape,
|
||||
"latent_shape": latent.shape,
|
||||
"sample_rate": target_sample_rate,
|
||||
"latent_mean": latent_mean.item(),
|
||||
"latent_std": latent_std.item(),
|
||||
}
|
||||
|
||||
return (latent, info)
|
||||
|
||||
class AudioVAEDecode:
|
||||
"""
|
||||
Decode audio latents back to waveforms
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latent": ("AUDIO_LATENT", {"tooltip": "Audio latent to decode"}),
|
||||
"vae": ("VAE", {"tooltip": "Stable Audio VAE model"}),
|
||||
},
|
||||
"optional": {
|
||||
"denormalize": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Denormalize output audio"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO",)
|
||||
RETURN_NAMES = ("audio",)
|
||||
FUNCTION = "decode_audio"
|
||||
CATEGORY = "audio/latent"
|
||||
|
||||
def decode_audio(self, latent, vae, denormalize=True):
|
||||
# Decode with VAE
|
||||
with torch.no_grad():
|
||||
# Ensure latent is on correct device
|
||||
device = next(vae.parameters()).device
|
||||
latent = latent.to(device)
|
||||
|
||||
# Decode
|
||||
waveform = vae.decode(latent)
|
||||
|
||||
# Move to CPU for further processing
|
||||
waveform = waveform.cpu()
|
||||
|
||||
# Denormalize if needed
|
||||
if denormalize:
|
||||
# Ensure audio is in valid range [-1, 1]
|
||||
waveform = torch.clamp(waveform, -1.0, 1.0)
|
||||
|
||||
# Extract sample rate (default to 44100 if not stored)
|
||||
sample_rate = 44100
|
||||
|
||||
# Return as (waveform, sample_rate) tuple
|
||||
return ((waveform, sample_rate),)
|
||||
|
||||
class AudioLatentInterpolate:
|
||||
"""
|
||||
Interpolate between two audio latents for smooth transitions and hybrid sounds
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latent_a": ("AUDIO_LATENT", {"tooltip": "First audio latent"}),
|
||||
"latent_b": ("AUDIO_LATENT", {"tooltip": "Second audio latent"}),
|
||||
"interpolation_factor": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Interpolation factor (0=A, 1=B)"
|
||||
}),
|
||||
"interpolation_mode": ([
|
||||
"linear",
|
||||
"spherical",
|
||||
"cubic",
|
||||
"sine"
|
||||
], {
|
||||
"default": "spherical",
|
||||
"tooltip": "Interpolation method"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"curve_power": ("FLOAT", {
|
||||
"default": 1.0,
|
||||
"min": 0.1,
|
||||
"max": 10.0,
|
||||
"step": 0.1,
|
||||
"tooltip": "Power curve for non-linear interpolation"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT",)
|
||||
RETURN_NAMES = ("latent",)
|
||||
FUNCTION = "interpolate_latents"
|
||||
CATEGORY = "audio/latent"
|
||||
|
||||
def interpolate_latents(self, latent_a, latent_b, interpolation_factor,
|
||||
interpolation_mode, curve_power=1.0):
|
||||
# Ensure latents are on the same device
|
||||
device = latent_a.device
|
||||
latent_b = latent_b.to(device)
|
||||
|
||||
# Apply curve to interpolation factor if needed
|
||||
if curve_power != 1.0:
|
||||
interpolation_factor = interpolation_factor ** curve_power
|
||||
|
||||
if interpolation_mode == "linear":
|
||||
# Simple linear interpolation
|
||||
result = (1 - interpolation_factor) * latent_a + interpolation_factor * latent_b
|
||||
|
||||
elif interpolation_mode == "spherical":
|
||||
# Spherical linear interpolation (SLERP)
|
||||
# Normalize latents
|
||||
latent_a_norm = F.normalize(latent_a.flatten(1), dim=1).reshape(latent_a.shape)
|
||||
latent_b_norm = F.normalize(latent_b.flatten(1), dim=1).reshape(latent_b.shape)
|
||||
|
||||
# Compute angle between latents
|
||||
dot_product = (latent_a_norm * latent_b_norm).sum()
|
||||
angle = torch.acos(torch.clamp(dot_product, -1, 1))
|
||||
|
||||
# SLERP formula
|
||||
if angle < 1e-6:
|
||||
result = (1 - interpolation_factor) * latent_a + interpolation_factor * latent_b
|
||||
else:
|
||||
sin_angle = torch.sin(angle)
|
||||
result = (torch.sin((1 - interpolation_factor) * angle) / sin_angle) * latent_a + \
|
||||
(torch.sin(interpolation_factor * angle) / sin_angle) * latent_b
|
||||
|
||||
elif interpolation_mode == "cubic":
|
||||
# Cubic interpolation (smooth start and end)
|
||||
t = interpolation_factor
|
||||
t2 = t * t
|
||||
t3 = t2 * t
|
||||
factor = 3 * t2 - 2 * t3
|
||||
result = (1 - factor) * latent_a + factor * latent_b
|
||||
|
||||
elif interpolation_mode == "sine":
|
||||
# Sine-based interpolation
|
||||
factor = 0.5 * (1 + torch.sin(torch.tensor(np.pi * (interpolation_factor - 0.5))))
|
||||
result = (1 - factor) * latent_a + factor * latent_b
|
||||
|
||||
return (result,)
|
||||
|
||||
class AudioLatentBlend:
|
||||
"""
|
||||
Blend multiple audio latents with customizable weights
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latent_a": ("AUDIO_LATENT", {"tooltip": "First audio latent"}),
|
||||
"latent_b": ("AUDIO_LATENT", {"tooltip": "Second audio latent"}),
|
||||
"blend_mode": ([
|
||||
"add",
|
||||
"multiply",
|
||||
"screen",
|
||||
"overlay",
|
||||
"soft_light",
|
||||
"difference",
|
||||
"exclusion"
|
||||
], {
|
||||
"default": "add",
|
||||
"tooltip": "Blending mode"
|
||||
}),
|
||||
"blend_factor": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 2.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Blend strength"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"latent_c": ("AUDIO_LATENT", {"tooltip": "Optional third audio latent"}),
|
||||
"latent_d": ("AUDIO_LATENT", {"tooltip": "Optional fourth audio latent"}),
|
||||
"weight_a": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_b": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_c": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_d": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"normalize": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT",)
|
||||
RETURN_NAMES = ("latent",)
|
||||
FUNCTION = "blend_latents"
|
||||
CATEGORY = "audio/latent"
|
||||
|
||||
def blend_latents(self, latent_a, latent_b, blend_mode, blend_factor,
|
||||
latent_c=None, latent_d=None,
|
||||
weight_a=1.0, weight_b=1.0, weight_c=1.0, weight_d=1.0,
|
||||
normalize=True):
|
||||
|
||||
device = latent_a.device
|
||||
|
||||
# Collect all available latents and weights
|
||||
latents = [latent_a, latent_b]
|
||||
weights = [weight_a, weight_b]
|
||||
|
||||
if latent_c is not None:
|
||||
latents.append(latent_c.to(device))
|
||||
weights.append(weight_c)
|
||||
if latent_d is not None:
|
||||
latents.append(latent_d.to(device))
|
||||
weights.append(weight_d)
|
||||
|
||||
# Normalize weights if requested
|
||||
if normalize:
|
||||
total_weight = sum(weights)
|
||||
weights = [w / total_weight for w in weights]
|
||||
|
||||
# Apply blend mode
|
||||
if blend_mode == "add":
|
||||
# Weighted addition
|
||||
result = sum(latent * weight for latent, weight in zip(latents, weights))
|
||||
result = result * blend_factor + latents[0] * (1 - blend_factor)
|
||||
|
||||
elif blend_mode == "multiply":
|
||||
# Multiplicative blending
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
blend = latents[i] * weights[i]
|
||||
result = result * (1 + blend_factor * (blend - 1))
|
||||
|
||||
elif blend_mode == "screen":
|
||||
# Screen blending (inverse multiply)
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
blend = 1 - (1 - result) * (1 - latents[i] * weights[i])
|
||||
result = result * (1 - blend_factor) + blend * blend_factor
|
||||
|
||||
elif blend_mode == "overlay":
|
||||
# Overlay blending
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
mask = (result < 0.5).float()
|
||||
blend = mask * (2 * result * latents[i] * weights[i]) + \
|
||||
(1 - mask) * (1 - 2 * (1 - result) * (1 - latents[i] * weights[i]))
|
||||
result = result * (1 - blend_factor) + blend * blend_factor
|
||||
|
||||
elif blend_mode == "soft_light":
|
||||
# Soft light blending
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
blend = latents[i] * weights[i]
|
||||
soft = (1 - 2 * blend) * result * result + 2 * blend * result
|
||||
result = result * (1 - blend_factor) + soft * blend_factor
|
||||
|
||||
elif blend_mode == "difference":
|
||||
# Difference blending
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
diff = torch.abs(result - latents[i] * weights[i])
|
||||
result = result * (1 - blend_factor) + diff * blend_factor
|
||||
|
||||
elif blend_mode == "exclusion":
|
||||
# Exclusion blending
|
||||
result = latents[0]
|
||||
for i in range(1, len(latents)):
|
||||
excl = result + latents[i] * weights[i] - 2 * result * latents[i] * weights[i]
|
||||
result = result * (1 - blend_factor) + excl * blend_factor
|
||||
|
||||
return (result,)
|
||||
|
||||
class AudioFeatureExtractor:
|
||||
"""
|
||||
Extract various audio features for latent manipulation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"audio": ("AUDIO", {"tooltip": "Audio to analyze"}),
|
||||
"feature_type": ([
|
||||
"spectral_centroid",
|
||||
"spectral_rolloff",
|
||||
"zero_crossing_rate",
|
||||
"mfcc",
|
||||
"spectral_bandwidth",
|
||||
"rms_energy",
|
||||
"tempo",
|
||||
"onset_strength"
|
||||
], {
|
||||
"default": "spectral_centroid",
|
||||
"tooltip": "Type of audio feature to extract"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"n_mfcc": ("INT", {
|
||||
"default": 13,
|
||||
"min": 1,
|
||||
"max": 40,
|
||||
"tooltip": "Number of MFCC coefficients"
|
||||
}),
|
||||
"hop_length": ("INT", {
|
||||
"default": 512,
|
||||
"min": 64,
|
||||
"max": 2048,
|
||||
"tooltip": "Hop length for feature extraction"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_FEATURES",)
|
||||
RETURN_NAMES = ("features",)
|
||||
FUNCTION = "extract_features"
|
||||
CATEGORY = "audio/analysis"
|
||||
|
||||
def extract_features(self, audio, feature_type, n_mfcc=13, hop_length=512):
|
||||
waveform, sample_rate = audio
|
||||
|
||||
# Convert to numpy for librosa
|
||||
if isinstance(waveform, torch.Tensor):
|
||||
waveform_np = waveform.squeeze().cpu().numpy()
|
||||
else:
|
||||
waveform_np = waveform
|
||||
|
||||
# Extract features based on type
|
||||
if feature_type == "spectral_centroid":
|
||||
features = librosa.feature.spectral_centroid(
|
||||
y=waveform_np, sr=sample_rate, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "spectral_rolloff":
|
||||
features = librosa.feature.spectral_rolloff(
|
||||
y=waveform_np, sr=sample_rate, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "zero_crossing_rate":
|
||||
features = librosa.feature.zero_crossing_rate(
|
||||
waveform_np, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "mfcc":
|
||||
features = librosa.feature.mfcc(
|
||||
y=waveform_np, sr=sample_rate, n_mfcc=n_mfcc, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "spectral_bandwidth":
|
||||
features = librosa.feature.spectral_bandwidth(
|
||||
y=waveform_np, sr=sample_rate, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "rms_energy":
|
||||
features = librosa.feature.rms(
|
||||
y=waveform_np, hop_length=hop_length
|
||||
)
|
||||
elif feature_type == "tempo":
|
||||
tempo, beats = librosa.beat.beat_track(
|
||||
y=waveform_np, sr=sample_rate, hop_length=hop_length
|
||||
)
|
||||
features = np.array([[tempo]])
|
||||
elif feature_type == "onset_strength":
|
||||
features = librosa.onset.onset_strength(
|
||||
y=waveform_np, sr=sample_rate, hop_length=hop_length
|
||||
).reshape(1, -1)
|
||||
|
||||
# Convert to tensor
|
||||
features_tensor = torch.from_numpy(features).float()
|
||||
|
||||
# Create feature dictionary
|
||||
feature_dict = {
|
||||
"type": feature_type,
|
||||
"data": features_tensor,
|
||||
"sample_rate": sample_rate,
|
||||
"hop_length": hop_length,
|
||||
"shape": features_tensor.shape
|
||||
}
|
||||
|
||||
return (feature_dict,)
|
||||
|
||||
class AudioLatentManipulator:
|
||||
"""
|
||||
Manipulate audio latents based on extracted features or manual controls
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latent": ("AUDIO_LATENT", {"tooltip": "Audio latent to manipulate"}),
|
||||
"manipulation_type": ([
|
||||
"frequency_shift",
|
||||
"temporal_stretch",
|
||||
"harmonic_emphasis",
|
||||
"noise_injection",
|
||||
"dynamic_range",
|
||||
"spatial_transform",
|
||||
"resonance_filter"
|
||||
], {
|
||||
"default": "frequency_shift",
|
||||
"tooltip": "Type of manipulation to apply"
|
||||
}),
|
||||
"strength": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 2.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Manipulation strength"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"features": ("AUDIO_FEATURES", {"tooltip": "Optional features to guide manipulation"}),
|
||||
"frequency": ("FLOAT", {
|
||||
"default": 440.0,
|
||||
"min": 20.0,
|
||||
"max": 20000.0,
|
||||
"tooltip": "Target frequency for frequency-based operations"
|
||||
}),
|
||||
"time_factor": ("FLOAT", {
|
||||
"default": 1.0,
|
||||
"min": 0.25,
|
||||
"max": 4.0,
|
||||
"tooltip": "Time stretch factor"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT",)
|
||||
RETURN_NAMES = ("latent",)
|
||||
FUNCTION = "manipulate_latent"
|
||||
CATEGORY = "audio/latent"
|
||||
|
||||
def manipulate_latent(self, latent, manipulation_type, strength,
|
||||
features=None, frequency=440.0, time_factor=1.0):
|
||||
device = latent.device
|
||||
result = latent.clone()
|
||||
|
||||
# Get latent dimensions
|
||||
batch, channels, *spatial_dims = result.shape
|
||||
|
||||
if manipulation_type == "frequency_shift":
|
||||
# Shift frequency content in latent space
|
||||
# Apply FFT-like transformation in latent space
|
||||
if len(spatial_dims) == 2:
|
||||
# 2D latent (height, width)
|
||||
fft_latent = torch.fft.fft2(result)
|
||||
# Shift frequencies
|
||||
shift_amount = int(strength * spatial_dims[1] * frequency / 22050)
|
||||
fft_latent = torch.roll(fft_latent, shifts=shift_amount, dims=-1)
|
||||
result = torch.fft.ifft2(fft_latent).real
|
||||
else:
|
||||
# 1D latent
|
||||
fft_latent = torch.fft.fft(result)
|
||||
shift_amount = int(strength * spatial_dims[0] * frequency / 22050)
|
||||
fft_latent = torch.roll(fft_latent, shifts=shift_amount, dims=-1)
|
||||
result = torch.fft.ifft(fft_latent).real
|
||||
|
||||
elif manipulation_type == "temporal_stretch":
|
||||
# Stretch or compress temporal dimension
|
||||
if len(spatial_dims) >= 1:
|
||||
time_dim = spatial_dims[-1]
|
||||
new_time_dim = int(time_dim * time_factor)
|
||||
|
||||
# Interpolate along time dimension
|
||||
if len(spatial_dims) == 2:
|
||||
result = F.interpolate(result, size=(spatial_dims[0], new_time_dim),
|
||||
mode='bilinear', align_corners=False)
|
||||
else:
|
||||
result = F.interpolate(result, size=new_time_dim,
|
||||
mode='linear', align_corners=False)
|
||||
|
||||
# Adjust back to original size with padding or cropping
|
||||
if new_time_dim > time_dim:
|
||||
result = result[..., :time_dim]
|
||||
elif new_time_dim < time_dim:
|
||||
pad_size = time_dim - new_time_dim
|
||||
result = F.pad(result, (0, pad_size))
|
||||
|
||||
elif manipulation_type == "harmonic_emphasis":
|
||||
# Emphasize harmonic content
|
||||
# Create harmonic mask in frequency domain
|
||||
if len(spatial_dims) >= 1:
|
||||
fft_latent = torch.fft.fft(result, dim=-1)
|
||||
magnitudes = torch.abs(fft_latent)
|
||||
|
||||
# Find peaks (harmonics)
|
||||
kernel_size = 5
|
||||
maxpool = nn.MaxPool1d(kernel_size, stride=1, padding=kernel_size//2)
|
||||
if len(spatial_dims) == 2:
|
||||
mag_flat = magnitudes.reshape(batch * channels * spatial_dims[0], -1)
|
||||
peaks = (magnitudes == maxpool(mag_flat).reshape(magnitudes.shape))
|
||||
else:
|
||||
peaks = (magnitudes == maxpool(magnitudes))
|
||||
|
||||
# Emphasize peaks
|
||||
fft_latent = fft_latent * (1 + strength * peaks.float())
|
||||
result = torch.fft.ifft(fft_latent, dim=-1).real
|
||||
|
||||
elif manipulation_type == "noise_injection":
|
||||
# Inject controlled noise
|
||||
noise = torch.randn_like(result) * strength * 0.1
|
||||
if features is not None and "data" in features:
|
||||
# Modulate noise by features
|
||||
feature_data = features["data"].to(device)
|
||||
if feature_data.numel() > 0:
|
||||
feature_scalar = feature_data.mean().item()
|
||||
noise = noise * (1 + feature_scalar)
|
||||
result = result + noise
|
||||
|
||||
elif manipulation_type == "dynamic_range":
|
||||
# Compress or expand dynamic range
|
||||
# Apply soft clipping/expansion
|
||||
threshold = 0.5
|
||||
if strength < 1.0:
|
||||
# Compression
|
||||
mask = torch.abs(result) > threshold
|
||||
result = torch.where(mask,
|
||||
torch.sign(result) * (threshold + (torch.abs(result) - threshold) * strength),
|
||||
result)
|
||||
else:
|
||||
# Expansion
|
||||
mask = torch.abs(result) < threshold
|
||||
result = torch.where(mask,
|
||||
result * strength,
|
||||
result)
|
||||
|
||||
elif manipulation_type == "spatial_transform":
|
||||
# Transform spatial relationships in latent
|
||||
if len(spatial_dims) == 2:
|
||||
# Rotate latent space
|
||||
angle = strength * np.pi
|
||||
cos_a = torch.cos(torch.tensor(angle))
|
||||
sin_a = torch.sin(torch.tensor(angle))
|
||||
|
||||
# Create rotation matrix
|
||||
rot_matrix = torch.tensor([[cos_a, -sin_a], [sin_a, cos_a]], device=device)
|
||||
|
||||
# Apply rotation (simplified - works on channel pairs)
|
||||
if channels >= 2:
|
||||
for i in range(0, channels-1, 2):
|
||||
pair = result[:, i:i+2].reshape(batch, 2, -1)
|
||||
rotated = torch.matmul(rot_matrix, pair)
|
||||
result[:, i:i+2] = rotated.reshape(batch, 2, *spatial_dims)
|
||||
|
||||
elif manipulation_type == "resonance_filter":
|
||||
# Apply resonant filtering in latent space
|
||||
if len(spatial_dims) >= 1:
|
||||
# Create resonance kernel
|
||||
center_freq = frequency / 22050 # Normalize frequency
|
||||
bandwidth = 0.1 * strength
|
||||
|
||||
freqs = torch.linspace(0, 1, spatial_dims[-1], device=device)
|
||||
resonance = torch.exp(-((freqs - center_freq) ** 2) / (2 * bandwidth ** 2))
|
||||
|
||||
# Apply in frequency domain
|
||||
fft_latent = torch.fft.fft(result, dim=-1)
|
||||
fft_latent = fft_latent * resonance
|
||||
result = torch.fft.ifft(fft_latent, dim=-1).real
|
||||
|
||||
return (result,)
|
||||
@@ -0,0 +1,485 @@
|
||||
"""
|
||||
Audio Style Transfer and Advanced Latent Manipulation Nodes
|
||||
"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from typing import Dict, List, Tuple, Any, Optional
|
||||
|
||||
class AudioStyleTransfer:
|
||||
"""
|
||||
Transfer style characteristics from one audio to another in latent space
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"content_latent": ("AUDIO_LATENT", {"tooltip": "Content audio latent"}),
|
||||
"style_latent": ("AUDIO_LATENT", {"tooltip": "Style audio latent"}),
|
||||
"transfer_mode": ([
|
||||
"global",
|
||||
"frequency_bands",
|
||||
"temporal_segments",
|
||||
"adaptive",
|
||||
"neural_style"
|
||||
], {
|
||||
"default": "adaptive",
|
||||
"tooltip": "Style transfer mode"
|
||||
}),
|
||||
"style_strength": ("FLOAT", {
|
||||
"default": 0.7,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "How much style to transfer"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"preserve_content": ("FLOAT", {
|
||||
"default": 0.3,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"tooltip": "How much original content to preserve"
|
||||
}),
|
||||
"frequency_bands": ("INT", {
|
||||
"default": 4,
|
||||
"min": 2,
|
||||
"max": 16,
|
||||
"tooltip": "Number of frequency bands for band-wise transfer"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT",)
|
||||
RETURN_NAMES = ("latent",)
|
||||
FUNCTION = "transfer_style"
|
||||
CATEGORY = "audio/style"
|
||||
|
||||
def transfer_style(self, content_latent, style_latent, transfer_mode,
|
||||
style_strength, preserve_content=0.3, frequency_bands=4):
|
||||
device = content_latent.device
|
||||
style_latent = style_latent.to(device)
|
||||
|
||||
if transfer_mode == "global":
|
||||
# Global style transfer using statistics matching
|
||||
# Compute statistics
|
||||
content_mean = content_latent.mean(dim=(2, 3), keepdim=True) if content_latent.dim() == 4 else content_latent.mean(dim=2, keepdim=True)
|
||||
content_std = content_latent.std(dim=(2, 3), keepdim=True) if content_latent.dim() == 4 else content_latent.std(dim=2, keepdim=True)
|
||||
|
||||
style_mean = style_latent.mean(dim=(2, 3), keepdim=True) if style_latent.dim() == 4 else style_latent.mean(dim=2, keepdim=True)
|
||||
style_std = style_latent.std(dim=(2, 3), keepdim=True) if style_latent.dim() == 4 else style_latent.std(dim=2, keepdim=True)
|
||||
|
||||
# Normalize content and apply style statistics
|
||||
normalized = (content_latent - content_mean) / (content_std + 1e-6)
|
||||
stylized = normalized * style_std + style_mean
|
||||
|
||||
# Blend with original
|
||||
result = stylized * style_strength + content_latent * preserve_content
|
||||
|
||||
elif transfer_mode == "frequency_bands":
|
||||
# Transfer style in different frequency bands
|
||||
result = content_latent.clone()
|
||||
|
||||
# Apply FFT
|
||||
fft_content = torch.fft.fft(content_latent, dim=-1)
|
||||
fft_style = torch.fft.fft(style_latent, dim=-1)
|
||||
|
||||
# Split into frequency bands
|
||||
freq_size = fft_content.shape[-1]
|
||||
band_size = freq_size // frequency_bands
|
||||
|
||||
for i in range(frequency_bands):
|
||||
start_idx = i * band_size
|
||||
end_idx = (i + 1) * band_size if i < frequency_bands - 1 else freq_size
|
||||
|
||||
# Transfer magnitude in this band
|
||||
content_mag = torch.abs(fft_content[..., start_idx:end_idx])
|
||||
content_phase = torch.angle(fft_content[..., start_idx:end_idx])
|
||||
style_mag = torch.abs(fft_style[..., start_idx:end_idx])
|
||||
|
||||
# Blend magnitudes
|
||||
new_mag = style_mag * style_strength + content_mag * preserve_content
|
||||
|
||||
# Reconstruct with new magnitude
|
||||
fft_content[..., start_idx:end_idx] = new_mag * torch.exp(1j * content_phase)
|
||||
|
||||
# Inverse FFT
|
||||
result = torch.fft.ifft(fft_content, dim=-1).real
|
||||
|
||||
elif transfer_mode == "temporal_segments":
|
||||
# Transfer style in temporal segments
|
||||
time_dim = content_latent.shape[-1]
|
||||
segment_size = time_dim // 4 # 4 segments
|
||||
|
||||
result = content_latent.clone()
|
||||
|
||||
for i in range(4):
|
||||
start_idx = i * segment_size
|
||||
end_idx = (i + 1) * segment_size if i < 3 else time_dim
|
||||
|
||||
# Get segments
|
||||
content_seg = content_latent[..., start_idx:end_idx]
|
||||
style_seg = style_latent[..., start_idx:end_idx]
|
||||
|
||||
# Transfer style in segment
|
||||
seg_mean_c = content_seg.mean()
|
||||
seg_std_c = content_seg.std()
|
||||
seg_mean_s = style_seg.mean()
|
||||
seg_std_s = style_seg.std()
|
||||
|
||||
normalized_seg = (content_seg - seg_mean_c) / (seg_std_c + 1e-6)
|
||||
stylized_seg = normalized_seg * seg_std_s + seg_mean_s
|
||||
|
||||
result[..., start_idx:end_idx] = stylized_seg * style_strength + content_seg * preserve_content
|
||||
|
||||
elif transfer_mode == "adaptive":
|
||||
# Adaptive style transfer using correlation
|
||||
# Flatten spatial dimensions
|
||||
content_flat = content_latent.reshape(content_latent.shape[0], content_latent.shape[1], -1)
|
||||
style_flat = style_latent.reshape(style_latent.shape[0], style_latent.shape[1], -1)
|
||||
|
||||
# Compute correlation matrix
|
||||
content_cov = torch.matmul(content_flat, content_flat.transpose(1, 2))
|
||||
style_cov = torch.matmul(style_flat, style_flat.transpose(1, 2))
|
||||
|
||||
# Eigen decomposition for whitening and coloring
|
||||
content_eig_val, content_eig_vec = torch.linalg.eigh(content_cov)
|
||||
style_eig_val, style_eig_vec = torch.linalg.eigh(style_cov)
|
||||
|
||||
# Whitening transform
|
||||
content_eig_val = torch.clamp(content_eig_val, min=1e-6)
|
||||
whitening = torch.matmul(content_eig_vec, torch.diag_embed(1.0 / torch.sqrt(content_eig_val)))
|
||||
|
||||
# Coloring transform
|
||||
style_eig_val = torch.clamp(style_eig_val, min=1e-6)
|
||||
coloring = torch.matmul(style_eig_vec, torch.diag_embed(torch.sqrt(style_eig_val)))
|
||||
|
||||
# Apply transforms
|
||||
whitened = torch.matmul(whitening.transpose(1, 2), content_flat)
|
||||
stylized = torch.matmul(coloring, whitened)
|
||||
|
||||
# Reshape back
|
||||
stylized = stylized.reshape(content_latent.shape)
|
||||
result = stylized * style_strength + content_latent * preserve_content
|
||||
|
||||
elif transfer_mode == "neural_style":
|
||||
# Neural style transfer inspired approach
|
||||
# Use convolutional layers to extract features
|
||||
conv1 = nn.Conv1d(content_latent.shape[1], 64, 3, padding=1).to(device)
|
||||
conv2 = nn.Conv1d(64, 32, 3, padding=1).to(device)
|
||||
|
||||
# Extract features
|
||||
content_feat1 = F.relu(conv1(content_latent.squeeze(2) if content_latent.dim() == 4 else content_latent))
|
||||
content_feat2 = F.relu(conv2(content_feat1))
|
||||
|
||||
style_feat1 = F.relu(conv1(style_latent.squeeze(2) if style_latent.dim() == 4 else style_latent))
|
||||
style_feat2 = F.relu(conv2(style_feat1))
|
||||
|
||||
# Compute Gram matrices
|
||||
def gram_matrix(features):
|
||||
b, c, w = features.size()
|
||||
features = features.view(b, c, w)
|
||||
gram = torch.bmm(features, features.transpose(1, 2))
|
||||
return gram / (c * w)
|
||||
|
||||
content_gram = gram_matrix(content_feat2)
|
||||
style_gram = gram_matrix(style_feat2)
|
||||
|
||||
# Match Gram matrices
|
||||
target_gram = style_gram * style_strength + content_gram * preserve_content
|
||||
|
||||
# Approximate reconstruction (simplified)
|
||||
result = content_latent + 0.1 * (target_gram.mean() - content_gram.mean())
|
||||
|
||||
return (result,)
|
||||
|
||||
class AudioLatentGuidance:
|
||||
"""
|
||||
Guide audio generation with reference latents during the diffusion process
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL", {"tooltip": "Stable Audio diffusion model"}),
|
||||
"reference_latent": ("AUDIO_LATENT", {"tooltip": "Reference audio latent for guidance"}),
|
||||
"guidance_strength": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 2.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Strength of latent guidance"
|
||||
}),
|
||||
"guidance_mode": ([
|
||||
"additive",
|
||||
"multiplicative",
|
||||
"attention",
|
||||
"gradient"
|
||||
], {
|
||||
"default": "attention",
|
||||
"tooltip": "How to apply guidance"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"start_at_step": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 1000,
|
||||
"tooltip": "Step to start applying guidance"
|
||||
}),
|
||||
"end_at_step": ("INT", {
|
||||
"default": 1000,
|
||||
"min": 0,
|
||||
"max": 1000,
|
||||
"tooltip": "Step to stop applying guidance"
|
||||
}),
|
||||
"features": ("AUDIO_FEATURES", {"tooltip": "Optional features to modulate guidance"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
RETURN_NAMES = ("model",)
|
||||
FUNCTION = "apply_guidance"
|
||||
CATEGORY = "audio/conditioning"
|
||||
|
||||
def apply_guidance(self, model, reference_latent, guidance_strength, guidance_mode,
|
||||
start_at_step=0, end_at_step=1000, features=None):
|
||||
# Clone the model to avoid modifying the original
|
||||
guided_model = model.clone()
|
||||
|
||||
# Store guidance parameters in the model
|
||||
guided_model.audio_latent_guidance = {
|
||||
"reference_latent": reference_latent,
|
||||
"strength": guidance_strength,
|
||||
"mode": guidance_mode,
|
||||
"start_step": start_at_step,
|
||||
"end_step": end_at_step,
|
||||
"features": features
|
||||
}
|
||||
|
||||
# Patch the model's forward method to include guidance
|
||||
original_forward = guided_model.model.forward
|
||||
|
||||
def guided_forward(x, timestep, context=None, **kwargs):
|
||||
# Get current step from timestep
|
||||
current_step = timestep[0].item() if isinstance(timestep, torch.Tensor) else timestep
|
||||
|
||||
# Check if we should apply guidance
|
||||
guidance = guided_model.audio_latent_guidance
|
||||
if guidance["start_step"] <= current_step <= guidance["end_step"]:
|
||||
ref_latent = guidance["reference_latent"].to(x.device)
|
||||
strength = guidance["strength"]
|
||||
|
||||
if guidance["mode"] == "additive":
|
||||
# Add reference latent influence
|
||||
x = x + strength * 0.1 * ref_latent
|
||||
|
||||
elif guidance["mode"] == "multiplicative":
|
||||
# Multiplicative guidance
|
||||
ref_norm = F.normalize(ref_latent.flatten(1), dim=1).reshape(ref_latent.shape)
|
||||
x = x * (1 + strength * ref_norm)
|
||||
|
||||
elif guidance["mode"] == "attention":
|
||||
# Attention-based guidance (modify context)
|
||||
if context is not None:
|
||||
# Add reference latent information to context
|
||||
ref_context = ref_latent.mean(dim=(2, 3), keepdim=True) if ref_latent.dim() == 4 else ref_latent.mean(dim=2, keepdim=True)
|
||||
ref_context = ref_context.expand_as(context)
|
||||
context = context + strength * 0.1 * ref_context
|
||||
|
||||
elif guidance["mode"] == "gradient":
|
||||
# Gradient-based guidance
|
||||
# Compute similarity between x and reference
|
||||
similarity = F.cosine_similarity(x.flatten(1), ref_latent.flatten(1), dim=1)
|
||||
similarity = similarity.mean()
|
||||
|
||||
# Modify x to increase similarity
|
||||
grad_direction = ref_latent - x
|
||||
x = x + strength * 0.05 * grad_direction * (1 - similarity)
|
||||
|
||||
# Call original forward
|
||||
return original_forward(x, timestep, context, **kwargs)
|
||||
|
||||
# Replace forward method
|
||||
guided_model.model.forward = guided_forward
|
||||
|
||||
return (guided_model,)
|
||||
|
||||
class AudioReferenceEncoder:
|
||||
"""
|
||||
Encode multiple audio references into a combined latent representation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"vae": ("VAE", {"tooltip": "Stable Audio VAE model"}),
|
||||
"audio_a": ("AUDIO", {"tooltip": "First reference audio"}),
|
||||
"combination_mode": ([
|
||||
"average",
|
||||
"weighted",
|
||||
"pca",
|
||||
"attention"
|
||||
], {
|
||||
"default": "attention",
|
||||
"tooltip": "How to combine multiple references"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"audio_b": ("AUDIO", {"tooltip": "Second reference audio"}),
|
||||
"audio_c": ("AUDIO", {"tooltip": "Third reference audio"}),
|
||||
"audio_d": ("AUDIO", {"tooltip": "Fourth reference audio"}),
|
||||
"weight_a": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_b": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_c": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"weight_d": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0}),
|
||||
"extract_segments": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Extract most characteristic segments"
|
||||
}),
|
||||
"segment_duration": ("FLOAT", {
|
||||
"default": 2.0,
|
||||
"min": 0.5,
|
||||
"max": 10.0,
|
||||
"tooltip": "Duration of segments to extract (seconds)"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO_LATENT", "LATENT_INFO")
|
||||
RETURN_NAMES = ("combined_latent", "info")
|
||||
FUNCTION = "encode_references"
|
||||
CATEGORY = "audio/reference"
|
||||
|
||||
def encode_references(self, vae, audio_a, combination_mode,
|
||||
audio_b=None, audio_c=None, audio_d=None,
|
||||
weight_a=1.0, weight_b=1.0, weight_c=1.0, weight_d=1.0,
|
||||
extract_segments=False, segment_duration=2.0):
|
||||
|
||||
device = next(vae.parameters()).device
|
||||
|
||||
# Collect all available audio inputs
|
||||
audios = [audio_a]
|
||||
weights = [weight_a]
|
||||
|
||||
if audio_b is not None:
|
||||
audios.append(audio_b)
|
||||
weights.append(weight_b)
|
||||
if audio_c is not None:
|
||||
audios.append(audio_c)
|
||||
weights.append(weight_c)
|
||||
if audio_d is not None:
|
||||
audios.append(audio_d)
|
||||
weights.append(weight_d)
|
||||
|
||||
# Normalize weights
|
||||
total_weight = sum(weights)
|
||||
weights = [w / total_weight for w in weights]
|
||||
|
||||
# Encode all audio inputs
|
||||
encoded_latents = []
|
||||
|
||||
for audio, weight in zip(audios, weights):
|
||||
waveform, sample_rate = audio
|
||||
|
||||
# Extract characteristic segment if requested
|
||||
if extract_segments:
|
||||
segment_samples = int(segment_duration * sample_rate)
|
||||
|
||||
# Find segment with highest energy
|
||||
if waveform.shape[-1] > segment_samples:
|
||||
# Compute energy in sliding windows
|
||||
energy = []
|
||||
for i in range(0, waveform.shape[-1] - segment_samples, segment_samples // 4):
|
||||
segment = waveform[..., i:i+segment_samples]
|
||||
energy.append(torch.sum(segment ** 2).item())
|
||||
|
||||
# Get the highest energy segment
|
||||
best_idx = np.argmax(energy) * (segment_samples // 4)
|
||||
waveform = waveform[..., best_idx:best_idx+segment_samples]
|
||||
|
||||
# Normalize audio
|
||||
waveform = waveform / torch.max(torch.abs(waveform))
|
||||
|
||||
# Ensure correct shape
|
||||
if waveform.dim() == 2:
|
||||
waveform = waveform.unsqueeze(0)
|
||||
|
||||
# Encode with VAE
|
||||
with torch.no_grad():
|
||||
waveform = waveform.to(device)
|
||||
latent = vae.encode(waveform)
|
||||
encoded_latents.append((latent, weight))
|
||||
|
||||
# Combine latents based on mode
|
||||
if combination_mode == "average":
|
||||
# Simple weighted average
|
||||
combined = sum(latent * weight for latent, weight in encoded_latents)
|
||||
|
||||
elif combination_mode == "weighted":
|
||||
# Weighted combination with normalization
|
||||
combined = torch.zeros_like(encoded_latents[0][0])
|
||||
for latent, weight in encoded_latents:
|
||||
combined += latent * weight
|
||||
combined = F.normalize(combined, dim=1)
|
||||
|
||||
elif combination_mode == "pca":
|
||||
# PCA-based combination
|
||||
# Stack all latents
|
||||
all_latents = torch.stack([lat for lat, _ in encoded_latents])
|
||||
|
||||
# Flatten spatial dimensions
|
||||
b, c, *spatial = all_latents.shape
|
||||
all_latents_flat = all_latents.reshape(len(encoded_latents), -1)
|
||||
|
||||
# Compute PCA
|
||||
mean = all_latents_flat.mean(0, keepdim=True)
|
||||
centered = all_latents_flat - mean
|
||||
|
||||
# Compute covariance
|
||||
cov = torch.matmul(centered.T, centered) / (len(encoded_latents) - 1)
|
||||
|
||||
# Get first principal component
|
||||
eigvals, eigvecs = torch.linalg.eigh(cov)
|
||||
pc1 = eigvecs[:, -1] # Largest eigenvalue
|
||||
|
||||
# Project and combine
|
||||
projections = torch.matmul(centered, pc1)
|
||||
combined_flat = mean + projections.mean() * pc1
|
||||
combined = combined_flat.reshape(1, c, *spatial)
|
||||
|
||||
elif combination_mode == "attention":
|
||||
# Attention-based combination
|
||||
# Use latents as queries and keys
|
||||
all_latents = torch.stack([lat for lat, _ in encoded_latents])
|
||||
|
||||
# Compute attention scores
|
||||
queries = all_latents.mean(dim=(2, 3)) if all_latents.dim() == 4 else all_latents.mean(dim=2)
|
||||
keys = queries
|
||||
|
||||
# Scaled dot-product attention
|
||||
scores = torch.matmul(queries, keys.transpose(0, 1)) / np.sqrt(queries.shape[-1])
|
||||
attention_weights = F.softmax(scores, dim=-1)
|
||||
|
||||
# Apply attention weights
|
||||
combined = torch.zeros_like(encoded_latents[0][0])
|
||||
for i, (latent, base_weight) in enumerate(encoded_latents):
|
||||
# Combine base weight with attention weight
|
||||
final_weight = base_weight * attention_weights[i].mean()
|
||||
combined += latent * final_weight
|
||||
|
||||
# Create info dictionary
|
||||
info = {
|
||||
"num_references": len(audios),
|
||||
"combination_mode": combination_mode,
|
||||
"weights": weights,
|
||||
"latent_shape": combined.shape,
|
||||
"extracted_segments": extract_segments
|
||||
}
|
||||
|
||||
return (combined, info)
|
||||
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 12,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadAudio",
|
||||
"pos": [100, 100],
|
||||
"size": [300, 100],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [1, 2]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": ["content_audio.wav"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadAudio",
|
||||
"pos": [100, 250],
|
||||
"size": [300, 100],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [3]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": ["style_audio.wav"]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LoadVAE",
|
||||
"pos": [100, 400],
|
||||
"size": [300, 100],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [4, 5, 6]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadVAE"
|
||||
},
|
||||
"widgets_values": ["stable_audio_vae.safetensors"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "AudioVAEEncode",
|
||||
"pos": [450, 100],
|
||||
"size": [300, 150],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"links": [7]
|
||||
},
|
||||
{
|
||||
"name": "info",
|
||||
"type": "LATENT_INFO",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AudioVAEEncode"
|
||||
},
|
||||
"widgets_values": [true, 44100]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "AudioVAEEncode",
|
||||
"pos": [450, 300],
|
||||
"size": [300, 150],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"links": [8]
|
||||
},
|
||||
{
|
||||
"name": "info",
|
||||
"type": "LATENT_INFO",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AudioVAEEncode"
|
||||
},
|
||||
"widgets_values": [true, 44100]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "AudioStyleTransfer",
|
||||
"pos": [800, 200],
|
||||
"size": [350, 200],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "content_latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "style_latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"links": [9]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AudioStyleTransfer"
|
||||
},
|
||||
"widgets_values": ["adaptive", 0.7, 0.3, 4]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "AudioVAEDecode",
|
||||
"pos": [1200, 200],
|
||||
"size": [300, 150],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "AUDIO_LATENT",
|
||||
"link": 9
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"links": [10]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AudioVAEDecode"
|
||||
},
|
||||
"widgets_values": [true]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "SaveAudio",
|
||||
"pos": [1550, 200],
|
||||
"size": [300, 100],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveAudio"
|
||||
},
|
||||
"widgets_values": ["styled_output.wav"]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 1, 0, 4, 0, "AUDIO"],
|
||||
[2, 1, 0, 9, 0, "AUDIO"],
|
||||
[3, 2, 0, 5, 0, "AUDIO"],
|
||||
[4, 3, 0, 4, 1, "VAE"],
|
||||
[5, 3, 0, 5, 1, "VAE"],
|
||||
[6, 3, 0, 7, 1, "VAE"],
|
||||
[7, 4, 0, 6, 0, "AUDIO_LATENT"],
|
||||
[8, 5, 0, 6, 1, "AUDIO_LATENT"],
|
||||
[9, 6, 0, 7, 0, "AUDIO_LATENT"],
|
||||
[10, 7, 0, 8, 0, "AUDIO"]
|
||||
],
|
||||
"config": {},
|
||||
"groups": [],
|
||||
"version": 1,
|
||||
"workflow": {
|
||||
"name": "Audio Style Transfer",
|
||||
"description": "Transfer audio style characteristics from one audio to another using latent space manipulation"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import random
|
||||
import numpy as np
|
||||
from typing import Dict, List, Tuple, Any
|
||||
|
||||
# Import audio nodes
|
||||
from .audio_nodes import NODE_CLASS_MAPPINGS as AUDIO_NODE_CLASS_MAPPINGS
|
||||
from .audio_nodes import NODE_DISPLAY_NAME_MAPPINGS as AUDIO_NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
class NetworkBending:
|
||||
"""
|
||||
Network Bending Node - Performs various modifications on neural network models
|
||||
@@ -1363,6 +1367,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"VAEChannelManipulator": VAEChannelManipulator,
|
||||
}
|
||||
|
||||
# Add audio nodes to the mappings
|
||||
NODE_CLASS_MAPPINGS.update(AUDIO_NODE_CLASS_MAPPINGS)
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"NetworkBending": "Network Bending",
|
||||
"NetworkBendingAdvanced": "Network Bending (Advanced)",
|
||||
@@ -1374,6 +1381,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VAEChannelManipulator": "VAE Channel Manipulator",
|
||||
}
|
||||
|
||||
# Add audio node display names
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(AUDIO_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
# Export web directory for UI components
|
||||
WEB_DIRECTORY = "./js"
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
Reference in New Issue
Block a user