"""
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._create_parameter_slider = create_parameter_slider
[docs]
self._create_parameter_switch = create_parameter_switch
[docs]
self.midi_helper = midi_helper
[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_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)