Source code for jdxi_editor.ui.widgets.slider_spinbox.slider_spinbox

"""
Slider Spinbox Widget for Roland JD-Xi
"""

from typing import Callable

from decologr import Decologr as log
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QDoubleSpinBox, QSpinBox, QVBoxLayout, QWidget

from jdxi_editor.ui.widgets.envelope.parameter import EnvelopeParameter
from picomidi.constant import Midi
from picomidi.sysex.parameter.address import AddressParameter
from picomidi.utils.conversion import midi_value_to_ms, ms_to_midi_value


[docs] def create_spinbox(min_value: int, max_value: int, suffix: str, value: int) -> QSpinBox: """ Create a spinbox with specified range and suffix :param min_value: int :param max_value: int :param suffix: str :param value: int :return: QSpinBox """ sb = QSpinBox() sb.setRange(min_value, max_value) sb.setSuffix(suffix) sb.setValue(value) return sb
[docs] def create_double_spinbox( min_value: float, max_value: float, step: float, value: int ) -> QDoubleSpinBox: """ Create a double spinbox with specified range, step, and initial value. :param min_value: int :param max_value: int :param step: float :param value: int :return: QDoubleSpinBox """ sb = QDoubleSpinBox() sb.setRange(min_value, max_value) sb.setSingleStep(step) sb.setValue(value) return sb
[docs] class AdsrSliderSpinbox(QWidget): """ ADSR Slider and Spinbox widget for Roland JD-Xi """
[docs] valueChanged = Signal(int)
[docs] envelope_changed = Signal(dict)
def __init__( self, param: AddressParameter, min_value: float = 0.0, max_value: float = 1.0, units: str = "", label: str = "", value: int | None | float = None, create_parameter_slider: Callable = None, parent: QWidget = None, show_spinbox: bool = False ): """ Initialize the ADSR slider and spinbox widget. :param param: AddressParameter :param min_value: int :param max_value: int :param units: str :param label: str :param value: int :param create_parameter_slider: Callable :param parent: QWidget :param show_spinbox: bool """ super().__init__(parent)
[docs] self.param = param
[docs] self.factor = Midi.value.max.SEVEN_BIT
if max_value > 1: self.factor = max_value
[docs] self.create_parameter_slider = create_parameter_slider
[docs] self.slider = self.create_parameter_slider( param=param, label=label, vertical=True, )
param_type = param.get_envelope_param_type() if param_type in [ EnvelopeParameter.SUSTAIN_LEVEL, EnvelopeParameter.PEAK_LEVEL, ]: self.spinbox = create_double_spinbox( min_value=min_value, max_value=max_value, step=0.01, value=value ) else: self.spinbox = create_spinbox( min_value=int(min_value), max_value=int(max_value), suffix=units, value=value, ) self.spinbox.setRange(min_value, max_value) layout = QVBoxLayout() layout.addWidget(self.slider) if show_spinbox: layout.addWidget(self.spinbox) self.setLayout(layout) # Connect both ways self.slider.valueChanged.connect(self._slider_changed) self.spinbox.valueChanged.connect(self._spinbox_changed)
[docs] def convert_to_envelope(self, value: float) -> float: """ Convert the slider value to envelope value based on parameter type :param value: float :return: float """ if self.param is None: log.error("Parameter is None, cannot convert to envelope") return 0.0 if value is None: log.error("Value is None, cannot convert to envelope") return 0.0 param_type = self.param.get_envelope_param_type() if param_type is None: log.error( f"Parameter type for {self.param.name} is None, cannot convert to envelope" ) return 0.0 if param_type == EnvelopeParameter.SUSTAIN_LEVEL: converted_value = value / self.factor log.message( f"convert_to_envelope param type: {param_type} value {value} -> env {converted_value}" ) return converted_value if param_type == EnvelopeParameter.PEAK_LEVEL: return value / self.factor elif param_type in [ EnvelopeParameter.ATTACK_TIME, EnvelopeParameter.DECAY_TIME, EnvelopeParameter.RELEASE_TIME, ]: return midi_value_to_ms(int(value)) else: log.error(f"Unknown envelope parameter type: {param_type}") return 0.0 # or raise an error, depending on design
[docs] def convert_from_envelope(self, value: float): """convert from envelope""" param_type = self.param.get_envelope_param_type() if param_type in [EnvelopeParameter.PEAK_LEVEL]: return int(value * self.factor) if param_type in [EnvelopeParameter.SUSTAIN_LEVEL]: converted_value = int(value * self.factor) log.message( f"convert_from_envelope param type: " f"{param_type} value {value} -> Slider {converted_value}", silent=True, ) return converted_value elif param_type in [ EnvelopeParameter.ATTACK_TIME, EnvelopeParameter.DECAY_TIME, EnvelopeParameter.RELEASE_TIME, ]: return ms_to_midi_value(value) else: return 64
[docs] def _slider_changed(self, value: int) -> None: """ Handle changes from the slider and update the spinbox and envelope :param value: :return: """ self.spinbox.blockSignals(True) self.spinbox.setValue(self.convert_to_envelope(value)) self.spinbox.blockSignals(False) self.envelope_changed.emit( {self.param.get_envelope_param_type(): self.convert_to_envelope(value)} )
[docs] def _spinbox_changed(self, value: float) -> None: """ Handle changes from the spinbox and update the slider and envelope :param value: :return: None """ self.slider.blockSignals(True) self.slider.setValue(self.convert_from_envelope(value)) self.slider.blockSignals(False) self.envelope_changed.emit({self.param.get_envelope_param_type(): value})
[docs] def setValue(self, value: float): """ Set the value of the spinbox and slider :param value: int :return: None """ self.spinbox.setValue(value)
[docs] def value(self) -> float: """ Get the value of the spinbox :return: int """ return self.spinbox.value()
[docs] def update(self): """Update the envelope values and plot""" super().update() self.slider.update() self.spinbox.update() self.parent.update()