Source code for jdxi_editor.midi.data.parameter.vocal_fx

"""
Module: VocalFXParameter
========================

This module defines the VocalFXParameter class, which represents various vocal effects parameters
in a synthesizer. These parameters control different aspects of vocal processing, including
level, pan, delay/reverb send levels, auto pitch settings, vocoder effects, and more.

The class provides methods to:

- Initialize vocal FX parameters with a given address, range, and optional display range.
- Validate and convert parameter values to the MIDI range (0-127).
- Define a variety of vocal effects parameters with specific ranges, including:
  - Level, pan, delay/reverb send levels, and output assignment
  - Auto pitch settings such as switch, type, scale, key, note, gender, octave, and balance
  - Vocoder parameters such as switch, envelope type, level, mic sensitivity, and mix level

The class also offers conversion utilities:
- Convert between MIDI values and display values.
- Handle special bipolar cases (e.g., pan, auto pitch gender).
- Retrieve the display value range or MIDI value range for parameters.

Parameters include:
- Level, pan, delay and reverb send levels, output assignment, and auto pitch settings
- Vocoder settings for on/off, envelope, level, mic sensitivity, synth level, and mic mix
- Auto pitch gender, octave, balance, and key/note configurations

The class also includes utility functions to get a parameter's address, range, display range,
and to convert between MIDI values and display values.

Usage example:
    # Initialize a VocalFXParameter object for the LEVEL parameter
    param = VocalFXParameter(address=0x00, min_val=0, max_val=127)

    # Access display range values
    print(param.display_min)  # Output: 0
    print(param.display_max)  # Output: 127

    # Validate a MIDI value
    midi_value = param.convert_to_midi(64)
"""

from typing import Optional, Tuple

from picomidi.sysex.parameter.address import AddressParameter


