dummy capture & playback routing

This commit is contained in:
serifpersia 2025-07-16 22:27:11 +02:00
parent 5b572c0df2
commit 8259275c9e
3 changed files with 406 additions and 207 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,96 @@ 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"])
routing_container, self.routing_combo = self.create_control_widget("LINE OUTPUTS", ["Stereo to All", "Swapped", "Digital In to All"])
# --- 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(latency_container)
middle_panel.addWidget(mock_container1) # --- End Latency Setting Re-added ---
middle_panel.addWidget(mock_container2)
middle_panel.addWidget(routing_container) 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() 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 +235,40 @@ 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) # --- Latency Signal Connection Re-added ---
self.routing_combo.currentIndexChanged.connect(self.on_routing_changed) 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): 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 +276,32 @@ 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") # --- Latency Setting Load Re-added ---
self.latency_combo.blockSignals(True) self.update_combo(self.latency_combo, "Latency Profile")
self.latency_combo.setCurrentIndex(latency_val) # --- End Latency Setting Load Re-added ---
self.latency_combo.blockSignals(False)
routing_val = AmixerController.get_control_value(self.card_id, "Playback Routing") self.update_combo(self.line_out_combo, "Line Out Source")
self.routing_combo.blockSignals(True) self.update_combo(self.digital_out_combo, "Digital Out Source")
self.routing_combo.setCurrentIndex(routing_val) self.update_combo(self.capture_12_combo, "Capture 1-2 Source")
self.routing_combo.blockSignals(False) self.update_combo(self.capture_34_combo, "Capture 3-4 Source")
def on_latency_changed(self, index): def update_combo(self, combo, control_name):
AmixerController.set_control_value(self.card_id, "Latency Profile", index) 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): def set_value(self, control_name, index):
AmixerController.set_control_value(self.card_id, "Playback Routing", index) AmixerController.set_control_value(self.card_id, control_name, index)
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)

View File

