pure-data/src/s_audio_alsamm.c

/* Copyright (c) 1997-2003 Guenter Geiger, Miller Puckette, Larry Troxler,
* Winfried Ritsch, Karl MacMillan, and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/*
   this audiodriverinterface inputs and outputs audio data using
   the ALSA MMAP API available on linux.
   this is optimized for hammerfall cards and does not make an attempt to be general
   now, please adapt to your needs or let me know ...
   constrains now:
    - audio Card with ALSA-Driver > 1.0.3,
    - alsa-device (preferable hw) with MMAP NONINTERLEAVED SIGNED-32Bit features
    - up to 4 cards with has to be hardwaresynced
   (winfried)
*/
#include <alsa/asoundlib.h>

#include "m_pd.h"
#include "s_stuff.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "s_audio_alsa.h"

/* needed for alsa 0.9 compatibility: */
#if (SND_LIB_MAJOR < 1)
#define ALSAAPI9
#endif
/* sample type magic ...
   Hammerfall/HDSP/DSPMADI cards always 32Bit where lower 8Bit not used (played) in AD/DA,
   but can have some bits set (subchannel coding)
*/
/* sample type magic ...
   Hammerfall/HDSP/DSPMADI cards always 32Bit where lower 8Bit not used (played) in AD/DA,
   but can have some bits set (subchannel coding)
*/

#define ALSAMM_SAMPLEWIDTH_32

#ifndef INT32_MAX
#define INT32_MAX
#endif

/* maybe:
    don't assume we can turn all 31 bits when doing float-to-fix;
    otherwise some audio drivers (e.g. Midiman/ALSA) wrap around.

    but not now on hammerfall (w)
*/

/* 24 Bit are used so MAX Samplevalue not INT32_MAX ??? */
#define F32MAX
#define CLIP32(x)

#define ALSAMM_FORMAT
/*
   maximum of 4 devices
   you can mix rme9632,hdsp9632 (18 chans) rme9652,hdsp9652 (26 chans), dsp-madi (64 chans)
   if synced
*/

/* the concept is holding data for each device
   where a device is in and output and has one name.

   but since we can use only ins or only outs or both
   on each hardware we list them  in used_???device
   for alsa separated for inputs and outputs

   due the need to link in and out-device on one card
   and do only one mmap prepare and start for in and out
   the concept turns out to be not very efficient,
   so  i will change it maybe in future...
*/

static int alsamm_incards =;
static t_alsa_dev *alsamm_indevice[ALSA_MAXDEV];
static int alsamm_outcards =;
static t_alsa_dev *alsamm_outdevice[ALSA_MAXDEV];

/*
   we need same samplerate, buffertime and so on for
   each card soo we use global vars...
   time is in us,   size in frames (i hope so ;-)
*/
static unsigned int alsamm_sr =;
static unsigned int alsamm_buffertime =;
static unsigned int alsamm_buffersize =;
static int alsamm_transfersize =;

/* bad style: we assume all cards give the same answer at init so we make this vars global
   to have a faster access in writing reading during send_dacs */
static snd_pcm_sframes_t alsamm_period_size;
static unsigned int alsamm_periods;
static snd_pcm_sframes_t alsamm_buffer_size;

/* if more than this sleep detected, should be more than periodsize/samplerate ??? */
static double sleep_time;

/* now we just sum all inputs/outputs of used cards to a global count
   and use them all
   ... later we should just use some channels of each card for pd
   so we reduce the overhead of using alsways all channels,
   and zero the rest once at start,
   because rme9652 and hdsp forces us to use all channels
   in mmap mode...

Note on why:
   normally hdsp and dspmadi can handle channel
   count from one to all since they can switch on/off
   the dma for them to reduce pci load, but this is only
   implemented in alsa low level drivers for dspmadi now and maybe fixed for hdsp in future
*/

static int alsamm_inchannels =;
static int alsamm_outchannels =;

/* Defines */
/*#define ALSAMM_DEBUG */
#ifdef ALSAMM_DEBUG

 #define DEBUG
 #define DEBUG2

 #define WATCH_PERIODS

 static int in_avail[WATCH_PERIODS];
 static  int out_avail[WATCH_PERIODS];
 static  int in_offset[WATCH_PERIODS];
 static  int out_offset[WATCH_PERIODS];
 static  int out_cm[WATCH_PERIODS];
 static  char *outaddr[WATCH_PERIODS];
 static  char *inaddr[WATCH_PERIODS];
 static  char *outaddr2[WATCH_PERIODS];
 static  char *inaddr2[WATCH_PERIODS];
 static  int xruns_watch[WATCH_PERIODS];
 static  int broken_opipe;

 static int dac_send = 0;
 static int alsamm_xruns = 0;

static void show_availist(void)
{
  int i;
  for(i=1;i<WATCH_PERIODS;i++){
    post("%2d:avail i=%7d %s o=%7d(%5d), offset i=%7d %s o=%7d, ptr i=%12p o=%12p, %d xruns ",
         i,in_avail[i],(out_avail[i] != in_avail[i])? "!=" : "==" , out_avail[i],out_cm[i],
         in_offset[i],(out_offset[i] != in_offset[i])? "!=" : "==" , out_offset[i],
         inaddr[i], outaddr[i], xruns_watch[i]);
  }
}
#else
 #define DEBUG(x)
 #define DEBUG2(x)
#endif

/* protos */
static char *alsamm_getdev(int nr);

static int set_hwparams(snd_pcm_t *handle,
                               snd_pcm_hw_params_t *params, int *chs);
static int set_swparams(snd_pcm_t *handle,
                               snd_pcm_sw_params_t *swparams, int playback);

static int alsamm_start(void);
static int alsamm_stop(void);

/* for debugging attach output of alsa messages to stdout stream */
snd_output_t* alsa_stdout;

static void check_error(int err, const char *why)
{}

int alsamm_open_audio(int rate, int blocksize)
{}


void alsamm_close_audio(void)
{}

/* ------- PCM INITS --------------------------------- */
static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params,int *chs)
{}

static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, int playback)
{}

/* ALSA Transfer helps */

/* xrun_recovery is called if time to late or error

  Note: use outhandle if synced i/o
        the devices are linked so prepare
        has only be called on out,
        hopefully resume too...
*/

static int xrun_recovery(snd_pcm_t *handle, int err)
{}

/* note that snd_pcm_avail has to be called before using this function */

static int alsamm_get_channels(snd_pcm_t *dev,
                               snd_pcm_uframes_t *avail,
                               snd_pcm_uframes_t *offset,
                               int nchns, char **addr)
{}


static int alsamm_start()
{}

static int alsamm_stop()
{}



/* ---------- ADC/DAC transfer in  the main loop ------- */

/* I see: (a guess as a documentation)

   all DAC data is in sys_soundout array with
   DEFDACBLKSIZE (mostly 64) for each channels which
   if we have more channels opened then dac-channels = sys_outchannels
   we have to zero (silence them), which should be done once.

Problems to solve:

   a) Since in ALSA MMAP, the MMAP region can change (don't ask me why)
   we have to do it each cycle or we say on RME HAMMERFALL/HDSP/DSPMADI
   it never changes to it once. so maybe we can do it once in open

   b) we never know if inputs are synced and zero them if not,
   except we use the control interface to check for, but this is
   a systemcall we cannot afford in RT Loops so we just don't
   and if not it will click... users fault ;-)))

*/

int alsamm_send_dacs(void)
{}


/* extra debug info */

void alsamm_showstat(snd_pcm_t *handle)
{}