[docs] class VocalFXParam(AddressParameter): """Vocal FX parameters""" def __init__( self, address: int, min_val: int, max_val: int, display_min: Optional[int] = None, display_max: Optional[int] = None, tooltip: Optional[str] = None, ): super().__init__(address, min_val, max_val)
[docs] self.display_min = display_min if display_min is not None else min_val
[docs] self.display_max = display_max if display_max is not None else max_val
[docs] self.tooltip = tooltip
[docs] LEVEL = (0x00, 0, 127, 0, 127, "Sets the level of the vocal FX.") # Level (0-127)
[docs] PAN = ( 0x01, -64, 63, -64, 63, "Sets the pan of the vocal FX.", ) # Pan (-64 to +63, centered at 64)
[docs] DELAY_SEND_LEVEL = ( 0x02, 0, 127, 0, 127, "Sets the level of the delay send.", ) # Delay send level (0-127)
[docs] REVERB_SEND_LEVEL = ( 0x03, 0, 127, 0, 127, "Sets the level of the reverb send.", ) # Reverb send level (0-127)
[docs] OUTPUT_ASSIGN = ( 0x04, 0, 4, 0, 4, "Sets the output assignment.", ) # Output assignment (0-4)
[docs] AUTO_PITCH_SWITCH = ( 0x05, 0, 1, 0, 1, "Sets the auto note on/off.", ) # Auto Note on/off (0-1)
[docs] AUTO_PITCH_TYPE = ( 0x06, 0, 3, 0, 3, "Sets the auto pitch preset_type.", ) # Auto Pitch preset_type (0-3)
[docs] AUTO_PITCH_SCALE = ( 0x07, 0, 1, 0, 1, "Sets the auto pitch scale.", ) # Scale CHROMATIC, Maj(Min)
[docs] AUTO_PITCH_KEY = ( 0x08, 0, 23, 0, 23, "Sets the auto pitch key.", ) # Auto Pitch key (0-23) C, Db, D, Eb, E, F, F#, G, Ab, A, Bb, B, Cm, C#m, Dm, D#m, Em, Fm, F#m, Gm, G#m, Am, Bbm, Bm
[docs] AUTO_PITCH_NOTE = ( 0x09, 0, 11, 0, 11, "Sets the auto pitch note.", ) # Auto Pitch note (0-11)
[docs] AUTO_PITCH_GENDER = ( 0x0A, -10, 10, -10, 10, "Sets the auto pitch gender.", ) # Gender (-10 to +10, centered at 0)
[docs] AUTO_PITCH_OCTAVE = ( 0x0B, -1, 1, -1, 1, "Sets the auto pitch octave.", ) # Octave (-1 to +1: 0-2)
[docs] AUTO_PITCH_BALANCE = ( 0x0C, 0, 100, 0, 100, "Sets the auto pitch balance.", ) # Dry/Wet Balance (0-100)
[docs] VOCODER_SWITCH = ( 0x0D, 0, 1, 0, 1, "Sets the vocoder on/off.", ) # Vocoder on/off (0-1)
[docs] VOCODER_ENVELOPE = ( 0x0E, 0, 2, 0, 2, "Sets the vocoder envelope preset_type.", ) # Vocoder envelope preset_type (0-2)
[docs] VOCODER_LEVEL = ( 0x0F, 0, 127, 0, 127, "Sets the vocoder level.", ) # Vocoder level (0-127)
[docs] VOCODER_MIC_SENS = ( 0x10, 0, 127, 0, 127, "Sets the vocoder mic sensitivity.", ) # Vocoder mic sensitivity (0-127)
[docs] VOCODER_SYNTH_LEVEL = ( 0x11, 0, 127, 0, 127, "Sets the vocoder synth level.", ) # Vocoder synth level (0-127)
[docs] VOCODER_MIC_MIX = ( 0x12, 0, 127, 0, 127, "Sets the vocoder mic mix level.", ) # Vocoder mic mix level (0-127)
[docs] VOCODER_MIC_HPF = ( 0x13, 0, 13, 0, 13, "Sets the vocoder mic HPF freq.", ) # Vocoder mic HPF freq (0-13)
[docs] def validate_value(self, value: int) -> int: """Validate and convert parameter value to MIDI range (0-127)""" if not isinstance(value, int): raise ValueError(f"Value must be integer, got {type(value)}") if value < self.min_val or value > self.max_val: raise ValueError( f"Value {value} out of range for {self.name} " f"(valid range: {self.min_val}-{self.max_val})" ) return value
@staticmethod
[docs] def get_name_by_address(address: int) -> Optional[str]: """Return the parameter name for address given address. :param address: int The address :return: Optional[str] The parameter name """ for param in VocalFXParam: if param.address == address: return param.name return None # Return None if the address is not found
@property
[docs] def display_name(self) -> str: """Get display name for the parameter""" return self.name.replace("_", " ").title()
@property
[docs] def is_switch(self) -> bool: """Returns True if parameter is address binary/enum switch""" return self in [ self.AUTO_PITCH_SWITCH, self.VOCODER_SWITCH, ]
@staticmethod
[docs] def get_address(param_name: str) -> Optional[int]: """ Get the address of address parameter by name. :param param_name: str The parameter name :return: Optional[int] The address """ param = VocalFXParam.get_by_name(param_name) if param: return param.STATUS[0] return None
@staticmethod
[docs] def get_range(param_name: str) -> Tuple[int, int]: """ Get the value range (min, max) of address parameter by name. :param param_name: str The parameter name :return: Tuple[int, int] The value range """ param = VocalFXParam.get_by_name(param_name) if param: return param.STATUS[1], param.STATUS[2] return None, None
@staticmethod
[docs] def get_display_range(param_name: str) -> Tuple[int, int]: """ Get the display value range (min, max) of address parameter by name. :param param_name: str The parameter name :return: Tuple[int, int] The display value range """ param = VocalFXParam.get_by_name(param_name) if param: return param.display_min, param.display_max return None, None
[docs] def get_display_value(self) -> Tuple[int, int]: """ Get the display value range (min, max) for the parameter :return: Tuple[int, int] The display value range """ if hasattr(self, "display_min") and hasattr(self, "display_max"): return self.display_min, self.display_max return self.min_val, self.max_val
[docs] def convert_from_display(self, display_value: int) -> int: """ Convert from display value to MIDI value (0-127) :param display_value: int The display value :return: int The MIDI value """ # Handle bipolar parameters if self in [ self.AUTO_PITCH_GENDER, self.PAN, self.AUTO_PITCH_BALANCE, ]: return display_value + 64 # -63 to +63 -> 0 to 127 else: return display_value # -63 to +63 -> 0 to 126
@staticmethod
[docs] def convert_to_display( value: int, min_val: int, max_val: int, display_min: int, display_max: int ) -> int: """ Convert address value to address display value within address range. :param value: int The address value :param min_val: int The address minimum value :param max_val: int The address maximum value :param display_min: int The display minimum value :param display_max: int The display maximum value :return: int The display value """ return int( (value - min_val) * (display_max - display_min) / (max_val - min_val) + display_min )
[docs] def convert_to_midi(self, display_value: int) -> int: """ Convert from display value to MIDI value :param display_value: int The display value :return: int The MIDI value """ # Handle special bipolar cases first if self == VocalFXParam.PAN: return display_value + 64 # -63 to +63 -> 0 to 127 elif self == VocalFXParam.AUTO_PITCH_GENDER: return display_value + 10 # -63 to +63 -> 0 to 127 # For parameters with simple linear scaling if hasattr(self, "display_min") and hasattr(self, "display_max"): return int( self.min_val + (display_value - self.display_min) * (self.max_val - self.min_val) / (self.display_max - self.display_min) ) return display_value
[docs] def convert_from_midi(self, midi_value: int) -> int: """ Convert from MIDI value to display value :param midi_value: int The MIDI value :return: int The display value """ # Handle special bipolar cases first if self == VocalFXParam.PAN: return midi_value - 64 # 0 to 127 -> -63 to +63 elif self == VocalFXParam.AUTO_PITCH_GENDER: return midi_value - 10 # 0 to 127 -> -63 to +63 # For parameters with simple linear scaling if hasattr(self, "display_min") and hasattr(self, "display_max"): return int( self.display_min + (midi_value - self.min_val) * (self.display_max - self.display_min) / (self.max_val - self.min_val) ) return midi_value
@staticmethod
[docs] def get_display_value_by_name(param_name: str, value: int) -> int: """ Get the display value for address parameter by name and value. :param param_name: str The parameter name :param value: int The value :return: int The display value """ param = VocalFXParam.get_by_name(param_name) if param: return param.convert_from_midi(value) return value
@staticmethod
[docs] def get_midi_range(param_name: str) -> Tuple[int, int]: """ Get the MIDI value range (min, max) of address parameter by name. :param param_name: str The parameter name :return: Tuple[int, int] The MIDI value range """ param = VocalFXParam.get_by_name(param_name) if param: return param.min_value, param.max_value
@staticmethod
[docs] def get_midi_value(param_name: str, value: int) -> Optional[int]: """ Get the MIDI value for address parameter by name and value. :param param_name: str The parameter name :param value: int The value :return: Optional[int] The MIDI value """ param = VocalFXParam.get_by_name(param_name) if param: return param.convert_to_midi(value) return None