/* Copyright (C) 2001 Paul Davis Copyright (C) 2005 Karsten Wiese, Rui Nuno Capela This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "hardware.h" #include "alsa_driver.h" #include "usx2y.h" #include #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #endif //#define DBGHWDEP #ifdef DBGHWDEP int dbg_offset; char dbg_buffer[8096]; #endif static int usx2y_set_input_monitor_mask (jack_hardware_t *hw, unsigned long mask) { return -1; } static int usx2y_change_sample_clock (jack_hardware_t *hw, SampleClockMode mode) { return -1; } static void usx2y_release (jack_hardware_t *hw) { usx2y_t *h = (usx2y_t *) hw->private_hw; if (h == 0) return; if (h->hwdep_handle) snd_hwdep_close(h->hwdep_handle); free(h); } static int usx2y_driver_get_channel_addresses_playback (alsa_driver_t *driver, snd_pcm_uframes_t *playback_avail) { channel_t chn; int iso; snd_pcm_uframes_t playback_iso_avail; char *playback; usx2y_t *h = (usx2y_t *) driver->hw->private_hw; if (0 > h->playback_iso_start) { int bytes = driver->playback_sample_bytes * 2 * driver->frames_per_cycle * driver->user_nperiods; iso = h->hwdep_pcm_shm->playback_iso_start; if (0 > iso) return 0; /* FIXME: return -1; */ if (++iso >= ARRAY_SIZE(h->hwdep_pcm_shm->captured_iso)) iso = 0; while((bytes -= h->hwdep_pcm_shm->captured_iso[iso].length) > 0) if (++iso >= ARRAY_SIZE(h->hwdep_pcm_shm->captured_iso)) iso = 0; h->playback_iso_bytes_done = h->hwdep_pcm_shm->captured_iso[iso].length + bytes; #ifdef DBGHWDEP dbg_offset = sprintf(dbg_buffer, "first iso = %i %i@%p:%i\n", iso, h->hwdep_pcm_shm->captured_iso[iso].length, h->hwdep_pcm_shm->playback, h->hwdep_pcm_shm->captured_iso[iso].offset); #endif } else { iso = h->playback_iso_start; } #ifdef DBGHWDEP dbg_offset += sprintf(dbg_buffer + dbg_offset, "iso = %i(%i;%i); ", iso, h->hwdep_pcm_shm->captured_iso[iso].offset, h->hwdep_pcm_shm->captured_iso[iso].frame); #endif playback = h->hwdep_pcm_shm->playback + h->hwdep_pcm_shm->captured_iso[iso].offset + h->playback_iso_bytes_done; playback_iso_avail = (h->hwdep_pcm_shm->captured_iso[iso].length - h->playback_iso_bytes_done) / (driver->playback_sample_bytes * 2); if (*playback_avail >= playback_iso_avail) { *playback_avail = playback_iso_avail; if (++iso >= ARRAY_SIZE(h->hwdep_pcm_shm->captured_iso)) iso = 0; h->playback_iso_bytes_done = 0; } else h->playback_iso_bytes_done = *playback_avail * (driver->playback_sample_bytes * 2); h->playback_iso_start = iso; for (chn = 0; chn < driver->playback_nchannels; chn++) { const snd_pcm_channel_area_t *a = &driver->playback_areas[chn]; driver->playback_addr[chn] = playback + a->first / 8; } #ifdef DBGHWDEP if (dbg_offset < (sizeof(dbg_buffer) - 256)) dbg_offset += sprintf(dbg_buffer + dbg_offset, "avail %li@%p\n", *playback_avail, driver->playback_addr[0]); else { printf(dbg_buffer); return -1; } #endif return 0; } static int usx2y_driver_get_channel_addresses_capture (alsa_driver_t *driver, snd_pcm_uframes_t *capture_avail) { channel_t chn; int iso; snd_pcm_uframes_t capture_iso_avail; int capture_offset; usx2y_t *h = (usx2y_t *) driver->hw->private_hw; if (0 > h->capture_iso_start) { iso = h->hwdep_pcm_shm->capture_iso_start; if (0 > iso) return 0; /* FIXME: return -1; */ h->capture_iso_bytes_done = 0; #ifdef DBGHWDEP dbg_offset = sprintf(dbg_buffer, "cfirst iso = %i %i@%p:%i\n", iso, h->hwdep_pcm_shm->captured_iso[iso].length, h->hwdep_pcm_shm->capture0x8, h->hwdep_pcm_shm->captured_iso[iso].offset); #endif } else { iso = h->capture_iso_start; } #ifdef DBGHWDEP dbg_offset += sprintf(dbg_buffer + dbg_offset, "ciso = %i(%i;%i); ", iso, h->hwdep_pcm_shm->captured_iso[iso].offset, h->hwdep_pcm_shm->captured_iso[iso].frame); #endif capture_offset = h->hwdep_pcm_shm->captured_iso[iso].offset + h->capture_iso_bytes_done; capture_iso_avail = (h->hwdep_pcm_shm->captured_iso[iso].length - h->capture_iso_bytes_done) / (driver->capture_sample_bytes * 2); if (*capture_avail >= capture_iso_avail) { *capture_avail = capture_iso_avail; if (++iso >= ARRAY_SIZE(h->hwdep_pcm_shm->captured_iso)) iso = 0; h->capture_iso_bytes_done = 0; } else h->capture_iso_bytes_done = *capture_avail * (driver->capture_sample_bytes * 2); h->capture_iso_start = iso; for (chn = 0; chn < driver->capture_nchannels; chn++) { driver->capture_addr[chn] = (chn < 2 ? h->hwdep_pcm_shm->capture0x8 : h->hwdep_pcm_shm->capture0xA) + capture_offset + ((chn & 1) ? driver->capture_sample_bytes : 0); } #ifdef DBGHWDEP { int f = 0; unsigned *u = driver->capture_addr[0]; static unsigned last; dbg_offset += sprintf(dbg_buffer + dbg_offset, "\nvon %6u bis %6u\n", last, u[0]); while (f < *capture_avail && dbg_offset < (sizeof(dbg_buffer) - 256)) { if (u[f] != last + 1) dbg_offset += sprintf(dbg_buffer + dbg_offset, "\nooops %6u %6u\n", last, u[f]); last = u[f++]; } } if (dbg_offset < (sizeof(dbg_buffer) - 256)) dbg_offset += sprintf(dbg_buffer + dbg_offset, "avail %li@%p\n", *capture_avail, driver->capture_addr[0]); else { printf(dbg_buffer); return -1; } #endif return 0; } static int usx2y_driver_start (alsa_driver_t *driver) { int err, i; snd_pcm_uframes_t poffset, pavail; usx2y_t *h = (usx2y_t *) driver->hw->private_hw; for (i = 0; i < driver->capture_nchannels; i++) // US428 channels 3+4 are on a separate 2 channel stream. // ALSA thinks its 1 stream with 4 channels. driver->capture_interleave_skip[i] = 2 * driver->capture_sample_bytes; driver->playback_interleave_skip[0] = 2 * driver->playback_sample_bytes; driver->playback_interleave_skip[1] = 2 * driver->playback_sample_bytes; driver->poll_last = 0; driver->poll_next = 0; if ((err = snd_pcm_prepare (driver->playback_handle)) < 0) { jack_error ("ALSA/USX2Y: prepare error for playback: %s", snd_strerror(err)); return -1; } if (driver->midi && !driver->xrun_recovery) (driver->midi->start)(driver->midi); if (driver->playback_handle) { /* int i, j; */ /* char buffer[2000]; */ h->playback_iso_start = h->capture_iso_start = -1; snd_hwdep_poll_descriptors(h->hwdep_handle, &h->pfds, 1); h->hwdep_pcm_shm = (snd_usX2Y_hwdep_pcm_shm_t*) mmap(NULL, sizeof(snd_usX2Y_hwdep_pcm_shm_t), PROT_READ, MAP_SHARED, h->pfds.fd, 0); if (MAP_FAILED == h->hwdep_pcm_shm) { perror("ALSA/USX2Y: mmap"); return -1; } if (mprotect(h->hwdep_pcm_shm->playback, sizeof(h->hwdep_pcm_shm->playback), PROT_READ|PROT_WRITE)) { perror("ALSA/USX2Y: mprotect"); return -1; } memset(h->hwdep_pcm_shm->playback, 0, sizeof(h->hwdep_pcm_shm->playback)); /* for (i = 0, j = 0; i < 2000;) { */ /* j += sprintf(buffer + j, "%04hX ", */ /* *(unsigned short*)(h->hwdep_pcm_shm->capture + i)); */ /* if (((i += 2) % 32) == 0) { */ /* jack_error(buffer); */ /* j = 0; */ /* } */ /* } */ } if (driver->hw_monitoring) { driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); } if (driver->playback_handle) { /* fill playback buffer with zeroes, and mark all fragments as having data. */ pavail = snd_pcm_avail_update (driver->playback_handle); if (pavail != driver->frames_per_cycle * driver->playback_nperiods) { jack_error ("ALSA/USX2Y: full buffer not available at start"); return -1; } if (snd_pcm_mmap_begin( driver->playback_handle, &driver->playback_areas, &poffset, &pavail) < 0) { return -1; } /* XXX this is cheating. ALSA offers no guarantee that we can access the entire buffer at any one time. It works on most hardware tested so far, however, buts its a liability in the long run. I think that alsa-lib may have a better function for doing this here, where the goal is to silence the entire buffer. */ { /* snd_pcm_uframes_t frag, nframes = driver->buffer_frames; */ /* while (nframes) { */ /* frag = nframes; */ /* if (usx2y_driver_get_channel_addresses_playback(driver, &frag) < 0) */ /* return -1; */ /* for (chn = 0; chn < driver->playback_nchannels; chn++) */ /* alsa_driver_silence_on_channel (driver, chn, frag); */ /* nframes -= frag; */ /* } */ } snd_pcm_mmap_commit (driver->playback_handle, poffset, driver->user_nperiods * driver->frames_per_cycle); if ((err = snd_pcm_start (driver->playback_handle)) < 0) { jack_error ("ALSA/USX2Y: could not start playback (%s)", snd_strerror (err)); return -1; } } if (driver->hw_monitoring && (driver->input_monitor_mask || driver->all_monitor_in)) { if (driver->all_monitor_in) { driver->hw->set_input_monitor_mask (driver->hw, ~0U); } else { driver->hw->set_input_monitor_mask ( driver->hw, driver->input_monitor_mask); } } driver->playback_nfds = snd_pcm_poll_descriptors_count (driver->playback_handle); driver->capture_nfds = snd_pcm_poll_descriptors_count (driver->capture_handle); if (driver->pfd) { free (driver->pfd); } driver->pfd = (struct pollfd *) malloc (sizeof (struct pollfd) * (driver->playback_nfds + driver->capture_nfds + 2)); return 0; } static int usx2y_driver_stop (alsa_driver_t *driver) { int err; JSList* node; int chn; usx2y_t *h = (usx2y_t *) driver->hw->private_hw; /* silence all capture port buffers, because we might be entering offline mode. */ for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) { jack_port_t* port; char* buf; jack_nframes_t nframes = driver->engine->control->buffer_size; port = (jack_port_t *) node->data; buf = jack_port_get_buffer (port, nframes); memset (buf, 0, sizeof (jack_default_audio_sample_t) * nframes); } if (driver->playback_handle) { if ((err = snd_pcm_drop (driver->playback_handle)) < 0) { jack_error ("ALSA/USX2Y: channel flush for playback " "failed (%s)", snd_strerror (err)); return -1; } } if (driver->hw_monitoring) { driver->hw->set_input_monitor_mask (driver->hw, 0); } munmap(h->hwdep_pcm_shm, sizeof(snd_usX2Y_hwdep_pcm_shm_t)); if (driver->midi && !driver->xrun_recovery) (driver->midi->stop)(driver->midi); return 0; } static int usx2y_driver_null_cycle (alsa_driver_t* driver, jack_nframes_t nframes) { jack_nframes_t nf; snd_pcm_uframes_t offset; snd_pcm_uframes_t contiguous, contiguous_; int chn; VERBOSE(driver->engine, "usx2y_driver_null_cycle (%p, %i)", driver, nframes); if (driver->capture_handle) { nf = nframes; offset = 0; while (nf) { contiguous = (nf > driver->frames_per_cycle) ? driver->frames_per_cycle : nf; if (snd_pcm_mmap_begin ( driver->capture_handle, &driver->capture_areas, (snd_pcm_uframes_t *) &offset, (snd_pcm_uframes_t *) &contiguous)) { return -1; } contiguous_ = contiguous; while (contiguous_) { snd_pcm_uframes_t frag = contiguous_; if (usx2y_driver_get_channel_addresses_capture(driver, &frag) < 0) return -1; contiguous_ -= frag; } if (snd_pcm_mmap_commit (driver->capture_handle, offset, contiguous) < 0) { return -1; } nf -= contiguous; } } if (driver->playback_handle) { nf = nframes; offset = 0; while (nf) { contiguous = (nf > driver->frames_per_cycle) ? driver->frames_per_cycle : nf; if (snd_pcm_mmap_begin ( driver->playback_handle, &driver->playback_areas, (snd_pcm_uframes_t *) &offset, (snd_pcm_uframes_t *) &contiguous)) { return -1; } { snd_pcm_uframes_t frag, nframes = contiguous; while (nframes) { frag = nframes; if (usx2y_driver_get_channel_addresses_playback(driver, &frag) < 0) return -1; for (chn = 0; chn < driver->playback_nchannels; chn++) alsa_driver_silence_on_channel (driver, chn, frag); nframes -= frag; } } if (snd_pcm_mmap_commit (driver->playback_handle, offset, contiguous) < 0) { return -1; } nf -= contiguous; } } return 0; } static int usx2y_driver_read (alsa_driver_t *driver, jack_nframes_t nframes) { snd_pcm_uframes_t contiguous; snd_pcm_sframes_t nread; snd_pcm_uframes_t offset; jack_default_audio_sample_t* buf[4]; channel_t chn; JSList *node; jack_port_t* port; int err; snd_pcm_uframes_t nframes_ = nframes; if (!driver->capture_handle || driver->engine->freewheeling) { return 0; } if (driver->midi) (driver->midi->read)(driver->midi, nframes); nread = 0; if (snd_pcm_mmap_begin (driver->capture_handle, &driver->capture_areas, &offset, &nframes_) < 0) { jack_error ("ALSA/USX2Y: %s: mmap areas info error", driver->alsa_name_capture); return -1; } for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) { port = (jack_port_t *) node->data; if (!jack_port_connected (port)) { continue; } buf[chn] = jack_port_get_buffer (port, nframes_); } while (nframes) { contiguous = nframes; if (usx2y_driver_get_channel_addresses_capture ( driver, &contiguous) < 0) { return -1; } for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) { port = (jack_port_t *) node->data; if (!jack_port_connected (port)) { /* no-copy optimization */ continue; } alsa_driver_read_from_channel (driver, chn, buf[chn] + nread, contiguous); /* sample_move_dS_s24(buf[chn] + nread, */ /* driver->capture_addr[chn], */ /* contiguous, */ /* driver->capture_interleave_skip); */ } nread += contiguous; nframes -= contiguous; } if ((err = snd_pcm_mmap_commit (driver->capture_handle, offset, nframes_)) < 0) { jack_error ("ALSA/USX2Y: could not complete read of %" PRIu32 " frames: error = %d", nframes_, err); return -1; } return 0; } static int usx2y_driver_write (alsa_driver_t* driver, jack_nframes_t nframes) { channel_t chn; JSList *node; jack_default_audio_sample_t* buf[2]; snd_pcm_sframes_t nwritten; snd_pcm_uframes_t contiguous; snd_pcm_uframes_t offset; jack_port_t *port; int err; snd_pcm_uframes_t nframes_ = nframes; driver->process_count++; if (!driver->playback_handle || driver->engine->freewheeling) { return 0; } if (driver->midi) (driver->midi->write)(driver->midi, nframes); nwritten = 0; /* check current input monitor request status */ driver->input_monitor_mask = 0; for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) { if (((jack_port_t *) node->data)->shared->monitor_requests) { driver->input_monitor_mask |= (1<hw_monitoring) { if ((driver->hw->input_monitor_mask != driver->input_monitor_mask) && !driver->all_monitor_in) { driver->hw->set_input_monitor_mask ( driver->hw, driver->input_monitor_mask); } } if (snd_pcm_mmap_begin(driver->playback_handle, &driver->playback_areas, &offset, &nframes_) < 0) { jack_error ("ALSA/USX2Y: %s: mmap areas info error", driver->alsa_name_capture); return -1; } for (chn = 0, node = driver->playback_ports; node; node = jack_slist_next (node), chn++) { port = (jack_port_t *) node->data; buf[chn] = jack_port_get_buffer (port, nframes_); } while (nframes) { contiguous = nframes; if (usx2y_driver_get_channel_addresses_playback ( driver, &contiguous) < 0) { return -1; } for (chn = 0, node = driver->playback_ports; node; node = jack_slist_next (node), chn++) { port = (jack_port_t *) node->data; alsa_driver_write_to_channel (driver, chn, buf[chn] + nwritten, contiguous); } nwritten += contiguous; nframes -= contiguous; } if ((err = snd_pcm_mmap_commit (driver->playback_handle, offset, nframes_)) < 0) { jack_error ("ALSA/USX2Y: could not complete playback of %" PRIu32 " frames: error = %d", nframes_, err); if (err != -EPIPE && err != -ESTRPIPE) return -1; } return 0; } static void usx2y_driver_setup (alsa_driver_t *driver) { driver->nt_start = (JackDriverNTStartFunction) usx2y_driver_start; driver->nt_stop = (JackDriverNTStopFunction) usx2y_driver_stop; driver->read = (JackDriverReadFunction) usx2y_driver_read; driver->write = (JackDriverReadFunction) usx2y_driver_write; driver->null_cycle = (JackDriverNullCycleFunction) usx2y_driver_null_cycle; } jack_hardware_t * jack_alsa_usx2y_hw_new (alsa_driver_t *driver) { jack_hardware_t *hw; usx2y_t *h; int hwdep_cardno; int hwdep_devno; char *hwdep_colon; char hwdep_name[9]; snd_hwdep_t *hwdep_handle; hw = (jack_hardware_t *) malloc (sizeof (jack_hardware_t)); hw->capabilities = 0; hw->input_monitor_mask = 0; hw->private_hw = 0; hw->set_input_monitor_mask = usx2y_set_input_monitor_mask; hw->change_sample_clock = usx2y_change_sample_clock; hw->release = usx2y_release; /* Derive the special USB US-X2Y hwdep pcm device name from * the playback one, thus allowing the use of the "rawusb" * experimental stuff if, and only if, the "hw:n,2" device * name is specified. Otherwise, fallback to generic backend. */ hwdep_handle = NULL; hwdep_cardno = hwdep_devno = 0; if ((hwdep_colon = strrchr(driver->alsa_name_playback, ':')) != NULL) sscanf(hwdep_colon, ":%d,%d", &hwdep_cardno, &hwdep_devno); if (hwdep_devno == 2) { snprintf(hwdep_name, sizeof(hwdep_name), "hw:%d,1", hwdep_cardno); if (snd_hwdep_open (&hwdep_handle, hwdep_name, O_RDWR) < 0) { jack_error ("ALSA/USX2Y: Cannot open hwdep device \"%s\"", hwdep_name); } else { /* Allocate specific USX2Y hwdep pcm struct. */ h = (usx2y_t *) malloc (sizeof (usx2y_t)); h->driver = driver; h->hwdep_handle = hwdep_handle; hw->private_hw = h; /* Set our own operational function pointers. */ usx2y_driver_setup(driver); jack_info("ALSA/USX2Y: EXPERIMENTAL hwdep pcm device %s" " (aka \"rawusb\")", driver->alsa_name_playback); } } return hw; }