@ -96,6 +96,9 @@ struct tascam_card {
struct urb *playback_urbs[NUM_PLAYBACK_URBS]; struct urb *playback_urbs[NUM_PLAYBACK_URBS];
size_t playback_urb_alloc_size; size_t playback_urb_alloc_size;
struct snd_pcm_substream *capture_substream;
atomic_t capture_active;
struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; struct urb *feedback_urbs[NUM_FEEDBACK_URBS];
size_t feedback_urb_alloc_size; size_t feedback_urb_alloc_size;
@ -103,7 +106,10 @@ struct tascam_card {
atomic_t playback_active; atomic_t playback_active;
int current_rate; int current_rate;
unsigned int latency_profile; 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_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
unsigned int feedback_pattern_out_idx; unsigned int feedback_pattern_out_idx;
@ -116,6 +122,9 @@ struct tascam_card {
snd_pcm_uframes_t driver_playback_pos; snd_pcm_uframes_t driver_playback_pos;
u64 last_period_pos; u64 last_period_pos;
u64 capture_frames_produced;
u64 last_capture_period_pos;
const unsigned int (*feedback_patterns)[8]; const unsigned int (*feedback_patterns)[8];
unsigned int feedback_base_value; unsigned int feedback_base_value;
unsigned int feedback_max_value; unsigned int feedback_max_value;
@ -148,8 +157,11 @@ static ssize_t driver_version_show(struct device *dev,
} }
static DEVICE_ATTR_RO(driver_version); static DEVICE_ATTR_RO(driver_version);
/* --- ALSA Control Definitions --- */
/* --- ALSA Control Definitions --- */ /* --- ALSA Control Definitions --- */
static const char * const latency_profile_texts[] = {"Low", "Normal", "High"}; 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) static int tascam_latency_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{ {
@ -164,86 +176,101 @@ static int tascam_latency_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ele
static int tascam_latency_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 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); struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
switch (tascam->latency_profile) { ucontrol->value.enumerated.item[0] = 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;
}
return 0; return 0;
} }
static int tascam_latency_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 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); struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
unsigned int new_profile; unsigned int new_profile = ucontrol->value.enumerated.item[0];
bool changed = false; if (new_profile >= 3)
return -EINVAL;
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;
}
if (tascam->latency_profile != new_profile) { if (tascam->latency_profile != new_profile) {
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 = { static const struct snd_kcontrol_new tascam_latency_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Latency Profile",
.name = "Latency Profile", .info = tascam_latency_info, .get = tascam_latency_get, .put = tascam_latency_put,
.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_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
static int tascam_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{ {
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1; uinfo->count = 1;
uinfo->value.enumerated.items = 3; uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item >= 3) if (uinfo->value.enumerated.item >= 2)
uinfo->value.enumerated.item = 2; uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name, playback_routing_texts[uinfo->value.enumerated.item]); strcpy(uinfo->value.enumerated.name, playback_source_texts[uinfo->value.enumerated.item]);
return 0; 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); uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
ucontrol->value.enumerated.item[0] = tascam->playback_routing; 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; return 0;
} }
static int tascam_routing_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 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; }
struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol);
unsigned int new_routing = ucontrol->value.enumerated.item[0];
bool changed = false;
if (new_routing >= 3) static int tascam_capture_12_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
return -EINVAL; { 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) { static const struct snd_kcontrol_new tascam_capture_12_control = {
tascam->playback_routing = new_routing; .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 1-2 Source",
changed = true; .info = tascam_capture_source_info, .get = tascam_capture_12_get, .put = tascam_capture_12_put,
} };
return changed;
}
static const struct snd_kcontrol_new tascam_routing_control = { static int tascam_capture_34_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, { u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_34_source; return 0; }
.name = "Playback Routing",
.info = tascam_routing_info, static int tascam_capture_34_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u)
.get = tascam_routing_get, { struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL;
.put = tascam_routing_put, 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 */ /* Validate and apply latency profile */
switch (tascam->latency_profile) { switch (tascam->latency_profile) {
case 1: case 0: feedback_packets = 1; break;
case 2: case 1: feedback_packets = 2; break;
case 5: case 2: feedback_packets = 5; break;
feedback_packets = tascam->latency_profile; default: feedback_packets = 2;
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;
} }
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 */ /* Configure Feedback URBs */
for (i = 0; i < NUM_FEEDBACK_URBS; i++) { for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->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, .trigger = tascam_pcm_trigger, .pointer = tascam_pcm_pointer,
}; };
static int tascam_capture_open_stub(struct snd_pcm_substream *s) { return -ENODEV; } /* --- Dummy Capture Implementation --- */
static int tascam_capture_close_stub(struct snd_pcm_substream *s) { return 0; } 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 = { 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) 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 *src_frame = src_buf + frames_to_bytes(runtime, current_frame_pos);
char *dst_frame = dst_buf + (f * BYTES_PER_FRAME); char *dst_frame = dst_buf + (f * BYTES_PER_FRAME);
switch (tascam->playback_routing) { char *src_12 = src_frame;
case 0: /* Stereo to All */ char *src_34 = src_frame + 6;
memcpy(dst_frame, src_frame, 6); /* Copy L/R to Out 1/2 */ char *dst_line_out = dst_frame;
memcpy(dst_frame + 6, src_frame, 6); /* Copy L/R to Out 3/4 */ char *dst_digital_out = dst_frame + 6;
break;
case 1: /* Swapped */ if (tascam->line_out_source == 0)
memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */ memcpy(dst_line_out, src_12, 6);
memcpy(dst_frame + 6, src_frame, 6); /* Copy 1/2 to Out 3/4 */ else
break; memcpy(dst_line_out, src_34, 6);
case 2: /* Digital In to All */
memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */ if (tascam->digital_out_source == 0)
memcpy(dst_frame + 6, src_frame + 6, 6); /* Copy 3/4 to Out 3/4 */ memcpy(dst_digital_out, src_12, 6);
break; 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_substream *substream;
struct snd_pcm_runtime *runtime; struct snd_pcm_runtime *runtime;
unsigned long flags; unsigned long flags;
u64 current_period, total_frames_in_urb = 0; u64 total_frames_in_urb = 0;
int ret, p; int ret, p;
unsigned int old_in_idx, new_in_idx; unsigned int old_in_idx, new_in_idx;
bool playback_period_elapsed = false;
bool capture_period_elapsed = false;
if (urb->status) if (urb->status)
return; return;
@ -827,7 +933,7 @@ static void feedback_urb_complete(struct urb *urb)
/* Hybrid Sync: Initial blind period for hardware to settle. */ /* Hybrid Sync: Initial blind period for hardware to settle. */
if (tascam->feedback_urb_skip_count > 0) { if (tascam->feedback_urb_skip_count > 0) {
tascam->feedback_urb_skip_count--; tascam->feedback_urb_skip_count--;
goto unlock_and_resubmit; goto unlock_and_continue;
} }
old_in_idx = tascam->feedback_pattern_in_idx; 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; tascam->playback_frames_consumed += total_frames_in_urb;
if (atomic_read(&tascam->capture_active))
current_period = div_u64(tascam->playback_frames_consumed, runtime->period_size); tascam->capture_frames_produced += total_frames_in_urb;
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;
} }
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); 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; urb->dev = tascam->dev;
ret = usb_submit_urb(urb, GFP_ATOMIC); ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) if (ret < 0)
@ -918,12 +1043,15 @@ static int tascam_create_pcm(struct tascam_card *tascam)
return err; return err;
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_latency_control, tascam)); err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_latency_control, tascam));
if (err < 0) if (err < 0) return err;
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_routing_control, tascam)); err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_digital_out_control, tascam));
if (err < 0) if (err < 0) return err;
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)); err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_samplerate_control, tascam));
if (err < 0) 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; card->private_free = tascam_card_private_free;
usb_set_intfdata(intf, tascam); usb_set_intfdata(intf, tascam);
spin_lock_init(&tascam->lock); spin_lock_init(&tascam->lock);
/* Initialize mixer controls to default values */ tascam->latency_profile = 1;
tascam->latency_profile = 2; /* Default to Normal Latency */ tascam->line_out_source = 0;
tascam->playback_routing = 0; /* Default to Stereo to All */ tascam->digital_out_source = 1;
tascam->capture_12_source = 0;
tascam->capture_34_source = 1;
tascam->current_rate = 0; /* Not known until hw_params */ tascam->current_rate = 0; /* Not known until hw_params */
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));