"""
ProgramCommonParameter
======================
Defines the ProgramCommonParameter class for managing common program-level
parameters in the JD-Xi synthesizer.
This class provides attributes and methods for handling program-wide settings,
such as program name, level, tempo, and vocal effects. It also includes
methods for retrieving display values, validating parameter values, and
handling partial-specific addressing.
Example usage:
# Create an instance for Program Level
program_level = ProgramCommonParameter(*ProgramCommonParameter.PROGRAM_LEVEL)
# Validate a value within range
validated_value = program_level.validate_value(100)
# Get the display name of a parameter
display_name = program_level.display_name # "Program Level"
# Get display value range
display_range = program_level.get_display_value() # (0, 127)
# Retrieve a parameter by name
param = ProgramCommonParameter.get_by_name("PROGRAM_TEMPO")
if param:
print(param.name, param.min_val, param.max_val)
# Get switch text representation
switch_text = program_level.get_switch_text(1) # "ON" or "---"
"""
from typing import Optional, Tuple
from picomidi.sysex.parameter.address import AddressParameter
[docs]
class SystemCommonParam(AddressParameter):
"""Program Common parameters"""
def __init__(
self,
address: int,
min_val: Optional[int] = None,
max_val: Optional[int] = None,
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]
MASTER_TUNE = (0x00, 24, 2024, -100, 100, "Master Tune") # Program Level (0-127)
[docs]
MASTER_KEY_SHIFT = (
0x04,
40,
88,
-24,
24,
"Volume of the program",
) # Program Level (0-127)
[docs]
MASTER_LEVEL = (
0x05,
0,
127,
0,
127,
"Volume of the program",
) # Program Level (0-127)
[docs]
def get_display_value(self) -> Tuple[int, int]:
"""Get the display value range (min, max) for the parameter"""
if hasattr(self, "display_min") and hasattr(self, "display_max"):
return self.display_min, self.display_max
return self.min_val, self.max_val
@property
[docs]
def display_name(self) -> str:
"""Get display name for the parameter"""
return {
self.MASTER_LEVEL: "Master Level",
}.get(self, self.name.replace("_", " ").title())
[docs]
def get_address_for_partial(self, partial_number: int = 0) -> Tuple[int, int]:
"""
Get parameter area and address adjusted for partial number.
:param partial_number: int The partial number
:return: Tuple[int, int] The address
"""
group_map = {0: 0x00}
group = group_map.get(
partial_number, 0x00
) # Default to 0x20 if partial_name is not 1, 2, or 3
return group, self.address
@property
[docs]
def is_switch(self) -> bool:
"""Returns True if parameter is address binary/enum switch"""
return self in []
[docs]
def get_switch_text(self, value: int) -> str:
"""Get display text for switch values
:param value: int The value
:return: str The display text
"""
if self.is_switch:
return "ON" if value else "OFF"
return str(value)
[docs]
def validate_value(self, value: int) -> int:
"""Validate and convert parameter value
:param value: int The value
:return: int The validated value
"""
if not isinstance(value, int):
raise ValueError(f"Value must be integer, got {type(value)}")
# Regular range check
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
[docs]
def get_partial_number(self) -> Optional[int]:
"""Returns the partial number (1-3) if this is address partial parameter, None otherwise"""
partial_params = {}
"""
{
self.PARTIAL1_SWITCH: 1,
self.PARTIAL1_SELECT: 1,
self.PARTIAL2_SWITCH: 2,
self.PARTIAL2_SELECT: 2,
self.PARTIAL3_SWITCH: 3,
self.PARTIAL3_SELECT: 3,
}
"""
return partial_params.get(self)
@staticmethod
[docs]
def get_by_name(param_name: str) -> Optional[object]:
"""Get the Parameter by name.
:param param_name: str The parameter name
:return: Optional[object] The parameter
Return the parameter member by name, or None if not found
"""
return ProgramCommonParam.__members__.get(param_name, None)
[docs]
class ProgramCommonParam(AddressParameter):
"""Program Common parameters"""
def __init__(
self,
address: int,
min_val: Optional[int] = None,
max_val: Optional[int] = None,
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
# Program name parameters (12 ASCII characters)
[docs]
TONE_NAME_1 = (0x00, 32, 127) # ASCII character 1
[docs]
TONE_NAME_2 = (0x01, 32, 127) # ASCII character 2
[docs]
TONE_NAME_3 = (0x02, 32, 127) # ASCII character 3
[docs]
TONE_NAME_4 = (0x03, 32, 127) # ASCII character 4
[docs]
TONE_NAME_5 = (0x04, 32, 127) # ASCII character 5
[docs]
TONE_NAME_6 = (0x05, 32, 127) # ASCII character 6
[docs]
TONE_NAME_7 = (0x06, 32, 127) # ASCII character 7
[docs]
TONE_NAME_8 = (0x07, 32, 127) # ASCII character 8
[docs]
TONE_NAME_9 = (0x08, 32, 127) # ASCII character 9
[docs]
TONE_NAME_10 = (0x09, 32, 127) # ASCII character 10
[docs]
TONE_NAME_11 = (0x0A, 32, 127) # ASCII character 11
[docs]
TONE_NAME_12 = (0x0B, 32, 127) # ASCII character 12
[docs]
PROGRAM_LEVEL = (
0x10,
0,
127,
0,
127,
"Volume of the program",
) # Program Level (0-127)
[docs]
PROGRAM_TEMPO = (
0x11,
500,
30000,
500,
30000,
"""Tempo of the program
The Tempo knob adjusts the setting in a range from 60 to 240.
If the SYSTEM parameter Sync Mode is set to SLAVE, only “MIDI” can be selected.
(Since the tempo is synchronized to an external device, it’s not possible to change the tempo
from the JD-Xi.)""",
) # Program Tempo (500-30000: 5.00-300.00 BPM)
[docs]
VOCAL_EFFECT = (
0x16,
0,
2,
0,
2,
) # Vocal Effect (0: OFF, 1: VOCODER, 2: AUTO-PITCH)
[docs]
VOCAL_EFFECT_NUMBER = (0x1C, 0, 20, 0, 20) # Vocal Effect Number (0-20: 1-21)
[docs]
VOCAL_EFFECT_PART = (0x1D, 0, 1, 0, 1) # Vocal Effect Part (0: Part 1, 1: Part 2)
[docs]
AUTO_NOTE_SWITCH = (0x1E, 0, 1, 0, 1) # Auto Note Switch (0: OFF, 1: ON)
[docs]
def get_display_value(self) -> Tuple[int, int]:
"""Get the display value range (min, max) for the parameter"""
if hasattr(self, "display_min") and hasattr(self, "display_max"):
return self.display_min, self.display_max
return self.min_val, self.max_val
@property
[docs]
def display_name(self) -> str:
"""Get display name for the parameter"""
return {
self.AUTO_NOTE_SWITCH: "Auto Note",
}.get(self, self.name.replace("_", " ").title())
[docs]
def get_address_for_partial(self, partial_number: int = 0) -> Tuple[int, int]:
"""
Get parameter area and address adjusted for partial number.
:param partial_number: int The partial number
:return: Tuple[int, int] The address
"""
group_map = {0: 0x00}
group = group_map.get(
partial_number, 0x00
) # Default to 0x20 if partial_name is not 1, 2, or 3
return group, self.address
@property
[docs]
def is_switch(self) -> bool:
"""Returns True if parameter is address binary/enum switch"""
return self in []
[docs]
def get_switch_text(self, value: int) -> str:
"""Get display text for switch values
:param value: int The value
:return: str The display text
"""
if self == self.AUTO_NOTE_SWITCH:
return ["OFF", "---", "ON"][value]
elif self.is_switch:
return "ON" if value else "OFF"
return str(value)
[docs]
def validate_value(self, value: int) -> int:
"""Validate and convert parameter value
:param value: int The value
:return: int The validated value
"""
if not isinstance(value, int):
raise ValueError(f"Value must be integer, got {type(value)}")
# Special handling for ring switch
if self == self.AUTO_NOTE_SWITCH and value == 1:
# Skip over the "---" value
value = 2
# Regular range check
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
[docs]
def get_partial_number(self) -> Optional[int]:
"""Returns the partial number (1-3) if this is address partial parameter, None otherwise"""
partial_params = {}
"""
{
self.PARTIAL1_SWITCH: 1,
self.PARTIAL1_SELECT: 1,
self.PARTIAL2_SWITCH: 2,
self.PARTIAL2_SELECT: 2,
self.PARTIAL3_SWITCH: 3,
self.PARTIAL3_SELECT: 3,
}
"""
return partial_params.get(self)
@staticmethod
[docs]
def get_by_name(param_name: str) -> Optional[object]:
"""Get the Parameter by name.
:param param_name: str The parameter name
:return: Optional[object] The parameter
Return the parameter member by name, or None if not found
"""
return ProgramCommonParam.__members__.get(param_name, None)