diff --git a/.gitignore b/.gitignore index 8cf8ec7..d0f36c6 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ dkms.conf *.cmake /tascam_controls/build /tascam_controls/.tools +raw_jack_us144mkii/jack_tascam diff --git a/raw_jack_us144mkii/Makefile b/raw_jack_us144mkii/Makefile new file mode 100644 index 0000000..2613e66 --- /dev/null +++ b/raw_jack_us144mkii/Makefile @@ -0,0 +1,22 @@ +obj-m += tascam_raw.o + +CC = gcc +CFLAGS = -Wall -O2 +LDFLAGS = -ljack + +KDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: module client + +module: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +client: jack_tascam + +jack_tascam: jack_tascam.c + $(CC) $(CFLAGS) -o jack_tascam jack_tascam.c $(LDFLAGS) + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + rm -f jack_tascam diff --git a/raw_jack_us144mkii/jack_tascam.c b/raw_jack_us144mkii/jack_tascam.c new file mode 100644 index 0000000..c7b8d1e --- /dev/null +++ b/raw_jack_us144mkii/jack_tascam.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tascam_common.h" + +#define DEVICE_NODE "/dev/tascam_raw" +#define MIDI_NODE "/dev/tascam_midi" +#define CHANNELS 4 +#define BYTES_PER_SAMPLE 3 +#define S24_MAX 8388607.0 +#define RAW_BLOCK_SIZE 512 +#define FRAMES_PER_BLOCK 8 + +int fd, midi_fd; +volatile sig_atomic_t keep_running = 1; + +jack_client_t *client_pb; +jack_client_t *client_cap; +jack_client_t *client_midi; + +jack_port_t *pb_ports[CHANNELS]; +jack_port_t *cap_ports[CHANNELS]; +jack_port_t *midi_in_port; +jack_port_t *midi_out_port; + +const char *pb_port_names[] = { + "Analog Left", "Analog Right", + "Digital Left", "Digital Right" +}; + +const char *cap_port_names[] = { + "Analog Left", "Analog Right", + "Digital Left", "Digital Right" +}; + +void signal_handler(int sig) { + keep_running = 0; +} + +void decode_block(const uint8_t *src, float **dst_ch, int offset) { + int frame, bit; + for (frame = 0; frame < FRAMES_PER_BLOCK; ++frame) { + const uint8_t *p_src = src + frame * 64; + int32_t ch[4] = {0}; + for (bit = 0; bit < 24; ++bit) { + uint8_t byte1 = p_src[bit]; + ch[0] = (ch[0] << 1) | (byte1 & 1); + ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1); + } + for (bit = 0; bit < 24; ++bit) { + uint8_t byte2 = p_src[bit + 32]; + ch[1] = (ch[1] << 1) | (byte2 & 1); + ch[3] = (ch[3] << 1) | ((byte2 >> 1) & 1); + } + for(int c=0; c<4; c++) { + int32_t val = ch[c]; + if (val & 0x800000) val |= 0xFF000000; + dst_ch[c][offset + frame] = (float)val / S24_MAX; + } + } +} + +int process_pb(jack_nframes_t nframes, void *arg) { + jack_default_audio_sample_t *pb_in[CHANNELS]; + + for (int i = 0; i < CHANNELS; i++) { + pb_in[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(pb_ports[i], nframes); + } + + size_t pb_bytes = nframes * CHANNELS * BYTES_PER_SAMPLE; + unsigned char *pb_buf = malloc(pb_bytes); + if (!pb_buf) return 0; + + unsigned char *ptr = pb_buf; + for (int i = 0; i < nframes; i++) { + for (int c = 0; c < CHANNELS; c++) { + float s = pb_in[c][i]; + if (s > 1.0f) s = 1.0f; else if (s < -1.0f) s = -1.0f; + int32_t val = (int32_t)(s * S24_MAX); + *ptr++ = (val >> 0) & 0xFF; + *ptr++ = (val >> 8) & 0xFF; + *ptr++ = (val >> 16) & 0xFF; + } + } + + if (write(fd, pb_buf, pb_bytes)) {}; + free(pb_buf); + return 0; +} + +int process_cap(jack_nframes_t nframes, void *arg) { + jack_default_audio_sample_t *cap_out[CHANNELS]; + for (int i = 0; i < CHANNELS; i++) { + cap_out[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(cap_ports[i], nframes); + } + + int blocks_needed = nframes / FRAMES_PER_BLOCK; + if (nframes % FRAMES_PER_BLOCK != 0) blocks_needed++; + + size_t raw_bytes = blocks_needed * RAW_BLOCK_SIZE; + unsigned char *raw_buf = malloc(raw_bytes); + if (!raw_buf) return 0; + + ssize_t read_len = read(fd, raw_buf, raw_bytes); + + float *dst_ptrs[4] = { cap_out[0], cap_out[1], cap_out[2], cap_out[3] }; + if (read_len > 0) { + int blocks_read = read_len / RAW_BLOCK_SIZE; + for(int b=0; b 8) continue; + uint8_t packet[9]; + memcpy(packet, event.buffer, event.size); + if (event.size < 8) memset(packet + event.size, 0xFD, 8 - event.size); + packet[8] = 0xE0; + if (write(midi_fd, packet, 9)) {}; + } + + uint8_t raw_midi[9 * 16]; + ssize_t m_read = read(midi_fd, raw_midi, sizeof(raw_midi)); + if (m_read > 0) { + int packets = m_read / 9; + for(int p=0; p 0) jack_midi_event_write(midi_out_buf, 0, pkt, len); + } + } + return 0; +} + +int main() { + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + fd = open(DEVICE_NODE, O_RDWR); + if (fd < 0) { fprintf(stderr, "Could not open %s\n", DEVICE_NODE); return 1; } + + midi_fd = open(MIDI_NODE, O_RDWR | O_NONBLOCK); + if (midi_fd < 0) { fprintf(stderr, "Could not open %s\n", MIDI_NODE); return 1; } + + client_pb = jack_client_open("TASCAM Output", JackNullOption, NULL); + if (!client_pb) { fprintf(stderr, "Failed to create Playback client\n"); return 1; } + + int rate = jack_get_sample_rate(client_pb); + printf("JACK Sample Rate: %d Hz\n", rate); + if (ioctl(fd, TASCAM_IOC_SET_RATE, &rate) < 0) { perror("IOCTL failed"); return 1; } + + jack_set_process_callback(client_pb, process_pb, NULL); + for (int i = 0; i < CHANNELS; i++) { + pb_ports[i] = jack_port_register(client_pb, pb_port_names[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0); + } + + client_cap = jack_client_open("TASCAM Input", JackNullOption, NULL); + if (!client_cap) { fprintf(stderr, "Failed to create Capture client\n"); return 1; } + + jack_set_process_callback(client_cap, process_cap, NULL); + for (int i = 0; i < CHANNELS; i++) { + cap_ports[i] = jack_port_register(client_cap, cap_port_names[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); + } + + client_midi = jack_client_open("TASCAM MIDI", JackNullOption, NULL); + if (!client_midi) { fprintf(stderr, "Failed to create MIDI client\n"); return 1; } + + jack_set_process_callback(client_midi, process_midi, NULL); + midi_in_port = jack_port_register(client_midi, "MIDI OUT", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput | JackPortIsTerminal, 0); + midi_out_port = jack_port_register(client_midi, "MIDI IN", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); + + if (jack_activate(client_pb)) return 1; + if (jack_activate(client_cap)) return 1; + if (jack_activate(client_midi)) return 1; + + printf("TASCAM Raw JACK Clients Running.\n"); + printf(" [1] TASCAM Output (Playback)\n"); + printf(" [2] TASCAM Input (Capture)\n"); + printf(" [3] TASCAM MIDI (MIDI)\n"); + printf("Running in background. Send SIGINT (Ctrl+C) to stop.\n"); + + while(keep_running) { + sleep(1); + } + + printf("\nStopping...\n"); + jack_client_close(client_pb); + jack_client_close(client_cap); + jack_client_close(client_midi); + close(fd); + close(midi_fd); + return 0; +} diff --git a/raw_jack_us144mkii/start_tascam_raw.sh b/raw_jack_us144mkii/start_tascam_raw.sh new file mode 100755 index 0000000..c36c5c4 --- /dev/null +++ b/raw_jack_us144mkii/start_tascam_raw.sh @@ -0,0 +1,169 @@ +#!/bin/bash + +DRIVER_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +echo "=========================================" +echo " TASCAM RAW DRIVER LAUNCHER" +echo "=========================================" + +echo "Select Sample Rate:" +echo " 1) 44100 Hz" +echo " 2) 48000 Hz (Recommended)" +echo " 3) 88200 Hz" +echo " 4) 96000 Hz" +read -p "Enter choice [1-4]: " rate_choice + +case $rate_choice in + 1) RATE=44100 ;; + 2) RATE=48000 ;; + 3) RATE=88200 ;; + 4) RATE=96000 ;; + *) echo "Invalid choice. Defaulting to 48000."; RATE=48000 ;; +esac + +echo "" +read -p "Enter Buffer Size (frames) [e.g., 64]: " BUFFER +if ! [[ "$BUFFER" =~ ^[0-9]+$ ]]; then + echo "Invalid buffer size. Defaulting to 64." + BUFFER=64 +fi + +LATENCY=$(awk "BEGIN { printf \"%.2f\", (($BUFFER * 2 * 1000 / $RATE) + 1.0) }") +echo "" +echo "-----------------------------------------" +echo " Estimated Playback Latency: ${LATENCY} ms" +echo "-----------------------------------------" +echo "" + +cleanup() { + echo "" + echo "=========================================" + echo " SHUTTING DOWN" + echo "=========================================" + + if [ ! -z "$CLIENT_PID" ]; then + echo "[*] Stopping Client..." + kill $CLIENT_PID 2>/dev/null + wait $CLIENT_PID 2>/dev/null + fi + + if [ ! -z "$A2J_PID" ]; then + echo "[*] Stopping a2jmidid..." + kill $A2J_PID 2>/dev/null + wait $A2J_PID 2>/dev/null + fi + + if [ ! -z "$JACK_PID" ]; then + echo "[*] Stopping JACK..." + kill $JACK_PID 2>/dev/null + wait $JACK_PID 2>/dev/null + fi + + echo "[*] Unloading Raw Driver..." + for i in {1..10}; do + if sudo rmmod tascam_raw 2>/dev/null; then + break + fi + sleep 0.5 + done + + echo "[*] Restoring Standard ALSA Driver..." + sudo modprobe snd_usb_us144mkii 2>/dev/null + + echo "[*] Restarting PipeWire/PulseAudio..." + systemctl --user start pipewire.socket pipewire-pulse.socket wireplumber.service 2>/dev/null + + echo "Done. Desktop audio restored." + exit +} + +trap cleanup SIGINT + +echo "" +echo "--- INITIALIZING ---" + +cd "$DRIVER_DIR" +if [ ! -f "jack_tascam" ] || [ "jack_tascam.c" -nt "jack_tascam" ] || [ ! -f "tascam_raw.ko" ]; then + echo "[1/6] Compiling..." + make clean > /dev/null + make > /dev/null + if [ $? -ne 0 ]; then echo "Error: Compilation failed."; exit 1; fi +fi + +echo "[2/6] Stopping Desktop Audio Services..." +systemctl --user stop pipewire.socket pipewire-pulse.socket wireplumber.service 2>/dev/null +killall -9 jackd 2>/dev/null +killall -9 a2jmidid 2>/dev/null + +echo "[3/6] Loading Kernel Driver..." +sudo rmmod snd_usb_us144mkii 2>/dev/null +sudo rmmod snd_usb_audio 2>/dev/null +sudo rmmod tascam_raw 2>/dev/null + +TASCAM_BUS_ID=$(grep -l "0644" /sys/bus/usb/devices/*/idVendor | xargs grep -l -E "8020|800f" | sed 's|/idProduct||' | xargs -I{} basename {}) +if [ ! -z "$TASCAM_BUS_ID" ]; then + if [ -e "/sys/bus/usb/devices/$TASCAM_BUS_ID:1.0/driver/unbind" ]; then + echo "$TASCAM_BUS_ID:1.0" | sudo tee /sys/bus/usb/devices/$TASCAM_BUS_ID:1.0/driver/unbind > /dev/null + fi + if [ -e "/sys/bus/usb/devices/$TASCAM_BUS_ID:1.1/driver/unbind" ]; then + echo "$TASCAM_BUS_ID:1.1" | sudo tee /sys/bus/usb/devices/$TASCAM_BUS_ID:1.1/driver/unbind > /dev/null + fi +fi + +if ! sudo insmod tascam_raw.ko; then + echo "Error: Failed to insert kernel module." + cleanup +fi + +echo " Waiting for device node..." +for ((i=0; i<50; i++)); do + if [ -e /dev/tascam_raw ]; then break; fi + sleep 0.1 +done + +if [ ! -e /dev/tascam_raw ]; then + echo "Error: /dev/tascam_raw was not created." + sudo dmesg | tail -n 5 + cleanup +fi + +sudo chmod 666 /dev/tascam_raw /dev/tascam_midi 2>/dev/null + +echo "[4/6] Starting JACK ($RATE Hz / $BUFFER frames)..." +jackd -R -d dummy -r $RATE -p $BUFFER >/dev/null 2>&1 & +JACK_PID=$! +sleep 2 + +if ! ps -p $JACK_PID > /dev/null; then + echo "Error: JACK failed to start." + cleanup +fi + +if command -v a2jmidid &> /dev/null; then + echo "[5/6] Starting a2jmidid..." + a2jmidid -e >/dev/null 2>&1 & + A2J_PID=$! +else + echo "[5/6] a2jmidid not found (skipping MIDI bridge)." +fi + +echo "[6/6] Starting TASCAM Bridge Client..." +./jack_tascam & +CLIENT_PID=$! +sleep 1 + +if ! ps -p $CLIENT_PID > /dev/null; then + echo "Error: Client failed to start." + cleanup +fi + +echo "" +echo "=========================================" +echo " SYSTEM LIVE!" +echo " Rate: $RATE Hz | Buffer: $BUFFER" +echo " Estimated Playback Latency: ${LATENCY} ms" +echo " Connect your apps in JACK now." +echo " Press Ctrl+C to stop and restore." +echo "=========================================" + +wait $CLIENT_PID diff --git a/raw_jack_us144mkii/tascam_common.h b/raw_jack_us144mkii/tascam_common.h new file mode 100644 index 0000000..8498035 --- /dev/null +++ b/raw_jack_us144mkii/tascam_common.h @@ -0,0 +1,9 @@ +#ifndef TASCAM_COMMON_H +#define TASCAM_COMMON_H + +#include + +#define TASCAM_IOC_MAGIC 'T' +#define TASCAM_IOC_SET_RATE _IOW(TASCAM_IOC_MAGIC, 1, int) + +#endif diff --git a/raw_jack_us144mkii/tascam_raw.c b/raw_jack_us144mkii/tascam_raw.c new file mode 100644 index 0000000..df56bc8 --- /dev/null +++ b/raw_jack_us144mkii/tascam_raw.c @@ -0,0 +1,636 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tascam_common.h" + +#define DRIVER_NAME "tascam_raw" +#define TASCAM_VID 0x0644 +#define TASCAM_PID_144 0x800f +#define TASCAM_PID_144MKII 0x8020 + +#define EP_AUDIO_OUT 0x02 +#define EP_FEEDBACK_IN 0x81 +#define EP_AUDIO_IN 0x86 +#define EP_MIDI_IN 0x83 +#define EP_MIDI_OUT 0x04 + +#define NUM_CHANNELS 4 +#define BYTES_PER_SAMPLE 3 +#define TASCAM_FRAME_SIZE (NUM_CHANNELS * BYTES_PER_SAMPLE) +#define PACKETS_PER_URB 8 +#define NUM_PLAYBACK_URBS 2 +#define NUM_FEEDBACK_URBS 2 +#define NUM_CAPTURE_URBS 4 +#define CAPTURE_URB_SIZE 512 +#define RING_BUFFER_SIZE (128 * 1024) + +#define NUM_MIDI_IN_URBS 4 +#define NUM_MIDI_OUT_URBS 4 +#define MIDI_PACKET_SIZE 9 +#define MIDI_BUF_SIZE 4096 + +#define FEEDBACK_ACCUMULATOR_SIZE 128 +#define FEEDBACK_SYNC_LOSS_THRESHOLD 41 + +struct us144mkii_frame_pattern_observer { + unsigned int sample_rate_khz; + unsigned int base_feedback_value; + int feedback_offset; + unsigned int full_frame_patterns[5][8]; + unsigned int current_index; + unsigned int previous_index; + bool sync_locked; +}; + +struct tascam_raw { + struct usb_device *udev; + + struct urb *playback_urbs[NUM_PLAYBACK_URBS]; + struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; + struct urb *capture_urbs[NUM_CAPTURE_URBS]; + + struct urb *midi_in_urbs[NUM_MIDI_IN_URBS]; + struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS]; + + int major; + struct cdev cdev; + struct class *cls; + + unsigned char *pb_ring_buf; + unsigned int pb_head; + unsigned int pb_tail; + unsigned char *cap_ring_buf; + unsigned int cap_head; + unsigned int cap_tail; + + unsigned char *midi_in_buf; + unsigned int midi_in_head; + unsigned int midi_in_tail; + + unsigned char *midi_out_buf; + unsigned int midi_out_head; + unsigned int midi_out_tail; + bool midi_out_busy[NUM_MIDI_OUT_URBS]; + + spinlock_t lock; + wait_queue_head_t write_wait; + wait_queue_head_t read_wait; + + bool running; + int current_rate; + + unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; + unsigned int feedback_pattern_out_idx; + unsigned int feedback_pattern_in_idx; + bool feedback_synced; + unsigned int feedback_consecutive_errors; + unsigned int feedback_urb_skip_count; + struct us144mkii_frame_pattern_observer fpo; +}; + +static struct tascam_raw *g_dev = NULL; + +static void fpo_init_pattern(unsigned int size, unsigned int *pattern_array, + unsigned int initial_value, int target_sum) +{ + int diff, i; + if (!size) return; + for (i = 0; i < size; ++i) pattern_array[i] = initial_value; + diff = target_sum - (size * initial_value); + for (i = 0; i < abs(diff); ++i) { + if (diff > 0) pattern_array[i]++; else pattern_array[i]--; + } +} + +static void try_send_midi(struct tascam_raw *dev) { + int i; + unsigned long flags; + spin_lock_irqsave(&dev->lock, flags); + + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (dev->midi_out_busy[i]) continue; + + int available = (dev->midi_out_head - dev->midi_out_tail) & (MIDI_BUF_SIZE - 1); + if (available >= MIDI_PACKET_SIZE) { + int tail = dev->midi_out_tail; + int j; + for(j=0; jmidi_out_urbs[i]->transfer_buffer)[j] = dev->midi_out_buf[(tail + j) & (MIDI_BUF_SIZE - 1)]; + } + dev->midi_out_tail = (tail + MIDI_PACKET_SIZE) & (MIDI_BUF_SIZE - 1); + dev->midi_out_busy[i] = true; + + usb_submit_urb(dev->midi_out_urbs[i], GFP_ATOMIC); + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void midi_out_complete(struct urb *urb) { + struct tascam_raw *dev = urb->context; + int i; + for(i=0; imidi_out_urbs[i] == urb) { + dev->midi_out_busy[i] = false; + break; + } + } + if (dev->running) try_send_midi(dev); +} + +static void midi_in_complete(struct urb *urb) { + struct tascam_raw *dev = urb->context; + unsigned long flags; + if (!dev->running) return; + + if (urb->status == 0 && urb->actual_length > 0) { + spin_lock_irqsave(&dev->lock, flags); + int available = (MIDI_BUF_SIZE - 1) - ((dev->midi_in_head - dev->midi_in_tail) & (MIDI_BUF_SIZE - 1)); + if (available >= urb->actual_length) { + int i; + u8 *src = urb->transfer_buffer; + for(i=0; iactual_length; i++) { + dev->midi_in_buf[dev->midi_in_head] = src[i]; + dev->midi_in_head = (dev->midi_in_head + 1) & (MIDI_BUF_SIZE - 1); + } + } + spin_unlock_irqrestore(&dev->lock, flags); + wake_up_interruptible(&dev->read_wait); + } + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void capture_complete(struct urb *urb) { + struct tascam_raw *dev = urb->context; + unsigned long flags; + if (!dev->running) return; + + if (urb->status == 0 && urb->actual_length > 0) { + spin_lock_irqsave(&dev->lock, flags); + int available = (RING_BUFFER_SIZE - 1) - ((dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1)); + if (available >= urb->actual_length) { + int chunk1 = min_t(int, urb->actual_length, RING_BUFFER_SIZE - dev->cap_head); + memcpy(dev->cap_ring_buf + dev->cap_head, urb->transfer_buffer, chunk1); + if (chunk1 < urb->actual_length) { + memcpy(dev->cap_ring_buf, urb->transfer_buffer + chunk1, urb->actual_length - chunk1); + } + dev->cap_head = (dev->cap_head + urb->actual_length) & (RING_BUFFER_SIZE - 1); + } + spin_unlock_irqrestore(&dev->lock, flags); + wake_up_interruptible(&dev->read_wait); + } + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void feedback_complete(struct urb *urb) { + struct tascam_raw *dev = urb->context; + int p; + if (!dev->running) return; + spin_lock(&dev->lock); + if (dev->feedback_urb_skip_count > 0) { + dev->feedback_urb_skip_count--; + spin_unlock(&dev->lock); + goto resubmit; + } + for (p = 0; p < urb->number_of_packets; p++) { + u8 feedback_value = 0; + const unsigned int *pattern; + bool packet_ok = (urb->iso_frame_desc[p].status == 0 && urb->iso_frame_desc[p].actual_length >= 1); + if (packet_ok) feedback_value = *((u8 *)urb->transfer_buffer + urb->iso_frame_desc[p].offset); + if (packet_ok) { + int delta = feedback_value - dev->fpo.base_feedback_value + dev->fpo.feedback_offset; + int pattern_idx = (delta < 0) ? 0 : (delta >= 5 ? 4 : delta); + pattern = dev->fpo.full_frame_patterns[pattern_idx]; + dev->feedback_consecutive_errors = 0; + int i; + for (i = 0; i < 8; i++) { + unsigned int in_idx = (dev->feedback_pattern_in_idx + i) % FEEDBACK_ACCUMULATOR_SIZE; + dev->feedback_accumulator_pattern[in_idx] = pattern[i]; + } + } else { + unsigned int nominal_frames = dev->current_rate / 8000; + int i; + if (dev->feedback_synced) { + dev->feedback_consecutive_errors++; + if (dev->feedback_consecutive_errors > FEEDBACK_SYNC_LOSS_THRESHOLD) dev->feedback_synced = false; + } + for (i = 0; i < 8; i++) { + unsigned int in_idx = (dev->feedback_pattern_in_idx + i) % FEEDBACK_ACCUMULATOR_SIZE; + dev->feedback_accumulator_pattern[in_idx] = nominal_frames; + } + } + dev->feedback_pattern_in_idx = (dev->feedback_pattern_in_idx + 8) % FEEDBACK_ACCUMULATOR_SIZE; + } + if (!dev->feedback_synced) { + unsigned int out_idx = dev->feedback_pattern_out_idx; + unsigned int new_in_idx = dev->feedback_pattern_in_idx; + bool is_ahead = (new_in_idx - out_idx + FEEDBACK_ACCUMULATOR_SIZE) % FEEDBACK_ACCUMULATOR_SIZE < (FEEDBACK_ACCUMULATOR_SIZE / 2); + if (is_ahead) dev->feedback_synced = true; + } + spin_unlock(&dev->lock); + resubmit: + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void playback_complete(struct urb *urb) { + struct tascam_raw *dev = urb->context; + unsigned long flags; + int i, total_bytes_for_urb = 0; + if (!dev->running) return; + spin_lock_irqsave(&dev->lock, flags); + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int frames_for_packet; + if (dev->feedback_synced) { + frames_for_packet = dev->feedback_accumulator_pattern[dev->feedback_pattern_out_idx]; + dev->feedback_pattern_out_idx = (dev->feedback_pattern_out_idx + 1) % FEEDBACK_ACCUMULATOR_SIZE; + } else { + frames_for_packet = dev->current_rate / 8000; + } + size_t bytes_for_packet = frames_for_packet * TASCAM_FRAME_SIZE; + urb->iso_frame_desc[i].offset = total_bytes_for_urb; + urb->iso_frame_desc[i].length = bytes_for_packet; + total_bytes_for_urb += bytes_for_packet; + } + urb->transfer_buffer_length = total_bytes_for_urb; + uint8_t *dst_buf = urb->transfer_buffer; + int available = (dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1); + if (available >= total_bytes_for_urb) { + int chunk1 = min_t(int, total_bytes_for_urb, RING_BUFFER_SIZE - dev->pb_tail); + memcpy(dst_buf, dev->pb_ring_buf + dev->pb_tail, chunk1); + if (chunk1 < total_bytes_for_urb) memcpy(dst_buf + chunk1, dev->pb_ring_buf, total_bytes_for_urb - chunk1); + dev->pb_tail = (dev->pb_tail + total_bytes_for_urb) & (RING_BUFFER_SIZE - 1); + } else { + memset(dst_buf, 0, total_bytes_for_urb); + } + spin_unlock_irqrestore(&dev->lock, flags); + wake_up_interruptible(&dev->write_wait); + usb_submit_urb(urb, GFP_ATOMIC); +} + +struct rate_config { int rate; u8 payload[3]; u16 reg_value; }; +static const struct rate_config rates[] = { + { 44100, {0x44, 0xac, 0x00}, 0x1000 }, + { 48000, {0x80, 0xbb, 0x00}, 0x1002 }, + { 88200, {0x88, 0x58, 0x01}, 0x1008 }, + { 96000, {0x00, 0x77, 0x01}, 0x100a }, +}; + +static int tascam_start_stream(struct tascam_raw *dev, int rate) { + int i; + const struct rate_config *cfg = NULL; + for (i = 0; i < 4; i++) { if (rates[i].rate == rate) { cfg = &rates[i]; break; } } + if (!cfg) return -EINVAL; + + dev->running = false; + for (i=0; iplayback_urbs[i]); + for (i=0; ifeedback_urbs[i]); + for (i=0; icapture_urbs[i]); + for (i=0; imidi_in_urbs[i]); + for (i=0; imidi_out_urbs[i]); + + dev->current_rate = rate; + dev->pb_head = dev->pb_tail = 0; + dev->cap_head = dev->cap_tail = 0; + dev->midi_in_head = dev->midi_in_tail = 0; + dev->midi_out_head = dev->midi_out_tail = 0; + for(i=0; imidi_out_busy[i] = false; + + dev->fpo.sample_rate_khz = rate / 1000; + dev->fpo.base_feedback_value = dev->fpo.sample_rate_khz; + dev->fpo.feedback_offset = 2; + dev->fpo.current_index = 0; + dev->fpo.previous_index = 0; + dev->fpo.sync_locked = false; + unsigned int initial_value = dev->fpo.sample_rate_khz / 8; + for (i = 0; i < 5; i++) { + int target_sum = dev->fpo.sample_rate_khz - dev->fpo.feedback_offset + i; + fpo_init_pattern(8, dev->fpo.full_frame_patterns[i], initial_value, target_sum); + } + dev->feedback_pattern_in_idx = 0; + dev->feedback_pattern_out_idx = 0; + dev->feedback_synced = false; + dev->feedback_consecutive_errors = 0; + dev->feedback_urb_skip_count = NUM_FEEDBACK_URBS; + unsigned int nominal = rate / 8000; + for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) dev->feedback_accumulator_pattern[i] = nominal; + + usb_set_interface(dev->udev, 0, 1); + usb_set_interface(dev->udev, 1, 1); + + u8 *buf = kmalloc(64, GFP_KERNEL); + usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), 0x49, 0xc0, 0x0000, 0x0000, buf, 1, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x49, 0x40, 0x0010, 0x0000, NULL, 0, 1000); + + u8 *rate_payload = kmemdup(cfg->payload, 3, GFP_KERNEL); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_AUDIO_OUT, rate_payload, 3, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_FEEDBACK_IN, rate_payload, 3, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_AUDIO_IN, rate_payload, 3, 1000); + kfree(rate_payload); + + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0d04, 0x0101, NULL, 0, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0e00, 0x0101, NULL, 0, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0f00, 0x0101, NULL, 0, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, cfg->reg_value, 0x0101, NULL, 0, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x110b, 0x0101, NULL, 0, 1000); + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x49, 0x40, 0x0030, 0x0000, NULL, 0, 1000); + kfree(buf); + + dev->running = true; + + for (i=0; ifeedback_urbs[i]->dev = dev->udev; + dev->feedback_urbs[i]->number_of_packets = 1; + dev->feedback_urbs[i]->transfer_buffer_length = 3; + dev->feedback_urbs[i]->iso_frame_desc[0].offset = 0; + dev->feedback_urbs[i]->iso_frame_desc[0].length = 3; + usb_submit_urb(dev->feedback_urbs[i], GFP_KERNEL); + } + for (i=0; icapture_urbs[i]->dev = dev->udev; + usb_submit_urb(dev->capture_urbs[i], GFP_KERNEL); + } + for (i=0; imidi_in_urbs[i]->dev = dev->udev; + usb_submit_urb(dev->midi_in_urbs[i], GFP_KERNEL); + } + for (i=0; iplayback_urbs[i]->dev = dev->udev; + int j, total = 0; + for(j=0; jplayback_urbs[i]->iso_frame_desc[j].offset = total; + dev->playback_urbs[i]->iso_frame_desc[j].length = len; + total += len; + } + dev->playback_urbs[i]->transfer_buffer_length = total; + memset(dev->playback_urbs[i]->transfer_buffer, 0, total); + usb_submit_urb(dev->playback_urbs[i], GFP_KERNEL); + } + + printk(KERN_INFO "Tascam Raw: Started stream at %d Hz\n", rate); + return 0; +} + +static int dev_open(struct inode *inode, struct file *file) { + if (!g_dev) return -ENODEV; + file->private_data = g_dev; + return 0; +} + +static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct tascam_raw *dev = file->private_data; + int new_rate; + if (iminor(file_inode(file)) != 0) return -ENOTTY; + switch (cmd) { + case TASCAM_IOC_SET_RATE: + if (copy_from_user(&new_rate, (int __user *)arg, sizeof(int))) return -EFAULT; + return tascam_start_stream(dev, new_rate); + default: return -ENOTTY; + } +} + +static ssize_t dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { + struct tascam_raw *dev = file->private_data; + int written = 0; + unsigned long flags; + if (!dev->running) return -EIO; + + int minor = iminor(file_inode(file)); + + if (minor == 1) { + spin_lock_irqsave(&dev->lock, flags); + int available = (MIDI_BUF_SIZE - 1) - ((dev->midi_out_head - dev->midi_out_tail) & (MIDI_BUF_SIZE - 1)); + int to_copy = min_t(int, count, available); + int chunk1 = min_t(int, to_copy, MIDI_BUF_SIZE - dev->midi_out_head); + + if (copy_from_user(dev->midi_out_buf + dev->midi_out_head, user_buf, chunk1)) { + spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT; + } + if (chunk1 < to_copy) { + if (copy_from_user(dev->midi_out_buf, user_buf + chunk1, to_copy - chunk1)) { + spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT; + } + } + dev->midi_out_head = (dev->midi_out_head + to_copy) & (MIDI_BUF_SIZE - 1); + spin_unlock_irqrestore(&dev->lock, flags); + + try_send_midi(dev); + return to_copy; + } + + while (written < count) { + spin_lock_irqsave(&dev->lock, flags); + int available = (RING_BUFFER_SIZE - 1) - ((dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1)); + spin_unlock_irqrestore(&dev->lock, flags); + + if (available == 0) { + if (file->f_flags & O_NONBLOCK) return written > 0 ? written : -EAGAIN; + if (wait_event_interruptible(dev->write_wait, + ((RING_BUFFER_SIZE - 1) - ((dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1))) > 0)) + return -ERESTARTSYS; + continue; + } + + int to_copy = min_t(int, count - written, available); + int chunk1 = min_t(int, to_copy, RING_BUFFER_SIZE - dev->pb_head); + + if (copy_from_user(dev->pb_ring_buf + dev->pb_head, user_buf + written, chunk1)) return -EFAULT; + if (chunk1 < to_copy) { + if (copy_from_user(dev->pb_ring_buf, user_buf + written + chunk1, to_copy - chunk1)) return -EFAULT; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->pb_head = (dev->pb_head + to_copy) & (RING_BUFFER_SIZE - 1); + spin_unlock_irqrestore(&dev->lock, flags); + written += to_copy; + } + return written; +} + +static ssize_t dev_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { + struct tascam_raw *dev = file->private_data; + int read = 0; + unsigned long flags; + if (!dev->running) return -EIO; + + int minor = iminor(file_inode(file)); + + if (minor == 1) { + spin_lock_irqsave(&dev->lock, flags); + int available = (dev->midi_in_head - dev->midi_in_tail) & (MIDI_BUF_SIZE - 1); + if (available == 0) { + spin_unlock_irqrestore(&dev->lock, flags); + if (file->f_flags & O_NONBLOCK) return -EAGAIN; + return 0; + } + int to_copy = min_t(int, count, available); + int chunk1 = min_t(int, to_copy, MIDI_BUF_SIZE - dev->midi_in_tail); + + if (copy_to_user(user_buf, dev->midi_in_buf + dev->midi_in_tail, chunk1)) { + spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT; + } + if (chunk1 < to_copy) { + if (copy_to_user(user_buf + chunk1, dev->midi_in_buf, to_copy - chunk1)) { + spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT; + } + } + dev->midi_in_tail = (dev->midi_in_tail + to_copy) & (MIDI_BUF_SIZE - 1); + spin_unlock_irqrestore(&dev->lock, flags); + return to_copy; + } + + while (read < count) { + spin_lock_irqsave(&dev->lock, flags); + int available = (dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1); + spin_unlock_irqrestore(&dev->lock, flags); + + if (available == 0) { + if (file->f_flags & O_NONBLOCK) return read > 0 ? read : -EAGAIN; + if (wait_event_interruptible(dev->read_wait, + ((dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1)) > 0)) + return -ERESTARTSYS; + continue; + } + + int to_copy = min_t(int, count - read, available); + int chunk1 = min_t(int, to_copy, RING_BUFFER_SIZE - dev->cap_tail); + + if (copy_to_user(user_buf + read, dev->cap_ring_buf + dev->cap_tail, chunk1)) return -EFAULT; + if (chunk1 < to_copy) { + if (copy_to_user(user_buf + read + chunk1, dev->cap_ring_buf, to_copy - chunk1)) return -EFAULT; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->cap_tail = (dev->cap_tail + to_copy) & (RING_BUFFER_SIZE - 1); + spin_unlock_irqrestore(&dev->lock, flags); + read += to_copy; + } + return read; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = dev_open, + .write = dev_write, + .read = dev_read, + .unlocked_ioctl = dev_ioctl, +}; + +static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *id) { + struct tascam_raw *dev; + int ret, i; + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) return -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) return -ENOMEM; + + dev->udev = interface_to_usbdev(intf); + dev->pb_ring_buf = kzalloc(RING_BUFFER_SIZE, GFP_KERNEL); + dev->cap_ring_buf = kzalloc(RING_BUFFER_SIZE, GFP_KERNEL); + dev->midi_in_buf = kzalloc(MIDI_BUF_SIZE, GFP_KERNEL); + dev->midi_out_buf = kzalloc(MIDI_BUF_SIZE, GFP_KERNEL); + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->write_wait); + init_waitqueue_head(&dev->read_wait); + + g_dev = dev; + + dev->major = register_chrdev(0, DRIVER_NAME, &fops); + if (dev->major < 0) goto err_free; + + dev->cls = class_create(DRIVER_NAME); + device_create(dev->cls, NULL, MKDEV(dev->major, 0), NULL, DRIVER_NAME); + device_create(dev->cls, NULL, MKDEV(dev->major, 1), NULL, "tascam_midi"); + + for (i=0; ifeedback_urbs[i] = usb_alloc_urb(1, GFP_KERNEL); + usb_fill_int_urb(dev->feedback_urbs[i], dev->udev, usb_rcvintpipe(dev->udev, EP_FEEDBACK_IN), + kzalloc(4, GFP_KERNEL), 4, feedback_complete, dev, 1); + } + for (i=0; iplayback_urbs[i] = usb_alloc_urb(PACKETS_PER_URB, GFP_KERNEL); + dev->playback_urbs[i]->dev = dev->udev; + dev->playback_urbs[i]->context = dev; + dev->playback_urbs[i]->pipe = usb_sndisocpipe(dev->udev, EP_AUDIO_OUT); + dev->playback_urbs[i]->transfer_flags = URB_ISO_ASAP; + dev->playback_urbs[i]->interval = 1; + dev->playback_urbs[i]->complete = playback_complete; + dev->playback_urbs[i]->number_of_packets = PACKETS_PER_URB; + dev->playback_urbs[i]->transfer_buffer = kzalloc(PACKETS_PER_URB * 144, GFP_KERNEL); + dev->playback_urbs[i]->transfer_buffer_length = PACKETS_PER_URB * 144; + } + for (i=0; icapture_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + dev->capture_urbs[i]->transfer_buffer = kzalloc(CAPTURE_URB_SIZE, GFP_KERNEL); + usb_fill_bulk_urb(dev->capture_urbs[i], dev->udev, usb_rcvbulkpipe(dev->udev, EP_AUDIO_IN), + dev->capture_urbs[i]->transfer_buffer, CAPTURE_URB_SIZE, capture_complete, dev); + } + for (i=0; imidi_in_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + dev->midi_in_urbs[i]->transfer_buffer = kzalloc(MIDI_PACKET_SIZE, GFP_KERNEL); + usb_fill_bulk_urb(dev->midi_in_urbs[i], dev->udev, usb_rcvbulkpipe(dev->udev, EP_MIDI_IN), + dev->midi_in_urbs[i]->transfer_buffer, MIDI_PACKET_SIZE, midi_in_complete, dev); + } + for (i=0; imidi_out_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + dev->midi_out_urbs[i]->transfer_buffer = kzalloc(MIDI_PACKET_SIZE, GFP_KERNEL); + usb_fill_bulk_urb(dev->midi_out_urbs[i], dev->udev, usb_sndbulkpipe(dev->udev, EP_MIDI_OUT), + dev->midi_out_urbs[i]->transfer_buffer, MIDI_PACKET_SIZE, midi_out_complete, dev); + } + + printk(KERN_INFO "Tascam Raw: Probed. Waiting for IOCTL.\n"); + return 0; + + err_free: + kfree(dev->pb_ring_buf); + kfree(dev->cap_ring_buf); + kfree(dev->midi_in_buf); + kfree(dev->midi_out_buf); + kfree(dev); + return ret; +} + +static void tascam_disconnect(struct usb_interface *intf) { + struct tascam_raw *dev = g_dev; + int i; + if (!dev) return; + + dev->running = false; + for (i=0; iplayback_urbs[i]); usb_free_urb(dev->playback_urbs[i]); } + for (i=0; ifeedback_urbs[i]); usb_free_urb(dev->feedback_urbs[i]); } + for (i=0; icapture_urbs[i]); usb_free_urb(dev->capture_urbs[i]); } + for (i=0; imidi_in_urbs[i]); usb_free_urb(dev->midi_in_urbs[i]); } + for (i=0; imidi_out_urbs[i]); usb_free_urb(dev->midi_out_urbs[i]); } + + device_destroy(dev->cls, MKDEV(dev->major, 0)); + device_destroy(dev->cls, MKDEV(dev->major, 1)); + class_destroy(dev->cls); + unregister_chrdev(dev->major, DRIVER_NAME); + + kfree(dev->pb_ring_buf); + kfree(dev->cap_ring_buf); + kfree(dev->midi_in_buf); + kfree(dev->midi_out_buf); + kfree(dev); + g_dev = NULL; +} + +static struct usb_device_id tascam_table[] = { + { USB_DEVICE(TASCAM_VID, TASCAM_PID_144) }, + { USB_DEVICE(TASCAM_VID, TASCAM_PID_144MKII) }, + { } +}; +MODULE_DEVICE_TABLE(usb, tascam_table); +static struct usb_driver tascam_driver = { .name = DRIVER_NAME, .probe = tascam_probe, .disconnect = tascam_disconnect, .id_table = tascam_table }; +module_usb_driver(tascam_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TASCAM US-144/US-144MKII Raw Driver (Audio + MIDI)");