"""
Piano keyboard widget for JD-Xi Manager.
This module defines address PianoKeyboard widget, address custom QWidget that arranges and displays
address set of piano keys styled like those on address JD-Xi synthesizer. The widget combines both
white and black keys to form address complete piano keyboard, along with labels representing
drum pad names.
Key Features:
- **Custom Key Dimensions:** White and black keys are sized and positioned appropriately,
with configurable widths and heights.
- **Dynamic Key Creation:** White keys are created first in address horizontal layout,
while black keys are overlaid at specific positions.
- **Drum Pad Labels:** A row of labels is displayed above the keyboard to denote
corresponding drum pad names.
- **Signal Integration:** Each key emits custom signals (noteOn and noteOff) to notify
parent widgets of key events.
- **MIDI Channel Configuration:** The widget supports setting address MIDI channel for outgoing
note messages.
- **Styling and Layout:** Uses QHBoxLayout and QVBoxLayout to manage key and label placement,
ensuring address neat appearance.
Usage Example:
from jdxi_editor.ui.widgets.piano.keyboard import PianoKeyboard
keyboard = PianoKeyboard(parent=main_window)
keyboard.set_midi_channel(1)
main_window.setCentralWidget(keyboard)
This module requires PySide6 and proper integration with the JD-Xi Manager's signal handling for note events.
"""
from decologr import Decologr as log
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (
QGraphicsDropShadowEffect,
QHBoxLayout,
QLabel,
QVBoxLayout,
QWidget,
)
from jdxi_editor.jdxi.style import JDXiStyle
from jdxi_editor.midi.data.piano.keyboard import (
DRUM_LABELS,
KEYBOARD_BLACK_NOTES,
KEYBOARD_WHITE_NOTES,
)
from jdxi_editor.ui.widgets.piano.key import PianoKey
[docs]
class PianoKeyboard(QWidget):
"""Widget containing a row of piano keys styled like JD-Xi"""
def __init__(self, parent: QWidget = None):
super().__init__(parent)
[docs]
self.current_channel = 0 # Default to analog synth channel
# Set keyboard dimensions
[docs]
self.white_key_width = 35
[docs]
self.white_key_height = 160
[docs]
self.black_key_width = int(self.white_key_width * 0.6)
[docs]
self.black_key_height = 100
# Define note patterns
[docs]
self.white_notes = KEYBOARD_WHITE_NOTES
[docs]
self.black_notes = KEYBOARD_BLACK_NOTES
# Calculate total width
total_width = len(self.white_notes) * self.white_key_width
self.setFixedSize(
total_width + 2, self.white_key_height + 30
) # Added height for labels
# Create main layout
main_layout = QVBoxLayout(self)
main_layout.setSpacing(0)
main_layout.setContentsMargins(0, 0, 0, 0)
# Add drum pad labels
labels_layout = QHBoxLayout()
labels_layout.setSpacing(0)
labels_layout.setContentsMargins(1, 1, 1, 1)
# Create and style labels
for text in DRUM_LABELS:
label = QLabel(text)
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
label.setStyleSheet(JDXiStyle.KEYBOARD_DRUM_LABELS)
labels_layout.addWidget(label)
labels_layout.addStretch()
main_layout.addLayout(labels_layout)
# Create keyboard container widget
keyboard_widget = QWidget()
keyboard_widget.setFixedSize(total_width + 2, self.white_key_height + 2)
keyboard_layout = QHBoxLayout(keyboard_widget)
keyboard_layout.setSpacing(0)
keyboard_layout.setContentsMargins(1, 1, 1, 1)
# Add shadow effect to keyboard widget
shadow_effect = QGraphicsDropShadowEffect()
shadow_effect.setBlurRadius(10)
shadow_effect.setOffset(5, 5)
shadow_effect.setColor(QColor(0, 0, 0, 50))
keyboard_widget.setGraphicsEffect(shadow_effect)
# Create keys
self._create_keys(keyboard_widget)
main_layout.addWidget(keyboard_widget)
[docs]
def _create_keys(self, keyboard_widget: QWidget) -> None:
"""
Create piano keys with individual shadows
:param keyboard_widget: QWidget
"""
def apply_shadow(widget):
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(10)
shadow.setOffset(3, 3)
shadow.setColor(QColor(0, 0, 0, 80))
widget.setGraphicsEffect(shadow)
# First create all white keys
for _, note in enumerate(self.white_notes):
key = PianoKey(
note,
is_black=False,
width=self.white_key_width,
height=self.white_key_height,
)
apply_shadow(key)
keyboard_widget.layout().addWidget(key)
if hasattr(self.parent(), "handle_piano_note_on"):
key.noteOn.connect(self.parent().handle_piano_note_on)
if hasattr(self.parent(), "handle_piano_note_off"):
key.noteOff.connect(self.parent().handle_piano_note_off)
# Add black keys
black_positions = [0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 17, 18, 19]
for pos, note in zip(
black_positions, [n for n in KEYBOARD_BLACK_NOTES if n is not None]
):
black_key = PianoKey(
note,
is_black=True,
width=self.black_key_width,
height=self.black_key_height,
)
black_key.setParent(keyboard_widget)
apply_shadow(black_key)
x_pos = (pos * self.white_key_width) + (
self.white_key_width - self.black_key_width // 2
)
black_key.move(x_pos, 0)
black_key.show()
if hasattr(self.parent(), "handle_piano_note_on"):
black_key.noteOn.connect(self.parent().handle_piano_note_on)
if hasattr(self.parent(), "handle_piano_note_off"):
black_key.noteOff.connect(self.parent().handle_piano_note_off)
[docs]
def set_midi_channel(self, channel: int) -> None:
"""Set MIDI channel for note messages
:param channel: int
"""
self.current_channel = channel
log.message(f"Piano keyboard set to channel {channel}")