Files
AbletonMCP_AI/AbletonMCP_AI/mcp_server/engines/advanced_automation.py
2026-04-12 22:14:35 -03:00

690 lines
24 KiB
Python

"""
Advanced Automation Engine for AbletonMCP_AI
Creates professional automation curves including:
- Filter sweeps (build-ups, breakdowns)
- Sidechain compression curves
- Send automation (reverb/delay)
- Volume ramps and fades
- Complete build-up automation packages
Author: Agent 6 - Advanced Automation Specialist
"""
import math
from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass
from enum import Enum
class CurveType(Enum):
"""Types of automation curves."""
LINEAR = "linear"
EXPONENTIAL = "exponential"
LOGARITHMIC = "logarithmic"
S_CURVE = "s_curve"
BEZIER = "bezier"
STEP = "step"
@dataclass
class AutomationPoint:
"""Represents a single automation point."""
time: float # In beats (can be fractional for precise positioning)
value: float
curve_type: str = "linear" # "linear", "bezier", "s_curve"
class AdvancedAutomation:
"""
Advanced automation engine for creating professional curves.
Handles:
- Filter sweeps (exponential/logarithmic)
- Sidechain ducking curves
- Send automation (reverb/delay)
- Volume ramps
- Build-up automation packages
"""
def __init__(self, live_bridge):
"""
Initialize with LiveBridge for direct Ableton API access.
Args:
live_bridge: LiveBridge instance for executing automation commands
"""
self.live_bridge = live_bridge
self._automation_history = []
def _generate_curve_points(
self,
start_val: float,
end_val: float,
start_time: float,
end_time: float,
num_points: int = 16,
curve_type: str = "linear"
) -> List[AutomationPoint]:
"""
Generate automation points following a curve.
Args:
start_val: Starting value
end_val: Ending value
start_time: Starting time in beats
end_time: Ending time in beats
num_points: Number of control points to generate
curve_type: Type of curve (linear, exponential, logarithmic, s_curve)
Returns:
List of AutomationPoint objects
"""
points = []
duration = end_time - start_time
for i in range(num_points + 1):
t = i / num_points # Normalized time 0-1
# Apply curve function
if curve_type == "linear":
factor = t
elif curve_type == "exponential":
# Exponential curve: starts slow, ends fast
factor = t ** 2 if start_val < end_val else 1 - (1 - t) ** 2
if start_val > end_val:
factor = 1 - (1 - t) ** 2
else:
factor = t ** 2
elif curve_type == "logarithmic":
# Logarithmic curve: starts fast, ends slow
factor = math.sqrt(t) if start_val < end_val else 1 - math.sqrt(1 - t)
if start_val > end_val:
factor = 1 - math.sqrt(1 - t)
else:
factor = math.sqrt(t)
elif curve_type == "s_curve":
# S-curve: smooth ease-in-out
factor = t ** 2 * (3 - 2 * t)
elif curve_type == "bezier":
# Cubic bezier approximation
factor = t ** 3 * (10 - 15 * t + 6 * t ** 2)
else:
factor = t
# Calculate value
if curve_type == "exponential" and start_val < end_val:
# For exponential upward sweeps, use proper log scale
if start_val > 0 and end_val > 0:
log_start = math.log10(start_val)
log_end = math.log10(end_val)
value = 10 ** (log_start + factor * (log_end - log_start))
else:
value = start_val + factor * (end_val - start_val)
else:
value = start_val + factor * (end_val - start_val)
time = start_time + t * duration
points.append(AutomationPoint(time=time, value=value, curve_type=curve_type))
return points
def create_filter_sweep(
self,
track_index: int,
start_bar: float,
end_bar: float,
start_freq: float = 200,
end_freq: float = 20000,
curve_type: str = "exponential",
filter_device: str = "Auto Filter",
parameter_name: str = "Frequency"
) -> Dict:
"""
Create a filter sweep automation curve.
Args:
track_index: Target track index
start_bar: Starting bar position
end_bar: Ending bar position
start_freq: Starting frequency in Hz (default 200)
end_freq: Ending frequency in Hz (default 20000)
curve_type: Curve type - "linear", "exponential", "logarithmic", "s_curve"
filter_device: Name of filter device (default "Auto Filter")
parameter_name: Parameter to automate (default "Frequency")
Returns:
Dict with automation details and status
"""
try:
# Convert bars to beats
start_beats = start_bar * 4
end_beats = end_bar * 4
# Generate exponential curve points for natural filter sweep
points = self._generate_curve_points(
start_val=start_freq,
end_val=end_freq,
start_time=start_beats,
end_time=end_beats,
num_points=24, # More points for smooth sweep
curve_type=curve_type
)
# Convert to format expected by LiveBridge
automation_points = [[p.time, p.value] for p in points]
# Apply automation via LiveBridge
result = self.live_bridge.add_automation(
track_index=track_index,
parameter_name=parameter_name,
points=automation_points,
device_name=filter_device
)
sweep_data = {
"track_index": track_index,
"device": filter_device,
"parameter": parameter_name,
"start_bar": start_bar,
"end_bar": end_bar,
"start_freq": start_freq,
"end_freq": end_freq,
"curve_type": curve_type,
"point_count": len(points),
"status": "success" if result else "failed"
}
self._automation_history.append(sweep_data)
return sweep_data
except Exception as e:
return {
"track_index": track_index,
"status": "error",
"error": str(e)
}
def create_sidechain_curve(
self,
track_index: int,
ducking_amount: float = 0.7,
attack_ms: float = 5,
release_ms: float = 100,
every_n_bars: float = 1.0,
total_bars: float = 16.0
) -> Dict:
"""
Create sidechain compression ducking curves synced to the beat.
Args:
track_index: Target track index (bass/synths)
ducking_amount: Amount of ducking 0.0-1.0 (0.7 = -12dB reduction)
attack_ms: Attack time in milliseconds (default 5)
release_ms: Release time in milliseconds (default 100)
every_n_bars: Duck every N bars (default 1.0 = every bar)
total_bars: Total duration in bars (default 16)
Returns:
Dict with automation details and status
"""
try:
# Calculate BPM-dependent times
bpm = 95 # Assume default, could be fetched from Live
ms_per_beat = 60000 / bpm
# Convert ms to beats
attack_beats = attack_ms / ms_per_beat
release_beats = release_ms / ms_per_beat
# Ducking value (1.0 = no ducking, lower = more reduction)
duck_val = 1.0 - ducking_amount
points = []
bar = 0.0
while bar < total_bars:
bar_start = bar * 4 # Convert to beats
# Start of duck (on the beat)
points.append([bar_start, 1.0])
# Attack phase (quick drop)
points.append([bar_start + attack_beats, duck_val])
# Sustain (hold duck)
sustain_end = bar_start + (2 * 4 * every_n_bars) / 3 # 2/3 through
points.append([sustain_end, duck_val])
# Release phase (recover)
bar_end = (bar + every_n_bars) * 4
points.append([bar_end, 1.0])
bar += every_n_bars
# Apply volume automation via LiveBridge
result = self.live_bridge.add_automation(
track_index=track_index,
parameter_name="volume",
points=points
)
sidechain_data = {
"track_index": track_index,
"ducking_amount": ducking_amount,
"attack_ms": attack_ms,
"release_ms": release_ms,
"every_n_bars": every_n_bars,
"total_bars": total_bars,
"point_count": len(points),
"status": "success" if result else "failed"
}
self._automation_history.append(sidechain_data)
return sidechain_data
except Exception as e:
return {
"track_index": track_index,
"status": "error",
"error": str(e)
}
def create_send_automation(
self,
track_index: int,
send_index: int,
points: List[Tuple[float, float]],
curve_type: str = "linear"
) -> Dict:
"""
Create send level automation (reverb/delay sends).
Args:
track_index: Target track index
send_index: Index of the return track (0 for A, 1 for B, etc.)
points: List of (bar_position, send_amount) tuples
Send amount should be 0.0-1.0
curve_type: Interpolation curve type
Returns:
Dict with automation details and status
"""
try:
# Convert bar positions to beats
automation_points = []
for bar_pos, amount in points:
beats = bar_pos * 4
automation_points.append([beats, amount])
# Apply send automation via LiveBridge
result = self.live_bridge.add_automation(
track_index=track_index,
parameter_name="send",
points=automation_points,
send_index=send_index
)
send_data = {
"track_index": track_index,
"send_index": send_index,
"send_letter": chr(ord('A') + send_index),
"point_count": len(points),
"curve_type": curve_type,
"status": "success" if result else "failed"
}
self._automation_history.append(send_data)
return send_data
except Exception as e:
return {
"track_index": track_index,
"send_index": send_index,
"status": "error",
"error": str(e)
}
def create_volume_ramp(
self,
track_index: int,
start_bar: float,
end_bar: float,
start_vol: float,
end_vol: float,
curve_type: str = "linear"
) -> Dict:
"""
Create a volume ramp/fade automation.
Args:
track_index: Target track index
start_bar: Starting bar position
end_bar: Ending bar position
start_vol: Starting volume (0.0-1.0, where 1.0 = 0dB)
end_vol: Ending volume (0.0-1.0)
curve_type: Curve type for the ramp
Returns:
Dict with automation details and status
"""
try:
start_beats = start_bar * 4
end_beats = end_bar * 4
# Generate curve points
points = self._generate_curve_points(
start_val=start_vol,
end_val=end_vol,
start_time=start_beats,
end_time=end_beats,
num_points=16,
curve_type=curve_type
)
automation_points = [[p.time, p.value] for p in points]
# Apply volume automation
result = self.live_bridge.add_automation(
track_index=track_index,
parameter_name="volume",
points=automation_points
)
ramp_data = {
"track_index": track_index,
"start_bar": start_bar,
"end_bar": end_bar,
"start_vol": start_vol,
"end_vol": end_vol,
"curve_type": curve_type,
"point_count": len(points),
"status": "success" if result else "failed"
}
self._automation_history.append(ramp_data)
return ramp_data
except Exception as e:
return {
"track_index": track_index,
"status": "error",
"error": str(e)
}
def add_build_up_automation(
self,
track_indices: List[int],
build_start: float,
drop_position: float,
include_filter: bool = True,
filter_end_freq: float = 15000,
include_volume: bool = True,
volume_boost: float = 0.1,
include_reverb_send: bool = True,
reverb_send_final: float = 0.6
) -> Dict:
"""
Create a complete build-up automation package.
Includes:
- Filter sweeps (opening up to drop)
- Volume ramps (slight boost leading to drop)
- Reverb send increases (wash before drop)
Args:
track_indices: List of track indices to apply automation to
build_start: Starting bar of build section
drop_position: Bar position where drop hits
include_filter: Add filter sweep (default True)
filter_end_freq: Filter frequency at drop (default 15000)
include_volume: Add volume automation (default True)
volume_boost: Volume increase during build (default 0.1)
include_reverb_send: Add reverb send automation (default True)
reverb_send_final: Reverb send amount at drop (default 0.6)
Returns:
Dict with complete build-up automation details
"""
results = {
"track_count": len(track_indices),
"build_start": build_start,
"drop_position": drop_position,
"track_automations": [],
"status": "success"
}
try:
for track_index in track_indices:
track_results = {
"track_index": track_index,
"automations": []
}
# 1. Filter sweep (opening up)
if include_filter:
filter_result = self.create_filter_sweep(
track_index=track_index,
start_bar=build_start,
end_bar=drop_position,
start_freq=400,
end_freq=filter_end_freq,
curve_type="exponential"
)
track_results["automations"].append({
"type": "filter_sweep",
"result": filter_result
})
# 2. Volume ramp (slight boost)
if include_volume:
vol_result = self.create_volume_ramp(
track_index=track_index,
start_bar=build_start,
end_bar=drop_position - 0.5, # Peak just before drop
start_vol=0.85,
end_vol=0.85 + volume_boost,
curve_type="s_curve"
)
track_results["automations"].append({
"type": "volume_ramp",
"result": vol_result
})
# Drop spike then normalize
spike_result = self.create_volume_ramp(
track_index=track_index,
start_bar=drop_position - 0.25,
end_bar=drop_position + 0.25,
start_vol=0.85 + volume_boost,
end_vol=0.85,
curve_type="linear"
)
track_results["automations"].append({
"type": "volume_spike",
"result": spike_result
})
# 3. Reverb send increase
if include_reverb_send:
# Reverb wash building up
reverb_points = [
(build_start, 0.1),
(build_start + (drop_position - build_start) * 0.5, 0.3),
(drop_position - 0.5, reverb_send_final),
(drop_position, 0.15) # Drop reverb at impact
]
reverb_result = self.create_send_automation(
track_index=track_index,
send_index=0, # Return A (Reverb)
points=reverb_points,
curve_type="exponential"
)
track_results["automations"].append({
"type": "reverb_send",
"result": reverb_result
})
results["track_automations"].append(track_results)
return results
except Exception as e:
results["status"] = "error"
results["error"] = str(e)
return results
def create_drop_impact(
self,
track_index: int,
drop_bar: float,
impact_duration: float = 0.5,
impact_boost_db: float = 3.0
) -> Dict:
"""
Create a volume spike automation for drop impact.
Args:
track_index: Target track index
drop_bar: Bar position of the drop
impact_duration: Duration of impact spike in bars (default 0.5)
impact_boost_db: Boost in dB (default 3.0)
Returns:
Dict with impact automation details
"""
try:
# Convert dB boost to linear
impact_boost_linear = 10 ** (impact_boost_db / 20)
# Create impact curve
base_vol = 0.85
peak_vol = min(base_vol * impact_boost_linear, 1.0)
impact_start = drop_bar
impact_peak = drop_bar + 0.125 # Peak at 1/8th note
impact_end = drop_bar + impact_duration
points = [
[impact_start * 4, base_vol],
[impact_peak * 4, peak_vol],
[impact_end * 4, base_vol]
]
result = self.live_bridge.add_automation(
track_index=track_index,
parameter_name="volume",
points=points
)
return {
"track_index": track_index,
"drop_bar": drop_bar,
"impact_duration": impact_duration,
"impact_boost_db": impact_boost_db,
"peak_vol": peak_vol,
"status": "success" if result else "failed"
}
except Exception as e:
return {
"track_index": track_index,
"status": "error",
"error": str(e)
}
def create_breakdown_automation(
self,
track_indices: List[int],
breakdown_start: float,
breakdown_end: float,
reverb_increase: float = 0.5
) -> Dict:
"""
Create automation for breakdown sections (reverb swells, filter).
Args:
track_indices: List of track indices
breakdown_start: Starting bar of breakdown
breakdown_end: Ending bar of breakdown
reverb_increase: Amount to increase reverb sends (default 0.5)
Returns:
Dict with breakdown automation details
"""
results = {
"section": "breakdown",
"tracks": [],
"status": "success"
}
try:
for track_index in track_indices:
track_data = {"track_index": track_index, "automations": []}
# Filter closing for breakdown
filter_close = self.create_filter_sweep(
track_index=track_index,
start_bar=breakdown_start,
end_bar=breakdown_start + 2,
start_freq=20000,
end_freq=800,
curve_type="exponential"
)
track_data["automations"].append({
"type": "filter_close",
"result": filter_close
})
# Reverb swell
mid_point = (breakdown_start + breakdown_end) / 2
reverb_points = [
(breakdown_start, 0.1),
(mid_point, 0.1 + reverb_increase),
(breakdown_end - 1, 0.1 + reverb_increase),
(breakdown_end, 0.1)
]
reverb_result = self.create_send_automation(
track_index=track_index,
send_index=0,
points=reverb_points,
curve_type="s_curve"
)
track_data["automations"].append({
"type": "reverb_swell",
"result": reverb_result
})
# Filter reopen at end
filter_open = self.create_filter_sweep(
track_index=track_index,
start_bar=breakdown_end - 2,
end_bar=breakdown_end,
start_freq=800,
end_freq=20000,
curve_type="exponential"
)
track_data["automations"].append({
"type": "filter_reopen",
"result": filter_open
})
results["tracks"].append(track_data)
return results
except Exception as e:
results["status"] = "error"
results["error"] = str(e)
return results
def get_automation_history(self) -> List[Dict]:
"""Return history of all automation operations."""
return self._automation_history.copy()
def clear_history(self):
"""Clear automation history."""
self._automation_history = []