|
- /*
- 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 <sys/mman.h>
-
- #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<<chn);
- }
- }
-
- if (driver->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;
- }
|