"""
Arpeggio Editor Module
This module defines the `ArpeggioEditor` class, a specialized editor for configuring arpeggiator
settings within a synthesizer. It extends the `SynthEditor` class, providing a user-friendly
interface to control various arpeggiator parameters.
Classes:
- ArpeggioEditor: A `QWidget` subclass that allows users to modify arpeggiator parameters
such as style, grid, duration, velocity, accent, swing, octave range, and motif.
Features:
- Provides an intuitive UI with labeled controls and dropdown menus for parameter selection.
- Includes a toggle switch to enable or disable the arpeggiator.
- Displays an instrument image for better user engagement.
- Uses MIDI integration to send real-time parameter changes to the synthesizer.
- Supports dynamic visualization and interaction through sliders and combo boxes.
Usage:
```python
from PySide6.QtWidgets import QApplication
from midi_helper import MIDIHelper
app = QApplication([])
midi_helper = MIDIHelper()
editor = ArpeggioEditor(midi_helper=midi_helper)
editor.show()
app.exec()
```
Dependencies:
- PySide6 (for UI components)
- MIDIHelper (for MIDI communication)
- ArpeggioParameter (for managing parameter addresses and value ranges)
- Slider (for smooth control over numerical parameters)
"""
from typing import Dict, Optional
from picomidi.sysex.parameter.address import AddressParameter
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QGroupBox,
QHBoxLayout,
QLabel,
QVBoxLayout,
QWidget,
)
from jdxi_editor.jdxi.preset.helper import JDXiPresetHelper
from jdxi_editor.jdxi.style import JDXiStyle
from jdxi_editor.midi.data.address.address import ZERO_BYTE, RolandSysExAddress
from jdxi_editor.midi.data.address.arpeggio import ArpeggioAddress
from jdxi_editor.midi.data.arpeggio.arpeggio import (
ArpeggioDuration,
ArpeggioGrid,
ArpeggioMotif,
ArpeggioOctaveRange,
ArpeggioSwitch,
)
from jdxi_editor.midi.data.arpeggio.data import (
ARPEGGIO_STYLE,
)
from jdxi_editor.midi.data.parameter.arpeggio import ArpeggioParam
from jdxi_editor.midi.data.parameter.program.zone import ProgramZoneParam
from jdxi_editor.midi.io.helper import MidiIOHelper
from jdxi_editor.ui.editors.synth.simple import BasicEditor
from jdxi_editor.ui.widgets.display.digital import DigitalTitle
[docs]
class ArpeggioEditor(BasicEditor):
"""Arpeggio Editor Window"""
def __init__(
self,
midi_helper: MidiIOHelper,
preset_helper: Optional[JDXiPresetHelper] = None,
parent: Optional[QWidget] = None,
):
super().__init__(midi_helper=midi_helper, parent=parent)
"""
Initialize the ArpeggioEditor
:param midi_helper: MidiIOHelper
:param preset_helper: JDXIPresetHelper
:param parent: QWidget
"""
self.setWindowTitle("Arpeggio Editor")
[docs]
self.midi_helper = midi_helper
[docs]
self.preset_helper = preset_helper
[docs]
self.address = RolandSysExAddress(
msb=ArpeggioAddress.TEMPORARY_PROGRAM,
umb=ArpeggioAddress.ARP_PART,
lmb=ArpeggioAddress.ARP_GROUP,
lsb=ZERO_BYTE,
)
[docs]
self.partial_number = 0
[docs]
self.instrument_icon_folder = "arpeggiator"
[docs]
self.default_image = "arpeggiator2.png"
[docs]
self.controls: Dict[AddressParameter, QWidget] = {}
if parent:
if parent.current_synth_type:
if parent.current_synth_type == "Digital 1":
self.partial_number = 0
elif parent.current_synth_type == "Digital 2":
self.partial_number = 1
elif parent.current_synth_type == "Digital 3":
self.partial_number = 2
elif parent.current_synth_type == "Digital 4":
self.partial_number = 3
# Main layout
layout = QVBoxLayout()
self.setLayout(layout)
[docs]
self.title_label = DigitalTitle(tone_name="Arpeggiator")
from jdxi_editor.jdxi.style.theme_manager import JDXiThemeManager
JDXiThemeManager.apply_instrument_title_label(self.title_label)
# Image display
[docs]
self.image_label = QLabel()
self.image_label.setAlignment(
Qt.AlignmentFlag.AlignCenter
) # Center align the image
title_group_box = QGroupBox()
title_group_layout = QHBoxLayout()
title_group_box.setLayout(title_group_layout)
title_group_layout.addWidget(self.title_label)
title_group_layout.addWidget(self.image_label)
self.update_instrument_image()
main_row_hlayout = QHBoxLayout()
layout.addLayout(main_row_hlayout)
main_row_hlayout.addStretch()
rows_layout = QVBoxLayout()
main_row_hlayout.addLayout(rows_layout)
rows_layout.addWidget(title_group_box)
# Add on-off switch
program_zone_row = QHBoxLayout()
common_button = self._create_parameter_switch(
ProgramZoneParam.ARPEGGIO_SWITCH,
"Master Arpeggiator",
[switch_setting.display_name for switch_setting in ArpeggioSwitch],
)
program_zone_row.addWidget(common_button)
rows_layout.addLayout(program_zone_row)
# Add on-off switch
switch_row = QHBoxLayout()
switch_row.addWidget(self.switch_button)
rows_layout.addLayout(switch_row)
# Create address combo box for Arpeggio Style
[docs]
self.style_combo = self._create_parameter_combo_box(
ArpeggioParam.ARPEGGIO_STYLE, "Style", ARPEGGIO_STYLE
)
style_row = QHBoxLayout()
style_row.addWidget(self.style_combo)
rows_layout.addLayout(style_row)
# Create address combo box for Arpeggio Grid
# Add grid combo box
grid_row = QHBoxLayout()
[docs]
self.grid_combo = self._create_parameter_combo_box(
ArpeggioParam.ARPEGGIO_GRID,
"Grid:",
[grid.display_name for grid in ArpeggioGrid],
)
grid_row.addWidget(self.grid_combo)
rows_layout.addLayout(grid_row)
# Add grid combo box
duration_row = QHBoxLayout()
# Create address combo box for Arpeggio Duration
[docs]
self.duration_combo = self._create_parameter_combo_box(
ArpeggioParam.ARPEGGIO_DURATION,
"Duration",
[duration.display_name for duration in ArpeggioDuration],
)
duration_row.addWidget(self.duration_combo)
rows_layout.addLayout(duration_row)
# Add sliders
[docs]
self.velocity_slider = self._create_parameter_slider(
ArpeggioParam.ARPEGGIO_VELOCITY, "Velocity", 0, 127
)
rows_layout.addWidget(self.velocity_slider)
[docs]
self.accent_slider = self._create_parameter_slider(
ArpeggioParam.ARPEGGIO_ACCENT_RATE, "Accent", 0, 127
)
rows_layout.addWidget(self.accent_slider)
# Add octave range combo box
octave_row = QHBoxLayout()
[docs]
self.octave_combo = self._create_parameter_combo_box(
ArpeggioParam.ARPEGGIO_OCTAVE_RANGE,
"Octave Range:",
[octave.display_name for octave in ArpeggioOctaveRange],
[octave.midi_value for octave in ArpeggioOctaveRange],
)
# Set default to 0
self.octave_combo.combo_box.setCurrentIndex(3) # Index 3 is OCT_ZERO
octave_row.addWidget(self.octave_combo)
rows_layout.addLayout(octave_row)
# Add motif combo box
motif_row = QHBoxLayout()
[docs]
self.motif_combo = self._create_parameter_combo_box(
ArpeggioParam.ARPEGGIO_MOTIF,
"Motif:",
[motif.name for motif in ArpeggioMotif],
)
motif_row.addWidget(self.motif_combo)
rows_layout.addLayout(motif_row)
rows_layout.addStretch()
main_row_hlayout.addStretch()