Fix AttributeError: remove redundant mapping_learned connection in main.py
TopBarWidget already connects mapping_learned to _on_mapping_learned internally in set_midi_engine, so the external connection in main.py was both redundant and referenced a non-existent method.
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
# main.py
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget,
|
||||
QApplication, QMainWindow, QWidget, QFileDialog,
|
||||
QVBoxLayout, QHBoxLayout, QSplitter
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont, QColor, QPalette
|
||||
|
||||
from audio_engine import AudioEngine
|
||||
from deck_widget import DeckWidget
|
||||
from deck_widget import DeckWidget, ExternalDeckWidget
|
||||
from cart_widget import CartBankWidget
|
||||
from playlist_widget import PlaylistWidget
|
||||
from midi_engine import MidiEngine
|
||||
@@ -25,7 +27,7 @@ class RadioPanelWindow(QMainWindow):
|
||||
self.engine = AudioEngine()
|
||||
|
||||
self.setWindowTitle("Getting To It")
|
||||
self.setMinimumSize(1280, 720)
|
||||
self.setMinimumSize(1400, 860)
|
||||
self.resize(1920, 1080)
|
||||
self._apply_global_style(THEMES[0])
|
||||
|
||||
@@ -33,31 +35,26 @@ class RadioPanelWindow(QMainWindow):
|
||||
self.setCentralWidget(central)
|
||||
|
||||
outer = QVBoxLayout(central)
|
||||
outer.setContentsMargins(8, 8, 8, 8)
|
||||
outer.setSpacing(8)
|
||||
outer.setContentsMargins(6, 6, 6, 6)
|
||||
outer.setSpacing(6)
|
||||
|
||||
# Top bar
|
||||
self.top_bar = TopBarWidget()
|
||||
self.top_bar.theme_changed.connect(self._on_theme_changed)
|
||||
self.top_bar.ratio_changed.connect(self._apply_ratio)
|
||||
self.top_bar.save_session.connect(self._save_session)
|
||||
self.top_bar.load_session.connect(self._load_session)
|
||||
outer.addWidget(self.top_bar)
|
||||
|
||||
# ── Build widgets ─────────────────────────────────────────────────
|
||||
|
||||
self.deck1 = DeckWidget(
|
||||
deck_key="deck1",
|
||||
label="DECK 1",
|
||||
accent_color="#00897b",
|
||||
engine=self.engine
|
||||
)
|
||||
self.deck2 = DeckWidget(
|
||||
deck_key="deck2",
|
||||
label="DECK 2",
|
||||
accent_color="#f9ca24",
|
||||
engine=self.engine
|
||||
)
|
||||
self.deck1 = DeckWidget("deck1", "DECK 1", "#00b894", self.engine)
|
||||
self.deck2 = DeckWidget("deck2", "DECK 2", "#6c5ce7", self.engine)
|
||||
self.deck3 = ExternalDeckWidget("deck3", "DECK 3", "#fdcb6e", self.engine)
|
||||
self.deck4 = ExternalDeckWidget("deck4", "DECK 4", "#e17055", self.engine)
|
||||
|
||||
self.cart_bank = CartBankWidget(self.engine)
|
||||
self.cart_bank.setFixedHeight(280)
|
||||
self.cart_bank.setFixedHeight(220)
|
||||
|
||||
self.playlist_widget = PlaylistWidget(
|
||||
engine=self.engine,
|
||||
@@ -72,46 +69,52 @@ class RadioPanelWindow(QMainWindow):
|
||||
|
||||
self.mixer = MixerWidget(self.engine)
|
||||
|
||||
# ── Centre column (decks + carts + mixer) ─────────────────────────
|
||||
# ── Centre column with vertical splitter ─────────────────────────
|
||||
centre_col = QVBoxLayout()
|
||||
centre_col.setSpacing(8)
|
||||
centre_col.setSpacing(6)
|
||||
|
||||
# Top: decks row + carts
|
||||
# Top: 4 decks in 2x2 grid + carts
|
||||
deck_area = QWidget()
|
||||
deck_area.setStyleSheet("background: transparent; border: none;")
|
||||
deck_layout = QVBoxLayout(deck_area)
|
||||
deck_layout.setContentsMargins(0, 0, 0, 0)
|
||||
deck_layout.setSpacing(8)
|
||||
deck_layout.setSpacing(4)
|
||||
|
||||
deck_row1 = QHBoxLayout()
|
||||
deck_row1.setSpacing(4)
|
||||
deck_row1.addWidget(self.deck1)
|
||||
deck_row1.addWidget(self.deck2)
|
||||
deck_layout.addLayout(deck_row1)
|
||||
|
||||
deck_row2 = QHBoxLayout()
|
||||
deck_row2.setSpacing(4)
|
||||
deck_row2.addWidget(self.deck3)
|
||||
deck_row2.addWidget(self.deck4)
|
||||
deck_layout.addLayout(deck_row2)
|
||||
|
||||
deck_row = QHBoxLayout()
|
||||
deck_row.setSpacing(8)
|
||||
deck_row.addWidget(self.deck1)
|
||||
deck_row.addWidget(self.deck2)
|
||||
deck_layout.addLayout(deck_row)
|
||||
deck_layout.addWidget(self.cart_bank)
|
||||
deck_layout.addStretch()
|
||||
|
||||
# Bottom: mixer
|
||||
centre_col.addWidget(deck_area, stretch=1)
|
||||
centre_col.addWidget(self.mixer)
|
||||
# Mixer fills remaining space below decks
|
||||
centre_col.addWidget(deck_area)
|
||||
centre_col.addWidget(self.mixer, stretch=1)
|
||||
|
||||
centre_widget = QWidget()
|
||||
centre_widget.setStyleSheet("background: transparent; border: none;")
|
||||
centre_widget.setLayout(centre_col)
|
||||
|
||||
# ── Splitter ──────────────────────────────────────────────────────
|
||||
# ── Horizontal splitter ──────────────────────────────────────────
|
||||
self.splitter = QSplitter(Qt.Horizontal)
|
||||
self.splitter.setHandleWidth(6)
|
||||
self.splitter.setHandleWidth(4)
|
||||
self._style_splitter(THEMES[0])
|
||||
|
||||
self.splitter.addWidget(self.playlist_widget)
|
||||
self.splitter.addWidget(centre_widget)
|
||||
self.splitter.addWidget(self.history_widget)
|
||||
self.splitter.setSizes([400, 1120, 400])
|
||||
self.splitter.setSizes([360, 1160, 360])
|
||||
|
||||
outer.addWidget(self.splitter)
|
||||
|
||||
# ── Wire signals ──────────────────────────────────────────────────
|
||||
# ── Wire signals ─────────────────────────────────────────────────
|
||||
self.deck1.track_started.connect(self.history_widget.add_track)
|
||||
self.deck2.track_started.connect(self.history_widget.add_track)
|
||||
|
||||
@@ -121,8 +124,8 @@ class RadioPanelWindow(QMainWindow):
|
||||
def _style_splitter(self, theme: dict):
|
||||
self.splitter.setStyleSheet(f"""
|
||||
QSplitter::handle {{
|
||||
background-color: #333333;
|
||||
border-radius: 3px;
|
||||
background-color: #22242a;
|
||||
border-radius: 2px;
|
||||
}}
|
||||
QSplitter::handle:hover {{
|
||||
background-color: {theme['accent']};
|
||||
@@ -137,10 +140,15 @@ class RadioPanelWindow(QMainWindow):
|
||||
self._style_splitter(theme)
|
||||
self.deck1.apply_theme(theme)
|
||||
self.deck2.apply_theme(theme)
|
||||
self.deck3.apply_theme(theme)
|
||||
self.deck4.apply_theme(theme)
|
||||
self.cart_bank.apply_theme(theme)
|
||||
self.history_widget.apply_theme(theme)
|
||||
self.mixer.apply_theme(theme)
|
||||
|
||||
def _apply_ratio(self, w, h):
|
||||
self.resize(w, h)
|
||||
|
||||
def _apply_global_style(self, theme: dict):
|
||||
self.setStyleSheet(f"""
|
||||
QMainWindow {{
|
||||
@@ -149,7 +157,7 @@ class RadioPanelWindow(QMainWindow):
|
||||
QWidget {{
|
||||
background-color: {theme['bg']};
|
||||
color: {theme['text']};
|
||||
font-family: Arial;
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
}}
|
||||
QToolTip {{
|
||||
background-color: {theme['bg2']};
|
||||
@@ -159,6 +167,60 @@ class RadioPanelWindow(QMainWindow):
|
||||
}}
|
||||
""")
|
||||
|
||||
def _save_session(self):
|
||||
path, _ = QFileDialog.getSaveFileName(
|
||||
self.top_bar, "Save Session",
|
||||
os.path.expanduser("~/.gti_radiostudio/session.json"),
|
||||
"JSON Files (*.json)"
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
|
||||
state = {
|
||||
'window_size': (self.width(), self.height()),
|
||||
'channels': [],
|
||||
}
|
||||
|
||||
for ch in self.engine.mixer['channels']:
|
||||
state['channels'].append({
|
||||
'name': ch.name,
|
||||
'volume': ch.volume,
|
||||
'muted': ch.muted,
|
||||
'solo': ch.solo,
|
||||
'pfl': ch.pfl,
|
||||
})
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
def _load_session(self):
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self.top_bar, "Load Session",
|
||||
os.path.expanduser("~/.gti_radiostudio/session.json"),
|
||||
"JSON Files (*.json)"
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
|
||||
with open(path) as f:
|
||||
state = json.load(f)
|
||||
|
||||
ws = state.get('window_size')
|
||||
if ws:
|
||||
self.resize(ws[0], ws[1])
|
||||
|
||||
for i, ch_state in enumerate(state.get('channels', [])):
|
||||
if i < len(self.engine.mixer['channels']):
|
||||
ch = self.engine.mixer['channels'][i]
|
||||
ch.name = ch_state.get('name', ch.name)
|
||||
ch.volume = ch_state.get('volume', ch.volume)
|
||||
ch.muted = ch_state.get('muted', ch.muted)
|
||||
ch.solo = ch_state.get('solo', ch.solo)
|
||||
ch.pfl = ch_state.get('pfl', ch.pfl)
|
||||
|
||||
self.mixer.refresh_strips()
|
||||
|
||||
def _setup_midi(self):
|
||||
self.midi = MidiEngine()
|
||||
|
||||
@@ -168,15 +230,27 @@ class RadioPanelWindow(QMainWindow):
|
||||
self.midi.cart_trigger.connect(self._on_midi_cart)
|
||||
self.midi.mixer_volume.connect(self._on_midi_mixer_volume)
|
||||
self.midi.mixer_mute.connect(self._on_midi_mixer_mute)
|
||||
self.midi.mixer_solo.connect(self._on_midi_mixer_solo)
|
||||
self.midi.mixer_pfl.connect(self._on_midi_mixer_pfl)
|
||||
self.midi.master_control.connect(self._on_midi_master_control)
|
||||
self.midi.controller_connected.connect(self._on_midi_status)
|
||||
|
||||
self.midi.start()
|
||||
self.top_bar.set_midi_engine(self.midi)
|
||||
|
||||
ports = self.midi.list_ports()
|
||||
if ports:
|
||||
print(f"[MIDI] Available ports: {ports}")
|
||||
|
||||
def _on_midi_play_pause(self, deck: str):
|
||||
if deck == 'deck1':
|
||||
self.deck1.toggle_play()
|
||||
elif deck == 'deck2':
|
||||
self.deck2.toggle_play()
|
||||
elif deck == 'deck3':
|
||||
self.deck3.toggle_play()
|
||||
elif deck == 'deck4':
|
||||
self.deck4.toggle_play()
|
||||
|
||||
def _on_midi_cue(self, deck: str):
|
||||
if deck == 'deck1':
|
||||
@@ -199,6 +273,31 @@ class RadioPanelWindow(QMainWindow):
|
||||
def _on_midi_mixer_mute(self, channel: int, muted: bool):
|
||||
self.engine.set_channel_mute(channel, muted)
|
||||
|
||||
def _on_midi_mixer_solo(self, channel: int, on: bool):
|
||||
self.engine.set_channel_solo(channel, on)
|
||||
|
||||
def _on_midi_mixer_pfl(self, channel: int, on: bool):
|
||||
self.engine.set_channel_pfl(channel, on)
|
||||
|
||||
def _on_midi_master_control(self, knob: str, val: float):
|
||||
master = self.engine.mixer['master']
|
||||
attrs = {
|
||||
"threshold_db": (-60, 0),
|
||||
"ratio": (1, 20),
|
||||
"attack_ms": (0.1, 50),
|
||||
"release_ms": (10, 1000),
|
||||
"makeup_gain_db": (0, 24),
|
||||
"ceiling_db": (-10, 0),
|
||||
}
|
||||
if knob in attrs:
|
||||
lo, hi = attrs[knob]
|
||||
v = lo + val * (hi - lo)
|
||||
if knob == "ratio":
|
||||
v = round(max(1, v), 1)
|
||||
elif knob in ("threshold_db", "ceiling_db", "makeup_gain_db"):
|
||||
v = round(max(lo, min(hi, v)), 1)
|
||||
setattr(master.compressor if knob != "ceiling_db" else master.limiter, knob, v)
|
||||
|
||||
def _on_midi_status(self, connected: bool, name: str):
|
||||
if connected:
|
||||
print(f"[MIDI] Connected: {name}")
|
||||
@@ -217,14 +316,14 @@ def main():
|
||||
app.setOrganizationName("Getting To It")
|
||||
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.Window, QColor("#0d0d0d"))
|
||||
palette.setColor(QPalette.WindowText, QColor("#cccccc"))
|
||||
palette.setColor(QPalette.Base, QColor("#111111"))
|
||||
palette.setColor(QPalette.AlternateBase, QColor("#1a1a1a"))
|
||||
palette.setColor(QPalette.Text, QColor("#cccccc"))
|
||||
palette.setColor(QPalette.Button, QColor("#2a2a2a"))
|
||||
palette.setColor(QPalette.ButtonText, QColor("#cccccc"))
|
||||
palette.setColor(QPalette.Highlight, QColor("#1a3a5a"))
|
||||
palette.setColor(QPalette.Window, QColor("#121418"))
|
||||
palette.setColor(QPalette.WindowText, QColor("#d0d4dc"))
|
||||
palette.setColor(QPalette.Base, QColor("#1a1c23"))
|
||||
palette.setColor(QPalette.AlternateBase, QColor("#22242c"))
|
||||
palette.setColor(QPalette.Text, QColor("#d0d4dc"))
|
||||
palette.setColor(QPalette.Button, QColor("#2a2c35"))
|
||||
palette.setColor(QPalette.ButtonText, QColor("#d0d4dc"))
|
||||
palette.setColor(QPalette.Highlight, QColor("#00b894"))
|
||||
palette.setColor(QPalette.HighlightedText, QColor("#ffffff"))
|
||||
app.setPalette(palette)
|
||||
|
||||
@@ -235,4 +334,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
Reference in New Issue
Block a user