diff --git a/tascam_controls/bg.png b/tascam_controls/bg.png new file mode 100644 index 0000000..5eec530 Binary files /dev/null and b/tascam_controls/bg.png differ diff --git a/tascam_controls/tascam-controls.py b/tascam_controls/tascam-controls.py index 45c6cf3..51aa04c 100644 --- a/tascam_controls/tascam-controls.py +++ b/tascam_controls/tascam-controls.py @@ -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) diff --git a/us144mkii.c b/us144mkii.c index e4cf804..d3b70cf 100644 --- a/us144mkii.c +++ b/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));