/* 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) { … }