Source code for jdxi_editor.ui.editors.analog.oscillator

"""
Analog Oscillator Section
"""

from typing import Callable

from picomidi.sysex.parameter.address import AddressParameter
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QTabWidget, QVBoxLayout, QWidget

from jdxi_editor.jdxi.style import JDXiStyle, JDXiThemeManager
from jdxi_editor.midi.data.address.address import RolandSysExAddress
from jdxi_editor.midi.data.analog.oscillator import AnalogOscWave, AnalogSubOscType
from jdxi_editor.midi.data.parameter.analog import AnalogParam
from jdxi_editor.midi.io.helper import MidiIOHelper
from jdxi_editor.ui.editors.digital.partial.pwm import PWMWidget
from jdxi_editor.ui.image.utils import base64_to_pixmap
from jdxi_editor.ui.image.waveform import generate_waveform_icon
from jdxi_editor.ui.widgets.button.waveform.analog import AnalogWaveformButton
from jdxi_editor.ui.widgets.pitch.envelope import PitchEnvelopeWidget
from jdxi_editor.ui.windows.jdxi.dimensions import JDXiDimensions


[docs] class AnalogOscillatorSection(QWidget): """Analog Oscillator Section""" def __init__( self, create_parameter_slider: Callable, create_parameter_switch: Callable, waveform_selected_callback: Callable, wave_buttons: dict, midi_helper: MidiIOHelper, controls: dict[AddressParameter, QWidget], address: RolandSysExAddress, ): super().__init__() """ Initialize the AnalogOscillatorSection :param create_parameter_slider: Callable :param create_parameter_switch: Callable :param waveform_selected_callback: Callable :param wave_buttons: dict :param midi_helper: MidiIOHelper :param address: RolandSysExAddress """
[docs] self.pitch_env_widget = None
[docs] self.pwm_widget = None
[docs] self._create_parameter_slider = create_parameter_slider
[docs] self._create_parameter_switch = create_parameter_switch
[docs] self._on_waveform_selected = waveform_selected_callback
[docs] self.wave_buttons = wave_buttons
[docs] self.midi_helper = midi_helper
[docs] self.address = address
[docs] self.controls = controls
self.init_ui()
[docs] def init_ui(self) -> None: """ Initialize the UI :return: None """ layout = QVBoxLayout() layout.setContentsMargins(1, 1, 1, 1) self.setLayout(layout) JDXiThemeManager.apply_adsr_style(self, analog=True) # --- Waveform buttons --- layout.addLayout(self.create_waveform_buttons()) # --- Tab widget to add pitch and PW controls to --- self.oscillator_tab_widget = QTabWidget() JDXiThemeManager.apply_tabs_style(self.oscillator_tab_widget, analog=True) layout.addWidget(self.oscillator_tab_widget) # --- Pitch Envelope --- pitch_widget = self._create_pitch_widget() self.oscillator_tab_widget.addTab(pitch_widget, "Tuning and Pitch") layout.addStretch() # --- Pulse Width controls --- self.oscillator_tab_widget.addTab(self.create_pw_group(), "Pulse Width")
[docs] def _create_pitch_widget(self) -> QWidget: """Tuning and pitch controls""" pitch_layout = QHBoxLayout() pitch_layout.addStretch() pitch_layout.addWidget(self.create_tuning_group()) pitch_layout.addWidget(self.create_pitch_env_group()) pitch_layout.addStretch() pitch_widget = QWidget() pitch_widget.setLayout(pitch_layout) pitch_widget.setMinimumHeight(JDXiDimensions.EDITOR_MINIMUM_HEIGHT) return pitch_widget
[docs] def create_waveform_buttons(self) -> QHBoxLayout: """ Create the waveform buttons :return: QHBoxLayout """ wave_layout = QHBoxLayout() wave_layout.addStretch() for waveform in [ AnalogOscWave.SAW, AnalogOscWave.TRIANGLE, AnalogOscWave.PULSE, ]: btn = AnalogWaveformButton(waveform) JDXiThemeManager.apply_button_rect_analog(btn) # Set icons icon_name = ( "upsaw" if waveform == AnalogOscWave.SAW else "triangle" if waveform == AnalogOscWave.TRIANGLE else "pwsqu" ) icon_base64 = generate_waveform_icon(icon_name, "#FFFFFF", 0.7) btn.setIcon(QIcon(base64_to_pixmap(icon_base64))) btn.setFixedSize(60, 30) btn.waveform_selected.connect(self._on_waveform_selected) self.wave_buttons[waveform] = btn self.controls[AnalogParam.OSC_WAVEFORM] = btn wave_layout.addWidget(btn) self.sub_oscillator_type_switch = self._create_parameter_switch( AnalogParam.SUB_OSCILLATOR_TYPE, "Type", [ AnalogSubOscType.OFF.display_name, AnalogSubOscType.OCT_DOWN_1.display_name, AnalogSubOscType.OCT_DOWN_2.display_name, ], ) wave_layout.addWidget(self.sub_oscillator_type_switch) wave_layout.addStretch() return wave_layout
[docs] def create_tuning_group(self) -> QGroupBox: """ Create the tuning group :return: QGroupBox """ tuning_group = QGroupBox("Tuning") tuning_layout = QHBoxLayout() tuning_group.setLayout(tuning_layout) tuning_layout.addStretch() tuning_layout.addWidget( self._create_parameter_slider( AnalogParam.OSC_PITCH_COARSE, "Coarse (1/2 tones)", vertical=True ) ) tuning_layout.addWidget( self._create_parameter_slider( AnalogParam.OSC_PITCH_FINE, "Fine (cents)", vertical=True ) ) tuning_layout.addStretch() return tuning_group
[docs] def create_pw_group(self) -> QGroupBox: """ Create the pulse width group :return: QGroupBox """ pw_group = QGroupBox("Pulse Width") pw_layout = QVBoxLayout() pw_layout.addStretch() pw_group.setLayout(pw_layout) pw_layout.addStretch() self.pwm_widget = PWMWidget( pulse_width_param=AnalogParam.OSC_PULSE_WIDTH, mod_depth_param=AnalogParam.OSC_PULSE_WIDTH_MOD_DEPTH, midi_helper=self.midi_helper, address=self.address, create_parameter_slider=self._create_parameter_slider, controls=self.controls, ) JDXiThemeManager.apply_adsr_style(self.pwm_widget, analog=True) self.pwm_widget.setMaximumHeight(JDXiStyle.PWM_WIDGET_HEIGHT) pw_layout.addWidget(self.pwm_widget) pw_layout.addStretch() return pw_group
[docs] def create_pitch_env_group(self) -> QGroupBox: """ Create the pitch envelope group :return: QGroupBox """ pitch_env_group = QGroupBox("Pitch Envelope") pitch_env_row_layout = QHBoxLayout() pitch_env_group.setLayout(pitch_env_row_layout) pitch_env_row_layout.addStretch() # --- Pitch Env Widget --- self.pitch_env_widget = PitchEnvelopeWidget( attack_param=AnalogParam.OSC_PITCH_ENV_ATTACK_TIME, decay_param=AnalogParam.OSC_PITCH_ENV_DECAY_TIME, depth_param=AnalogParam.OSC_PITCH_ENV_DEPTH, midi_helper=self.midi_helper, create_parameter_slider=self._create_parameter_slider, controls=self.controls, address=self.address, ) JDXiThemeManager.apply_adsr_style(self.pitch_env_widget, analog=True) env_group = QGroupBox("Envelope") env_group.setProperty("adsr", True) env_layout = QHBoxLayout() env_group.setLayout(env_layout) pitch_env_row_layout.addWidget(self.pitch_env_widget) pitch_env_row_layout.addWidget( self._create_parameter_slider( AnalogParam.OSC_PITCH_ENV_VELOCITY_SENSITIVITY, "Velocity Sensitivity", vertical=True, ) ) pitch_env_row_layout.addStretch() return pitch_env_group
[docs] def _update_pw_controls_state(self, waveform: AnalogOscWave): """ Update pulse width controls enabled state based on waveform :param waveform: AnalogOscWave value :return: None """ pw_enabled = waveform == AnalogOscWave.PULSE self.pwm_widget.setEnabled(pw_enabled)