Logo Search packages:      
Sourcecode: jackd2 version File versions  Download package

usx2y.c

/*
    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 seperate 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;
}

Generated by  Doxygen 1.6.0   Back to index