dummy capture & playback routing
This commit is contained in:
parent
5b572c0df2
commit
8259275c9e
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
|
|
@ -4,8 +4,9 @@ import re
|
|||
import os
|
||||
from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox,
|
||||
QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox)
|
||||
from PyQt6.QtGui import QPixmap, QFont, QIcon
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QPixmap, QFont, QIcon, QPainter
|
||||
from PyQt6.QtCore import Qt, QBuffer, QIODevice
|
||||
import base64
|
||||
|
||||
def resource_path(relative_path):
|
||||
try:
|
||||
|
|
@ -16,41 +17,63 @@ def resource_path(relative_path):
|
|||
|
||||
DARK_STYLESHEET = """
|
||||
QWidget {
|
||||
background-color: #2b2b2b;
|
||||
color: #f0f0f0;
|
||||
background-color: transparent;
|
||||
color: #DAE0ED;
|
||||
font-family: Arial;
|
||||
}
|
||||
QLabel {
|
||||
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 {
|
||||
background-color: #3c3f41;
|
||||
border: 1px solid #555;
|
||||
background-color: rgba(10, 10, 20, 0.25);
|
||||
border: 1px solid #3A4760;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
color: #DAE0ED;
|
||||
}
|
||||
QComboBox:hover {
|
||||
border: 1px solid #777;
|
||||
background-color: rgba(15, 15, 25, 0.35);
|
||||
border: 1px solid #6482B4;
|
||||
}
|
||||
QComboBox::drop-down {
|
||||
border: none;
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
background-color: #3c3f41;
|
||||
border: 1px solid #555;
|
||||
selection-background-color: #5a5d5f;
|
||||
background-color: rgba(15, 15, 25, 0.9);
|
||||
border: 1px solid #3A4760;
|
||||
selection-background-color: #6A3AB1;
|
||||
color: #DAE0ED;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #3c3f41;
|
||||
border: 1px solid #555;
|
||||
background-color: rgba(10, 10, 20, 0.25);
|
||||
border: 1px solid #3A4760;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
color: #92E8FF;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #4f5254;
|
||||
background-color: rgba(15, 15, 25, 0.35);
|
||||
border: 1px solid #6482B4;
|
||||
}
|
||||
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
|
||||
|
||||
@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
|
||||
def set_control_value(card_id, control_name, value):
|
||||
if not card_id: return False
|
||||
|
|
@ -127,50 +135,96 @@ class TascamControlPanel(QWidget):
|
|||
def init_ui(self):
|
||||
self.setWindowTitle("TASCAM US-144MKII Control Panel")
|
||||
self.setWindowIcon(QIcon(resource_path("icon.ico")))
|
||||
self.setFixedSize(800, 450)
|
||||
self.setFixedSize(820, 450)
|
||||
|
||||
self.setStyleSheet(DARK_STYLESHEET)
|
||||
|
||||
main_layout = QHBoxLayout(self)
|
||||
left_panel, middle_panel, right_panel = QVBoxLayout(), QVBoxLayout(), QVBoxLayout()
|
||||
self.background_label = QLabel(self)
|
||||
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.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.setFont(QFont("Arial", 15, QFont.Weight.Bold))
|
||||
header_layout.addWidget(logo_label)
|
||||
header_layout.addWidget(title_label)
|
||||
|
||||
info_layout = QGridLayout()
|
||||
title_label.setObjectName("Title")
|
||||
left_panel.addWidget(logo_label)
|
||||
left_panel.addWidget(title_label)
|
||||
info_grid.setSpacing(5)
|
||||
self.info_labels = {}
|
||||
info_data = {
|
||||
"Driver Version:": "N/A", "Device:": "US-144 MKII",
|
||||
"Sample Width:": "24 bits", "Sample Rate:": "N/A",
|
||||
"Sample Clock Source:": "internal", "Digital Input Status:": "unavailable"
|
||||
"Driver Version:": "driver_version", "Device:": "device",
|
||||
"Sample Width:": "sample_width", "Sample Rate:": "sample_rate",
|
||||
"Sample Clock Source:": "clock_source", "Digital Input Status:": "digital_status"
|
||||
}
|
||||
for row, (label_text, value_text) in enumerate(info_data.items()):
|
||||
key = label_text.replace(":", "").replace(" ", "_").lower()
|
||||
label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold))
|
||||
value_label = QLabel(value_text, font=QFont("Arial", 10))
|
||||
info_layout.addWidget(label, row, 0, Qt.AlignmentFlag.AlignLeft)
|
||||
info_layout.addWidget(value_label, row, 1, Qt.AlignmentFlag.AlignLeft)
|
||||
row = 0
|
||||
for label_text, key in info_data.items():
|
||||
label = QLabel(label_text)
|
||||
label.setFont(QFont("Arial", 9, QFont.Weight.Bold))
|
||||
value_label = QLabel("N/A")
|
||||
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
|
||||
|
||||
left_panel.addLayout(info_layout)
|
||||
row += 1
|
||||
left_panel.addLayout(info_grid)
|
||||
left_panel.addStretch()
|
||||
|
||||
middle_panel.setSpacing(15)
|
||||
latency_container, self.latency_combo = self.create_control_widget("Audio Performance", ["low latency", "normal latency", "high latency"])
|
||||
mock_container1, _ = self.create_control_widget("Sample Clock Source", ["internal", "auto"])
|
||||
mock_container2, _ = self.create_control_widget("Digital Output Format", ["S/PDIF"])
|
||||
routing_container, self.routing_combo = self.create_control_widget("LINE OUTPUTS", ["Stereo to All", "Swapped", "Digital In to All"])
|
||||
middle_panel = QVBoxLayout()
|
||||
middle_panel.setSpacing(0)
|
||||
|
||||
# --- Latency Setting Re-added ---
|
||||
latency_header = QLabel("AUDIO PERFORMANCE")
|
||||
latency_header.setObjectName("SectionHeader")
|
||||
latency_container, self.latency_combo = self.create_control_widget("Latency Profile", ["low latency", "normal latency", "high latency"])
|
||||
middle_panel.addWidget(latency_header)
|
||||
middle_panel.addWidget(latency_container)
|
||||
middle_panel.addWidget(mock_container1)
|
||||
middle_panel.addWidget(mock_container2)
|
||||
middle_panel.addWidget(routing_container)
|
||||
# --- End Latency Setting Re-added ---
|
||||
|
||||
inputs_header = QLabel("INPUTS")
|
||||
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.addStretch()
|
||||
|
||||
right_panel = QVBoxLayout()
|
||||
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.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
|
@ -181,27 +235,40 @@ class TascamControlPanel(QWidget):
|
|||
right_panel.addStretch()
|
||||
right_panel.addWidget(exit_button, 0, Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
top_level_layout = QVBoxLayout()
|
||||
top_level_layout.addLayout(header_layout)
|
||||
top_level_layout.addSpacing(20)
|
||||
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)
|
||||
top_level_layout.addLayout(left_panel, 3)
|
||||
top_level_layout.addLayout(middle_panel, 3)
|
||||
top_level_layout.addLayout(right_panel, 3)
|
||||
|
||||
self.latency_combo.currentIndexChanged.connect(self.on_latency_changed)
|
||||
self.routing_combo.currentIndexChanged.connect(self.on_routing_changed)
|
||||
# --- Latency Signal Connection Re-added ---
|
||||
self.latency_combo.currentIndexChanged.connect(lambda i: self.set_value("Latency Profile", i))
|
||||
# --- End Latency Signal Connection Re-added ---
|
||||
|
||||
self.line_out_combo.currentIndexChanged.connect(lambda i: self.set_value("Line Out Source", i))
|
||||
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):
|
||||
container_widget = QWidget()
|
||||
layout = QVBoxLayout(container_widget)
|
||||
layout.setContentsMargins(0,0,0,0)
|
||||
layout.setContentsMargins(0, 8, 0, 8)
|
||||
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.addItems(combo_items)
|
||||
layout.addWidget(label)
|
||||
|
|
@ -209,30 +276,32 @@ class TascamControlPanel(QWidget):
|
|||
return container_widget, combo_box
|
||||
|
||||
def load_dynamic_settings(self):
|
||||
driver_ver = AmixerController.read_sysfs_attr(self.card_id, "driver_version")
|
||||
self.info_labels['driver_version'].setText(driver_ver)
|
||||
self.info_labels['driver_version'].setText(AmixerController.read_sysfs_attr(self.card_id, "driver_version"))
|
||||
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")
|
||||
if rate_val > 0:
|
||||
self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz")
|
||||
else:
|
||||
self.info_labels['sample_rate'].setText("N/A (inactive)")
|
||||
self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz" if rate_val > 0 else "N/A (inactive)")
|
||||
|
||||
latency_val = AmixerController.get_control_value(self.card_id, "Latency Profile")
|
||||
self.latency_combo.blockSignals(True)
|
||||
self.latency_combo.setCurrentIndex(latency_val)
|
||||
self.latency_combo.blockSignals(False)
|
||||
# --- Latency Setting Load Re-added ---
|
||||
self.update_combo(self.latency_combo, "Latency Profile")
|
||||
# --- End Latency Setting Load Re-added ---
|
||||
|
||||
routing_val = AmixerController.get_control_value(self.card_id, "Playback Routing")
|
||||
self.routing_combo.blockSignals(True)
|
||||
self.routing_combo.setCurrentIndex(routing_val)
|
||||
self.routing_combo.blockSignals(False)
|
||||
self.update_combo(self.line_out_combo, "Line Out Source")
|
||||
self.update_combo(self.digital_out_combo, "Digital Out Source")
|
||||
self.update_combo(self.capture_12_combo, "Capture 1-2 Source")
|
||||
self.update_combo(self.capture_34_combo, "Capture 3-4 Source")
|
||||
|
||||
def on_latency_changed(self, index):
|
||||
AmixerController.set_control_value(self.card_id, "Latency Profile", index)
|
||||
def update_combo(self, combo, control_name):
|
||||
value = AmixerController.get_control_value(self.card_id, control_name)
|
||||
combo.blockSignals(True)
|
||||
combo.setCurrentIndex(value)
|
||||
combo.blockSignals(False)
|
||||
|
||||
def on_routing_changed(self, index):
|
||||
AmixerController.set_control_value(self.card_id, "Playback Routing", index)
|
||||
def set_value(self, control_name, index):
|
||||
AmixerController.set_control_value(self.card_id, control_name, index)
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
|
|
|
|||
362
us144mkii.c
362
us144mkii.c
|
|
@ -96,6 +96,9 @@ struct tascam_card {
|
|||
struct urb *playback_urbs[NUM_PLAYBACK_URBS];
|
||||
size_t playback_urb_alloc_size;
|
||||
|
||||
struct snd_pcm_substream *capture_substream;
|
||||
atomic_t capture_active;
|
||||
|
||||
struct urb *feedback_urbs[NUM_FEEDBACK_URBS];
|
||||
size_t feedback_urb_alloc_size;
|
||||
|
||||
|
|
@ -103,7 +106,10 @@ struct tascam_card {
|
|||
atomic_t playback_active;
|
||||
int current_rate;
|
||||
unsigned int latency_profile;
|
||||
unsigned int playback_routing;
|
||||
unsigned int line_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */
|
||||
unsigned int digital_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */
|
||||
unsigned int capture_12_source; /* 0: Analog In, 1: Digital In */
|
||||
unsigned int capture_34_source; /* 0: Analog In, 1: Digital In */
|
||||
|
||||
unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
|
||||
unsigned int feedback_pattern_out_idx;
|
||||
|
|
@ -116,6 +122,9 @@ struct tascam_card {
|
|||
snd_pcm_uframes_t driver_playback_pos;
|
||||
u64 last_period_pos;
|
||||
|
||||
u64 capture_frames_produced;
|
||||
u64 last_capture_period_pos;
|
||||
|
||||
const unsigned int (*feedback_patterns)[8];
|
||||
unsigned int feedback_base_value;
|
||||
unsigned int feedback_max_value;
|
||||
|
|
@ -148,102 +157,120 @@ static ssize_t driver_version_show(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RO(driver_version);
|
||||
|
||||
/* --- ALSA Control Definitions --- */
|
||||
/* --- ALSA Control Definitions --- */
|
||||
static const char * const latency_profile_texts[] = {"Low", "Normal", "High"};
|
||||
static const char * const playback_source_texts[] = {"Playback 1-2", "Playback 3-4"};
|
||||
static const char * const capture_source_texts[] = {"Analog In", "Digital In"};
|
||||
|
||||
static int tascam_latency_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item >= 3)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name, latency_profile_texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item >= 3)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name, latency_profile_texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_latency_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol);
|
||||
switch (tascam->latency_profile) {
|
||||
case 1: ucontrol->value.enumerated.item[0] = 0; break;
|
||||
case 2: ucontrol->value.enumerated.item[0] = 1; break;
|
||||
case 5: ucontrol->value.enumerated.item[0] = 2; break;
|
||||
default: ucontrol->value.enumerated.item[0] = 1; break;
|
||||
}
|
||||
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
|
||||
ucontrol->value.enumerated.item[0] = tascam->latency_profile;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_latency_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol);
|
||||
unsigned int new_profile;
|
||||
bool changed = false;
|
||||
|
||||
switch (ucontrol->value.enumerated.item[0]) {
|
||||
case 0: new_profile = 1; break;
|
||||
case 1: new_profile = 2; break;
|
||||
case 2: new_profile = 5; break;
|
||||
default: return -EINVAL;
|
||||
}
|
||||
|
||||
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int new_profile = ucontrol->value.enumerated.item[0];
|
||||
if (new_profile >= 3)
|
||||
return -EINVAL;
|
||||
if (tascam->latency_profile != new_profile) {
|
||||
tascam->latency_profile = new_profile;
|
||||
changed = true;
|
||||
return 1;
|
||||
}
|
||||
return changed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new tascam_latency_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Latency Profile",
|
||||
.info = tascam_latency_info,
|
||||
.get = tascam_latency_get,
|
||||
.put = tascam_latency_put,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Latency Profile",
|
||||
.info = tascam_latency_info, .get = tascam_latency_get, .put = tascam_latency_put,
|
||||
};
|
||||
|
||||
static const char * const playback_routing_texts[] = {"Stereo to All", "Swapped", "Digital In to All"};
|
||||
|
||||
static int tascam_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
static int tascam_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item >= 3)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name, playback_routing_texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item >= 2)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, playback_source_texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
static int tascam_line_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->line_out_source; return 0; }
|
||||
|
||||
static int tascam_line_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL;
|
||||
if (t->line_out_source == u->value.enumerated.item[0]) return 0;
|
||||
t->line_out_source = u->value.enumerated.item[0]; return 1; }
|
||||
|
||||
static const struct snd_kcontrol_new tascam_line_out_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line Out Source",
|
||||
.info = tascam_playback_source_info, .get = tascam_line_out_get, .put = tascam_line_out_put,
|
||||
};
|
||||
|
||||
static int tascam_digital_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->digital_out_source; return 0; }
|
||||
|
||||
static int tascam_digital_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL;
|
||||
if (t->digital_out_source == u->value.enumerated.item[0]) return 0;
|
||||
t->digital_out_source = u->value.enumerated.item[0]; return 1; }
|
||||
|
||||
static const struct snd_kcontrol_new tascam_digital_out_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Out Source",
|
||||
.info = tascam_playback_source_info, .get = tascam_digital_out_get, .put = tascam_digital_out_put,
|
||||
};
|
||||
|
||||
static int tascam_capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol);
|
||||
ucontrol->value.enumerated.item[0] = tascam->playback_routing;
|
||||
return 0;
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item >= 2)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, capture_source_texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_routing_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol);
|
||||
unsigned int new_routing = ucontrol->value.enumerated.item[0];
|
||||
bool changed = false;
|
||||
static int tascam_capture_12_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_12_source; return 0; }
|
||||
|
||||
if (new_routing >= 3)
|
||||
return -EINVAL;
|
||||
static int tascam_capture_12_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL;
|
||||
if (t->capture_12_source == u->value.enumerated.item[0]) return 0;
|
||||
t->capture_12_source = u->value.enumerated.item[0]; return 1; }
|
||||
|
||||
if (tascam->playback_routing != new_routing) {
|
||||
tascam->playback_routing = new_routing;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
static const struct snd_kcontrol_new tascam_capture_12_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 1-2 Source",
|
||||
.info = tascam_capture_source_info, .get = tascam_capture_12_get, .put = tascam_capture_12_put,
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new tascam_routing_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Playback Routing",
|
||||
.info = tascam_routing_info,
|
||||
.get = tascam_routing_get,
|
||||
.put = tascam_routing_put,
|
||||
static int tascam_capture_34_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_34_source; return 0; }
|
||||
|
||||
static int tascam_capture_34_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
|
||||
{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL;
|
||||
if (t->capture_34_source == u->value.enumerated.item[0]) return 0;
|
||||
t->capture_34_source = u->value.enumerated.item[0]; return 1; }
|
||||
|
||||
static const struct snd_kcontrol_new tascam_capture_34_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 3-4 Source",
|
||||
.info = tascam_capture_source_info, .get = tascam_capture_34_get, .put = tascam_capture_34_put,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -598,20 +625,12 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
|
||||
/* Validate and apply latency profile */
|
||||
switch (tascam->latency_profile) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 5:
|
||||
feedback_packets = tascam->latency_profile;
|
||||
break;
|
||||
default:
|
||||
dev_warn(tascam->card->dev, "Invalid latency_profile value %d, falling back to default (2).\n", tascam->latency_profile);
|
||||
tascam->latency_profile = 2;
|
||||
feedback_packets = 2;
|
||||
case 0: feedback_packets = 1; break;
|
||||
case 1: feedback_packets = 2; break;
|
||||
case 2: feedback_packets = 5; break;
|
||||
default: feedback_packets = 2;
|
||||
}
|
||||
|
||||
dev_info(tascam->card->dev, "Prepare: Using latency profile %u (%u feedback packets) for %u Hz\n",
|
||||
tascam->latency_profile, feedback_packets, runtime->rate);
|
||||
|
||||
/* Configure Feedback URBs */
|
||||
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
|
||||
struct urb *f_urb = tascam->feedback_urbs[i];
|
||||
|
|
@ -713,10 +732,95 @@ static struct snd_pcm_ops tascam_playback_ops = {
|
|||
.trigger = tascam_pcm_trigger, .pointer = tascam_pcm_pointer,
|
||||
};
|
||||
|
||||
static int tascam_capture_open_stub(struct snd_pcm_substream *s) { return -ENODEV; }
|
||||
static int tascam_capture_close_stub(struct snd_pcm_substream *s) { return 0; }
|
||||
/* --- Dummy Capture Implementation --- */
|
||||
static int tascam_capture_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
runtime->hw = tascam_pcm_hw;
|
||||
tascam->capture_substream = substream;
|
||||
atomic_set(&tascam->capture_active, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_capture_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||
|
||||
tascam->capture_substream = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
||||
}
|
||||
|
||||
static int tascam_capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int tascam_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
tascam->capture_frames_produced = 0;
|
||||
tascam->last_capture_period_pos = 0;
|
||||
snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
|
||||
runtime->buffer_size * runtime->channels);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
atomic_set(&tascam->capture_active, 1);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
atomic_set(&tascam->capture_active, 0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
u64 pos;
|
||||
unsigned long flags;
|
||||
|
||||
if (!atomic_read(&tascam->capture_active))
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&tascam->lock, flags);
|
||||
pos = tascam->capture_frames_produced;
|
||||
spin_unlock_irqrestore(&tascam->lock, flags);
|
||||
|
||||
return runtime ? pos % runtime->buffer_size : 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops tascam_capture_ops = {
|
||||
.open = tascam_capture_open_stub, .close = tascam_capture_close_stub,
|
||||
.open = tascam_capture_open,
|
||||
.close = tascam_capture_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = tascam_capture_hw_params,
|
||||
.hw_free = tascam_capture_hw_free,
|
||||
.prepare = tascam_capture_prepare,
|
||||
.trigger = tascam_capture_trigger,
|
||||
.pointer = tascam_capture_pointer,
|
||||
};
|
||||
|
||||
static void playback_urb_complete(struct urb *urb)
|
||||
|
|
@ -780,20 +884,20 @@ static void playback_urb_complete(struct urb *urb)
|
|||
char *src_frame = src_buf + frames_to_bytes(runtime, current_frame_pos);
|
||||
char *dst_frame = dst_buf + (f * BYTES_PER_FRAME);
|
||||
|
||||
switch (tascam->playback_routing) {
|
||||
case 0: /* Stereo to All */
|
||||
memcpy(dst_frame, src_frame, 6); /* Copy L/R to Out 1/2 */
|
||||
memcpy(dst_frame + 6, src_frame, 6); /* Copy L/R to Out 3/4 */
|
||||
break;
|
||||
case 1: /* Swapped */
|
||||
memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */
|
||||
memcpy(dst_frame + 6, src_frame, 6); /* Copy 1/2 to Out 3/4 */
|
||||
break;
|
||||
case 2: /* Digital In to All */
|
||||
memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */
|
||||
memcpy(dst_frame + 6, src_frame + 6, 6); /* Copy 3/4 to Out 3/4 */
|
||||
break;
|
||||
}
|
||||
char *src_12 = src_frame;
|
||||
char *src_34 = src_frame + 6;
|
||||
char *dst_line_out = dst_frame;
|
||||
char *dst_digital_out = dst_frame + 6;
|
||||
|
||||
if (tascam->line_out_source == 0)
|
||||
memcpy(dst_line_out, src_12, 6);
|
||||
else
|
||||
memcpy(dst_line_out, src_34, 6);
|
||||
|
||||
if (tascam->digital_out_source == 0)
|
||||
memcpy(dst_digital_out, src_12, 6);
|
||||
else
|
||||
memcpy(dst_digital_out, src_34, 6);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -809,9 +913,11 @@ static void feedback_urb_complete(struct urb *urb)
|
|||
struct snd_pcm_substream *substream;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
unsigned long flags;
|
||||
u64 current_period, total_frames_in_urb = 0;
|
||||
u64 total_frames_in_urb = 0;
|
||||
int ret, p;
|
||||
unsigned int old_in_idx, new_in_idx;
|
||||
bool playback_period_elapsed = false;
|
||||
bool capture_period_elapsed = false;
|
||||
|
||||
if (urb->status)
|
||||
return;
|
||||
|
|
@ -827,7 +933,7 @@ static void feedback_urb_complete(struct urb *urb)
|
|||
/* Hybrid Sync: Initial blind period for hardware to settle. */
|
||||
if (tascam->feedback_urb_skip_count > 0) {
|
||||
tascam->feedback_urb_skip_count--;
|
||||
goto unlock_and_resubmit;
|
||||
goto unlock_and_continue;
|
||||
}
|
||||
|
||||
old_in_idx = tascam->feedback_pattern_in_idx;
|
||||
|
|
@ -886,20 +992,39 @@ static void feedback_urb_complete(struct urb *urb)
|
|||
}
|
||||
}
|
||||
|
||||
if (total_frames_in_urb > 0)
|
||||
if (total_frames_in_urb > 0) {
|
||||
tascam->playback_frames_consumed += total_frames_in_urb;
|
||||
|
||||
current_period = div_u64(tascam->playback_frames_consumed, runtime->period_size);
|
||||
if (current_period > tascam->last_period_pos) {
|
||||
tascam->last_period_pos = current_period;
|
||||
spin_unlock_irqrestore(&tascam->lock, flags);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
goto resubmit;
|
||||
if (atomic_read(&tascam->capture_active))
|
||||
tascam->capture_frames_produced += total_frames_in_urb;
|
||||
}
|
||||
|
||||
unlock_and_resubmit:
|
||||
if (runtime->period_size > 0) {
|
||||
u64 current_period = div_u64(tascam->playback_frames_consumed, runtime->period_size);
|
||||
if (current_period > tascam->last_period_pos) {
|
||||
tascam->last_period_pos = current_period;
|
||||
playback_period_elapsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic_read(&tascam->capture_active) && tascam->capture_substream) {
|
||||
struct snd_pcm_runtime *capture_runtime = tascam->capture_substream->runtime;
|
||||
if (capture_runtime && capture_runtime->period_size > 0) {
|
||||
u64 current_capture_period = div_u64(tascam->capture_frames_produced, capture_runtime->period_size);
|
||||
if (current_capture_period > tascam->last_capture_period_pos) {
|
||||
tascam->last_capture_period_pos = current_capture_period;
|
||||
capture_period_elapsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unlock_and_continue:
|
||||
spin_unlock_irqrestore(&tascam->lock, flags);
|
||||
resubmit:
|
||||
|
||||
if (playback_period_elapsed)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
if (capture_period_elapsed)
|
||||
snd_pcm_period_elapsed(tascam->capture_substream);
|
||||
|
||||
urb->dev = tascam->dev;
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret < 0)
|
||||
|
|
@ -918,12 +1043,15 @@ static int tascam_create_pcm(struct tascam_card *tascam)
|
|||
return err;
|
||||
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_latency_control, tascam));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_routing_control, tascam));
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (err < 0) return err;
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_line_out_control, tascam));
|
||||
if (err < 0) return err;
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_digital_out_control, tascam));
|
||||
if (err < 0) return err;
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_capture_12_control, tascam));
|
||||
if (err < 0) return err;
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_capture_34_control, tascam));
|
||||
if (err < 0) return err;
|
||||
|
||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_samplerate_control, tascam));
|
||||
if (err < 0)
|
||||
|
|
@ -1038,9 +1166,11 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
|
|||
card->private_free = tascam_card_private_free;
|
||||
usb_set_intfdata(intf, tascam);
|
||||
spin_lock_init(&tascam->lock);
|
||||
/* Initialize mixer controls to default values */
|
||||
tascam->latency_profile = 2; /* Default to Normal Latency */
|
||||
tascam->playback_routing = 0; /* Default to Stereo to All */
|
||||
tascam->latency_profile = 1;
|
||||
tascam->line_out_source = 0;
|
||||
tascam->digital_out_source = 1;
|
||||
tascam->capture_12_source = 0;
|
||||
tascam->capture_34_source = 1;
|
||||
tascam->current_rate = 0; /* Not known until hw_params */
|
||||
|
||||
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
|
||||
|
|
|
|||
Loading…
Reference in New Issue