feat: initial capture + exact routing & python control panel gui updated

This commit is contained in:
serifpersia 2025-07-15 15:53:51 +02:00
parent a61ad1d1e9
commit d1cf14f8b7
3 changed files with 652 additions and 306 deletions

BIN
tascam_controls/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -4,8 +4,9 @@ import re
import os import os
from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox,
QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox) QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox)
from PyQt6.QtGui import QPixmap, QFont, QIcon from PyQt6.QtGui import QPixmap, QFont, QIcon, QPainter
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt, QBuffer, QIODevice
import base64
def resource_path(relative_path): def resource_path(relative_path):
try: try:
@ -16,41 +17,63 @@ def resource_path(relative_path):
DARK_STYLESHEET = """ DARK_STYLESHEET = """
QWidget { QWidget {
background-color: #2b2b2b; background-color: transparent;
color: #f0f0f0; color: #DAE0ED;
font-family: Arial; font-family: Arial;
} }
QLabel { QLabel {
background-color: transparent; background-color: transparent;
} }
QLabel#Title {
font-size: 15pt;
font-weight: bold;
color: #FFFFFF;
}
QLabel#SectionHeader {
font-size: 11pt;
font-weight: bold;
color: #92E8FF;
margin-top: 10px;
margin-bottom: 3px;
}
QLabel#ControlLabel {
font-size: 9pt;
color: #CBD2E6;
}
QComboBox { QComboBox {
background-color: #3c3f41; background-color: rgba(10, 10, 20, 0.25);
border: 1px solid #555; border: 1px solid #3A4760;
border-radius: 4px; border-radius: 4px;
padding: 4px; padding: 4px;
color: #DAE0ED;
} }
QComboBox:hover { QComboBox:hover {
border: 1px solid #777; background-color: rgba(15, 15, 25, 0.35);
border: 1px solid #6482B4;
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: #3c3f41; background-color: rgba(15, 15, 25, 0.9);
border: 1px solid #555; border: 1px solid #3A4760;
selection-background-color: #5a5d5f; selection-background-color: #6A3AB1;
color: #DAE0ED;
} }
QPushButton { QPushButton {
background-color: #3c3f41; background-color: rgba(10, 10, 20, 0.25);
border: 1px solid #555; border: 1px solid #3A4760;
border-radius: 4px; border-radius: 4px;
padding: 5px; padding: 5px;
color: #92E8FF;
} }
QPushButton:hover { QPushButton:hover {
background-color: #4f5254; background-color: rgba(15, 15, 25, 0.35);
border: 1px solid #6482B4;
} }
QPushButton:pressed { QPushButton:pressed {
background-color: #5a5d5f; background-color: rgba(20, 20, 30, 0.45);
border: 1px solid #A020F0;
} }
""" """
@ -81,21 +104,6 @@ class AmixerController:
return 0 return 0
return 0 return 0
@staticmethod
def get_control_string(card_id, control_name):
if not card_id: return "N/A"
try:
cmd = ['amixer', '-c', card_id, 'cget', f"name='{control_name}'"]
output = subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL)
for line in output.splitlines():
if ': values=' in line:
value_str = line.split('=')[1]
byte_values = [int(b, 16) for b in value_str.split(',')]
return bytes(byte_values).partition(b'\0')[0].decode('utf-8', errors='ignore').strip()
except (FileNotFoundError, subprocess.CalledProcessError, IndexError, ValueError):
return "Error"
return "N/A"
@staticmethod @staticmethod
def set_control_value(card_id, control_name, value): def set_control_value(card_id, control_name, value):
if not card_id: return False if not card_id: return False
@ -127,50 +135,88 @@ class TascamControlPanel(QWidget):
def init_ui(self): def init_ui(self):
self.setWindowTitle("TASCAM US-144MKII Control Panel") self.setWindowTitle("TASCAM US-144MKII Control Panel")
self.setWindowIcon(QIcon(resource_path("icon.ico"))) self.setWindowIcon(QIcon(resource_path("icon.ico")))
self.setFixedSize(800, 450) self.setFixedSize(820, 450)
self.setStyleSheet(DARK_STYLESHEET) self.setStyleSheet(DARK_STYLESHEET)
main_layout = QHBoxLayout(self) self.background_label = QLabel(self)
left_panel, middle_panel, right_panel = QVBoxLayout(), QVBoxLayout(), QVBoxLayout() self.background_label.setGeometry(self.rect())
self.background_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
header_layout = QVBoxLayout() bg_image_path = resource_path("bg.png")
self.original_bg_pixmap = QPixmap(bg_image_path)
if self.original_bg_pixmap.isNull():
print(f"Warning: Could not load background image from {bg_image_path}. Using solid color.")
self.setStyleSheet(self.styleSheet() + "TascamControlPanel { background-color: #1a1a1a; }")
else:
self._update_background_pixmap()
self.background_label.lower()
content_container = QWidget(self)
top_level_layout = QHBoxLayout(content_container)
top_level_layout.setContentsMargins(20, 20, 20, 20)
top_level_layout.setSpacing(25)
main_overall_layout = QVBoxLayout(self)
main_overall_layout.setContentsMargins(0, 0, 0, 0)
main_overall_layout.addWidget(content_container)
left_panel = QVBoxLayout()
info_grid = QGridLayout()
logo_label = QLabel() logo_label = QLabel()
logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaled(250, 50, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaledToWidth(250, Qt.TransformationMode.SmoothTransformation))
title_label = QLabel("US-144 MKII Control Panel") title_label = QLabel("US-144 MKII Control Panel")
title_label.setFont(QFont("Arial", 15, QFont.Weight.Bold)) title_label.setObjectName("Title")
header_layout.addWidget(logo_label) left_panel.addWidget(logo_label)
header_layout.addWidget(title_label) left_panel.addWidget(title_label)
info_grid.setSpacing(5)
info_layout = QGridLayout()
self.info_labels = {} self.info_labels = {}
info_data = { info_data = {
"Driver Version:": "N/A", "Device:": "US-144 MKII", "Driver Version:": "driver_version", "Device:": "device",
"Sample Width:": "24 bits", "Sample Rate:": "N/A", "Sample Width:": "sample_width", "Sample Rate:": "sample_rate",
"Sample Clock Source:": "internal", "Digital Input Status:": "unavailable" "Sample Clock Source:": "clock_source", "Digital Input Status:": "digital_status"
} }
for row, (label_text, value_text) in enumerate(info_data.items()): row = 0
key = label_text.replace(":", "").replace(" ", "_").lower() for label_text, key in info_data.items():
label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold)) label = QLabel(label_text)
value_label = QLabel(value_text, font=QFont("Arial", 10)) label.setFont(QFont("Arial", 9, QFont.Weight.Bold))
info_layout.addWidget(label, row, 0, Qt.AlignmentFlag.AlignLeft) value_label = QLabel("N/A")
info_layout.addWidget(value_label, row, 1, Qt.AlignmentFlag.AlignLeft) value_label.setFont(QFont("Arial", 9))
info_grid.addWidget(label, row, 0)
info_grid.addWidget(value_label, row, 1)
self.info_labels[key] = value_label self.info_labels[key] = value_label
row += 1
left_panel.addLayout(info_layout) left_panel.addLayout(info_grid)
left_panel.addStretch() left_panel.addStretch()
middle_panel.setSpacing(15) middle_panel = QVBoxLayout()
latency_container, self.latency_combo = self.create_control_widget("Audio Performance", ["low latency", "normal latency", "high latency"]) middle_panel.setSpacing(0)
mock_container1, _ = self.create_control_widget("Sample Clock Source", ["internal", "auto"])
mock_container2, _ = self.create_control_widget("Digital Output Format", ["S/PDIF"]) inputs_header = QLabel("INPUTS")
routing_container, self.routing_combo = self.create_control_widget("LINE OUTPUTS", ["Stereo to All", "Swapped", "Digital In to All"]) inputs_header.setObjectName("SectionHeader")
capture_12_container, self.capture_12_combo = self.create_control_widget("ch1 and ch2", ["Analog In", "Digital In"])
capture_34_container, self.capture_34_combo = self.create_control_widget("ch3 and ch4", ["Analog In", "Digital In"])
middle_panel.addWidget(inputs_header)
middle_panel.addWidget(capture_12_container)
middle_panel.addWidget(capture_34_container)
line_header = QLabel("LINE")
line_header.setObjectName("SectionHeader")
line_out_container, self.line_out_combo = self.create_control_widget("ch1 and ch2", ["Playback 1-2", "Playback 3-4"])
middle_panel.addWidget(line_header)
middle_panel.addWidget(line_out_container)
digital_header = QLabel("DIGITAL")
digital_header.setObjectName("SectionHeader")
digital_out_container, self.digital_out_combo = self.create_control_widget("ch3 and ch4", ["Playback 1-2", "Playback 3-4"])
middle_panel.addWidget(digital_header)
middle_panel.addWidget(digital_out_container)
middle_panel.addWidget(latency_container)
middle_panel.addWidget(mock_container1)
middle_panel.addWidget(mock_container2)
middle_panel.addWidget(routing_container)
middle_panel.addStretch() middle_panel.addStretch()
right_panel = QVBoxLayout()
device_image_label = QLabel() device_image_label = QLabel()
device_image_label.setPixmap(QPixmap(resource_path("device.png")).scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) device_image_label.setPixmap(QPixmap(resource_path("device.png")).scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
device_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) device_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
@ -181,27 +227,36 @@ class TascamControlPanel(QWidget):
right_panel.addStretch() right_panel.addStretch()
right_panel.addWidget(exit_button, 0, Qt.AlignmentFlag.AlignCenter) right_panel.addWidget(exit_button, 0, Qt.AlignmentFlag.AlignCenter)
top_level_layout = QVBoxLayout() top_level_layout.addLayout(left_panel, 3)
top_level_layout.addLayout(header_layout) top_level_layout.addLayout(middle_panel, 3)
top_level_layout.addSpacing(20) top_level_layout.addLayout(right_panel, 3)
panels_layout = QHBoxLayout()
panels_layout.setContentsMargins(10, 0, 10, 0)
panels_layout.addLayout(left_panel, 1)
panels_layout.addLayout(middle_panel, 1)
panels_layout.addLayout(right_panel, 1)
top_level_layout.addLayout(panels_layout)
main_layout.addLayout(top_level_layout)
self.setLayout(main_layout)
self.latency_combo.currentIndexChanged.connect(self.on_latency_changed) self.line_out_combo.currentIndexChanged.connect(lambda i: self.set_value("Line Out Source", i))
self.routing_combo.currentIndexChanged.connect(self.on_routing_changed) self.digital_out_combo.currentIndexChanged.connect(lambda i: self.set_value("Digital Out Source", i))
self.capture_12_combo.currentIndexChanged.connect(lambda i: self.set_value("Capture 1-2 Source", i))
self.capture_34_combo.currentIndexChanged.connect(lambda i: self.set_value("Capture 3-4 Source", i))
def _update_background_pixmap(self):
if not self.original_bg_pixmap.isNull():
scaled_pixmap = self.original_bg_pixmap.scaled(
self.size(),
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
Qt.TransformationMode.SmoothTransformation
)
self.background_label.setPixmap(scaled_pixmap)
def resizeEvent(self, event):
self.background_label.setGeometry(self.rect())
self._update_background_pixmap()
super().resizeEvent(event)
def create_control_widget(self, label_text, combo_items): def create_control_widget(self, label_text, combo_items):
container_widget = QWidget() container_widget = QWidget()
layout = QVBoxLayout(container_widget) layout = QVBoxLayout(container_widget)
layout.setContentsMargins(0,0,0,0) layout.setContentsMargins(0, 8, 0, 8)
layout.setSpacing(2) layout.setSpacing(2)
label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold)) label = QLabel(label_text)
label.setObjectName("ControlLabel")
combo_box = QComboBox() combo_box = QComboBox()
combo_box.addItems(combo_items) combo_box.addItems(combo_items)
layout.addWidget(label) layout.addWidget(label)
@ -209,30 +264,29 @@ class TascamControlPanel(QWidget):
return container_widget, combo_box return container_widget, combo_box
def load_dynamic_settings(self): def load_dynamic_settings(self):
driver_ver = AmixerController.read_sysfs_attr(self.card_id, "driver_version") self.info_labels['driver_version'].setText(AmixerController.read_sysfs_attr(self.card_id, "driver_version"))
self.info_labels['driver_version'].setText(driver_ver) self.info_labels['device'].setText("US-144 MKII")
self.info_labels['sample_width'].setText("24 bits")
self.info_labels['clock_source'].setText("internal")
self.info_labels['digital_status'].setText("unavailable")
rate_val = AmixerController.get_control_value(self.card_id, "Sample Rate") rate_val = AmixerController.get_control_value(self.card_id, "Sample Rate")
if rate_val > 0: self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz" if rate_val > 0 else "N/A (inactive)")
self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz")
else:
self.info_labels['sample_rate'].setText("N/A (inactive)")
latency_val = AmixerController.get_control_value(self.card_id, "Latency Profile") self.update_combo(self.line_out_combo, "Line Out Source")
self.latency_combo.blockSignals(True) self.update_combo(self.digital_out_combo, "Digital Out Source")
self.latency_combo.setCurrentIndex(latency_val) self.update_combo(self.digital_out_combo, "Digital Out Source")
self.latency_combo.blockSignals(False) self.update_combo(self.capture_12_combo, "Capture 1-2 Source")
self.update_combo(self.capture_34_combo, "Capture 3-4 Source")
routing_val = AmixerController.get_control_value(self.card_id, "Playback Routing") def update_combo(self, combo, control_name):
self.routing_combo.blockSignals(True) value = AmixerController.get_control_value(self.card_id, control_name)
self.routing_combo.setCurrentIndex(routing_val) combo.blockSignals(True)
self.routing_combo.blockSignals(False) combo.setCurrentIndex(value)
combo.blockSignals(False)
def on_latency_changed(self, index): def set_value(self, control_name, index):
AmixerController.set_control_value(self.card_id, "Latency Profile", index) AmixerController.set_control_value(self.card_id, control_name, index)
def on_routing_changed(self, index):
AmixerController.set_control_value(self.card_id, "Playback Routing", index)
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)

File diff suppressed because it is too large Load Diff