feat: add 4 new voice effects (echo, robot, chorus, tremolo)

- Removed MAX_ACTIVE_EFFECTS limit (effects unlimited)
- Added echo effect (0-100%): spatial delay/reverb
- Added robot effect (0-100%): ring modulation voice
- Added chorus effect (0-100%): multiple voices effect
- Added tremolo depth (0.0-1.0) and rate (0.0-10.0 Hz): amplitude modulation
- Effects apply in order: pitch → speed → echo → chorus → tremolo → robot
- Updated /effects command with all 7 effect choices
- Updated /effects list to display all 7 effects with emojis
- Updated warning system: warns when > 2 active effects
- Added validation and formatting for all new effects
- Updated voice_manager.py to handle all 7 effect storage/loading

Note: Cancel button for processing >10s not yet implemented
Note: Queue system needs updating to handle all effect parameters
This commit is contained in:
2026-01-31 17:10:19 -06:00
parent 8d4ac59f73
commit 795d5087e9
3 changed files with 306 additions and 49 deletions

View File

@@ -201,9 +201,20 @@ class VoiceManager:
# Convert to proper types (JSON stores them as strings)
pitch = effects.get("pitch", AudioEffects.PITCH_DEFAULT)
speed = effects.get("speed", AudioEffects.SPEED_DEFAULT)
echo = effects.get("echo", AudioEffects.ECHO_DEFAULT)
robot = effects.get("robot", AudioEffects.ROBOT_DEFAULT)
chorus = effects.get("chorus", AudioEffects.CHORUS_DEFAULT)
tremolo_depth = effects.get("tremolo_depth", AudioEffects.TREMOLO_DEPTH_DEFAULT)
tremolo_rate = effects.get("tremolo_rate", AudioEffects.TREMOLO_RATE_DEFAULT)
return {
"pitch": int(pitch) if pitch is not None else AudioEffects.PITCH_DEFAULT,
"speed": float(speed) if speed is not None else AudioEffects.SPEED_DEFAULT,
"echo": int(echo) if echo is not None else AudioEffects.ECHO_DEFAULT,
"robot": int(robot) if robot is not None else AudioEffects.ROBOT_DEFAULT,
"chorus": int(chorus) if chorus is not None else AudioEffects.CHORUS_DEFAULT,
"tremolo_depth": float(tremolo_depth) if tremolo_depth is not None else AudioEffects.TREMOLO_DEPTH_DEFAULT,
"tremolo_rate": float(tremolo_rate) if tremolo_rate is not None else AudioEffects.TREMOLO_RATE_DEFAULT,
}
def set_user_effect(self, user_id: int, effect_name: str, value: Any) -> tuple[bool, str]:
@@ -222,24 +233,37 @@ class VoiceManager:
if user_id not in self._user_effects:
self._user_effects[user_id] = {}
# Check if this would exceed max effects
# Save the effect
current_effects = self._user_effects[user_id].copy()
if effect_name == "pitch":
current_effects["pitch"] = int(value)
elif effect_name == "speed":
current_effects["speed"] = float(value)
elif effect_name == "echo":
current_effects["echo"] = int(value)
elif effect_name == "robot":
current_effects["robot"] = int(value)
elif effect_name == "chorus":
current_effects["chorus"] = int(value)
elif effect_name == "tremolo_depth":
current_effects["tremolo_depth"] = float(value)
elif effect_name == "tremolo_rate":
current_effects["tremolo_rate"] = float(value)
# Count active effects and show warning if > 2
active_count = AudioEffects.count_active_effects(
current_effects.get("pitch", AudioEffects.PITCH_DEFAULT),
current_effects.get("speed", AudioEffects.SPEED_DEFAULT),
pitch=current_effects.get("pitch", AudioEffects.PITCH_DEFAULT),
speed=current_effects.get("speed", AudioEffects.SPEED_DEFAULT),
echo=current_effects.get("echo", AudioEffects.ECHO_DEFAULT),
robot=current_effects.get("robot", AudioEffects.ROBOT_DEFAULT),
chorus=current_effects.get("chorus", AudioEffects.CHORUS_DEFAULT),
tremolo_depth=current_effects.get("tremolo_depth", AudioEffects.TREMOLO_DEPTH_DEFAULT),
)
# Save the effect
self._user_effects[user_id][effect_name] = value
self._save_preferences()
if active_count >= AudioEffects.MAX_ACTIVE_EFFECTS:
return True, f"Effect applied! ⚠️ You now have {active_count} active effects (max {AudioEffects.MAX_ACTIVE_EFFECTS}). More effects = slower processing."
if active_count > 2:
return True, f"Effect applied! ⚠️ You have {active_count} active effects. Performance may be slower with more effects."
else:
return True, "Effect applied successfully!"
@@ -252,7 +276,14 @@ class VoiceManager:
def count_active_effects(self, user_id: int) -> int:
"""Count how many effects are active for a user."""
effects = self.get_user_effects(user_id)
return AudioEffects.count_active_effects(effects["pitch"], effects["speed"])
return AudioEffects.count_active_effects(
pitch=effects["pitch"],
speed=effects["speed"],
echo=effects["echo"],
robot=effects["robot"],
chorus=effects["chorus"],
tremolo_depth=effects["tremolo_depth"],
)
def _load_preferences(self) -> None:
"""Load user voice preferences from JSON file."""