feat: initial capture + exact routing & python control panel gui updated
This commit is contained in:
parent
a61ad1d1e9
commit
d1cf14f8b7
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
|
|
@ -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)
|
||||||
|
|
|
||||||
718
us144mkii.c
718
us144mkii.c
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue