From 57045df5fe3ec557e57dc7434ac1a07b5521bffc Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Mon, 29 Jul 2002 17:06:19 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r58, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=59 --- pd/src/s_linux.c | 3087 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3087 insertions(+) create mode 100644 pd/src/s_linux.c (limited to 'pd/src/s_linux.c') diff --git a/pd/src/s_linux.c b/pd/src/s_linux.c new file mode 100644 index 00000000..5c394674 --- /dev/null +++ b/pd/src/s_linux.c @@ -0,0 +1,3087 @@ +/* Copyright (c) 1997-1999 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 file implements the sys_ functions profiled in m_imp.h for + audio and MIDI I/O. In Linux there might be several APIs for doing the + audio part; right now there are three (OSS, ALSA, RME); the third is + for the RME 9652 driver by Ritsch (but not for the OSS compatible + one by Geiger; for that one, OSS should work.) + + FUNCTION PREFIXES. + sys_ -- functions which must be exported to Pd on all platforms + linux_ -- linux-specific objects which don't depend on API, + mostly static but some exported. + oss_, alsa_, rme_ -- API-specific functions, all of which are + static. + + ALSA SUPPORT. If ALSA99 is defined we support ALSA 0.5x; if ALSA01, + ALSA 0.9x. (the naming scheme reflects the possibility of further API + changes in the future...) We define "ALSA" for code relevant to both + APIs. + + For MIDI, we only offer the OSS API; ALSA has to emulate OSS for us. +*/ + +/* OSS include (whether we're doing OSS audio or not we need this for MIDI) */ + + +/* IOhannes::: + * hacked this to add advanced multidevice-support + * 1311:forum::für::umläute:2001 + */ + +#include + +#if (defined(ALSA01) || defined(ALSA99)) +#define ALSA +#endif + +#ifdef ALSA99 +#include +#endif +#ifdef ALSA01 +#include +#endif + +#include "m_imp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local function prototypes */ + +static void linux_close_midi( void); + +static int oss_open_audio(int naudioindev, int *audioindev, int nchindev, + int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, + int *choutdev, int rate); /* IOhannes */ + +static void oss_close_audio(void); +static int oss_send_dacs(void); +static void oss_reportidle(void); + +#ifdef ALSA +typedef int16_t t_alsa_sample16; +typedef int32_t t_alsa_sample32; +#define ALSA_SAMPLEWIDTH_16 sizeof(t_alsa_sample16) +#define ALSA_SAMPLEWIDTH_32 sizeof(t_alsa_sample32) +#define ALSA_XFERSIZE16 (signed int)(sizeof(t_alsa_sample16) * DACBLKSIZE) +#define ALSA_XFERSIZE32 (signed int)(sizeof(t_alsa_sample32) * DACBLKSIZE) +#define ALSA_MAXDEV 1 +#define ALSA_JITTER 1024 +#define ALSA_EXTRABUFFER 2048 +#define ALSA_DEFFRAGSIZE 64 +#define ALSA_DEFNFRAG 12 + +#ifdef ALSA99 +typedef struct _alsa_dev +{ + snd_pcm_t *handle; + snd_pcm_channel_info_t info; + snd_pcm_channel_setup_t setup; +} t_alsa_dev; + +t_alsa_dev alsa_device[ALSA_MAXDEV]; +static int n_alsa_dev; +static char *alsa_buf; +static int alsa_samplewidth; +#endif /* ALSA99 */ + +#ifdef ALSA01 +typedef struct _alsa_dev +{ + snd_pcm_t *inhandle; + snd_pcm_t *outhandle; +} t_alsa_dev; + +t_alsa_dev alsa_device; +static short *alsa_buf; +static int alsa_samplewidth; +static snd_pcm_status_t* in_status; +static snd_pcm_status_t* out_status; +#endif /* ALSA01 */ + +#if 0 /* early alsa 0.9 beta dists had different names for these: */ +#define SND_PCM_ACCESS_RW_INTERLEAVED SNDRV_PCM_ACCESS_RW_INTERLEAVED +#define SND_PCM_FORMAT_S32 SNDRV_PCM_FORMAT_S32 +#define SND_PCM_FORMAT_S16 SNDRV_PCM_FORMAT_S16 +#define SND_PCM_SUBFORMAT_STD SNDRV_PCM_SUBFORMAT_STD +#endif + +static int alsa_mode; +static int alsa_open_audio(int inchans, int outchans, int rate); +static void alsa_close_audio(void); +static int alsa_send_dacs(void); +static void alsa_set_params(t_alsa_dev *dev, int dir, int rate, int voices); +static void alsa_reportidle(void); +#endif /* ALSA */ + +#ifdef RME_HAMMERFALL +static int rme9652_open_audio(int inchans, int outchans, int rate); +static void rme9652_close_audio(void); +static int rme9652_send_dacs(void); +static void rme9652_reportidle(void); +#endif /* RME_HAMMERFALL */ + +/* Defines */ +#define DEBUG(x) x +#define DEBUG2(x) {x;} + +#define OSS_MAXCHPERDEV 32 /* max channels per OSS device */ +#define OSS_MAXDEV 4 /* maximum number of input or output devices */ +#define OSS_DEFFRAGSIZE 256 /* default log fragment size (frames) */ +#define OSS_DEFAUDIOBUF 40000 /* default audiobuffer, microseconds */ +#define OSS_DEFAULTCH 2 +#define RME_DEFAULTCH 8 /* need this even if RME undefined */ +typedef int16_t t_oss_int16; +typedef int32_t t_oss_int32; +#define OSS_MAXSAMPLEWIDTH sizeof(t_oss_int32) +#define OSS_BYTESPERCHAN(width) (DACBLKSIZE * (width)) +#define OSS_XFERSAMPS(chans) (DACBLKSIZE* (chans)) +#define OSS_XFERSIZE(chans, width) (DACBLKSIZE * (chans) * (width)) + +#ifdef RME_HAMMERFALL +typedef int32_t t_rme_sample; +#define RME_SAMPLEWIDTH sizeof(t_rme_sample) +#define RME_BYTESPERCHAN (DACBLKSIZE * RME_SAMPLEWIDTH) +#endif /* RME_HAMMERFALL */ + +/* GLOBALS */ +static int linux_whichapi = API_OSS; +static int linux_inchannels; +static int linux_outchannels; +static int linux_advance_samples; /* scheduler advance in samples */ +static int linux_meters; /* true if we're metering */ +static float linux_inmax; /* max input amplitude */ +static float linux_outmax; /* max output amplitude */ +static int linux_fragsize = 0; /* for block mode; block size (sample frames) */ +static int linux_nfragment = 0; /* ... and number of blocks. */ + +#ifdef ALSA99 +static int alsa_devno = 1; +#endif +#ifdef ALSA01 +static char alsa_devname[512] = "hw:0,0"; +static int alsa_use_plugin = 0; +#endif + +/* our device handles */ + +typedef struct _oss_dev +{ + int d_fd; + unsigned int d_space; /* bytes available for writing/reading */ + int d_bufsize; /* total buffer size in blocks for this device */ + int d_dropcount; /* # of buffers to drop for resync (output only) */ + unsigned int d_nchannels; /* number of channels for this device */ + unsigned int d_bytespersamp; /* bytes per sample (2 for 16 bit, 4 for 32) */ +} t_oss_dev; + +static t_oss_dev linux_dacs[OSS_MAXDEV]; +static t_oss_dev linux_adcs[OSS_MAXDEV]; +static int linux_noutdevs = 0; +static int linux_nindevs = 0; + + /* exported variables */ +int sys_schedadvance = OSS_DEFAUDIOBUF; /* scheduler advance in microsecs */ +float sys_dacsr; +int sys_hipriority = 0; +t_sample *sys_soundout; +t_sample *sys_soundin; + + /* OSS-specific private variables */ +static int oss_blockmode = 1; /* flag to use "blockmode" */ +static int oss_32bit = 0; /* allow 23 bit transfers in OSS */ +static char ossdsp[] = "/dev/dsp%d"; + +#ifndef INT32_MAX +#define INT32_MAX 0x7fffffff +#endif + /* don't assume we can turn all 31 bits when doing float-to-fix; + otherwise some audio drivers (e.g. Midiman/ALSA) wrap around. */ +#define FMAX 0x7ffff000 +#define CLIP32(x) (((x)>FMAX)?FMAX:((x) < -FMAX)?-FMAX:(x)) + + +/* ------------- private routines for all APIS ------------------- */ + +static void linux_flush_all_underflows_to_zero(void) +{ +/* + TODO: Implement similar thing for linux (GGeiger) + + One day we will figure this out, I hope, because it + costs CPU time dearly on Intel - LT + */ + /* union fpc_csr f; + f.fc_word = get_fpc_csr(); + f.fc_struct.flush = 1; + set_fpc_csr(f.fc_word); + */ +} + + /* set sample rate and channels. Must set sample rate before "configuring" + any devices so we know scheduler advance in samples. */ + +static void linux_setsr(int sr) +{ + sys_dacsr = sr; + linux_advance_samples = (sys_schedadvance * sys_dacsr) / (1000000.); + if (linux_advance_samples < 3 * DACBLKSIZE) + linux_advance_samples = 3 * DACBLKSIZE; +} + +static void linux_setch(int chin, int chout) +{ + int nblk; + int inbytes = chin * (DACBLKSIZE*sizeof(float)); + int outbytes = chout * (DACBLKSIZE*sizeof(float)); + + linux_inchannels = chin; + linux_outchannels = chout; + if (sys_soundin) + free(sys_soundin); + sys_soundin = (t_float *)malloc(inbytes); + memset(sys_soundin, 0, inbytes); + + if (sys_soundout) + free(sys_soundout); + sys_soundout = (t_float *)malloc(outbytes); + memset(sys_soundout, 0, outbytes); + + if (sys_verbose) + post("input channels = %d, output channels = %d", + linux_inchannels, linux_outchannels); +} + +/* ---------------- MIDI routines -------------------------- */ + +static int oss_nmidiin; +static int oss_midiinfd[MAXMIDIINDEV]; +static int oss_nmidiout; +static int oss_midioutfd[MAXMIDIOUTDEV]; + +static void oss_midiout(int fd, int n) +{ + char b = n; + if ((write(fd, (char *) &b, 1)) != 1) + perror("midi write"); +} + +#define O_MIDIFLAG O_NDELAY + +void linux_open_midi(int nmidiin, int *midiinvec, int nmidiout, int *midioutvec) +{ + int i; + for (i = 0; i < nmidiout; i++) + oss_midioutfd[i] = -1; + for (i = 0, oss_nmidiin = 0; i < nmidiin; i++) + { + int fd = -1, j, outdevindex = -1; + char namebuf[80]; + int devno = midiinvec[i]; + + for (j = 0; j < nmidiout; j++) + if (midioutvec[j] == midiinvec[i]) + outdevindex = j; + + /* try to open the device for read/write. */ + if (devno == 1 && fd < 0 && outdevindex >= 0) + { + sys_setalarm(1000000); + fd = open("/dev/midi", O_RDWR | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, + "device 1: tried /dev/midi READ/WRITE; returned %d\n", fd); + if (outdevindex >= 0 && fd >= 0) + oss_midioutfd[outdevindex] = fd; + } + if (fd < 0 && outdevindex >= 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%2.2d", devno-1); + fd = open(namebuf, O_RDWR | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, + "device %d: tried %s READ/WRITE; returned %d\n", + devno, namebuf, fd); + if (outdevindex >= 0 && fd >= 0) + oss_midioutfd[outdevindex] = fd; + } + if (fd < 0 && outdevindex >= 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%d", devno-1); + fd = open(namebuf, O_RDWR | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, "device %d: tried %s READ/WRITE; returned %d\n", + devno, namebuf, fd); + if (outdevindex >= 0 && fd >= 0) + oss_midioutfd[outdevindex] = fd; + } + if (devno == 1 && fd < 0) + { + sys_setalarm(1000000); + fd = open("/dev/midi", O_RDONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, + "device 1: tried /dev/midi READONLY; returned %d\n", fd); + } + if (fd < 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%2.2d", devno-1); + fd = open(namebuf, O_RDONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, "device %d: tried %s READONLY; returned %d\n", + devno, namebuf, fd); + } + if (fd < 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%d", devno-1); + fd = open(namebuf, O_RDONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, "device %d: tried %s READONLY; returned %d\n", + devno, namebuf, fd); + } + if (fd >= 0) + oss_midiinfd[oss_nmidiin++] = fd; + else post("couldn't open MIDI input device %d", devno); + } + for (i = 0, oss_nmidiout = 0; i < nmidiout; i++) + { + int fd = oss_midioutfd[i]; + char namebuf[80]; + int devno = midioutvec[i]; + if (devno == 1 && fd < 0) + { + sys_setalarm(1000000); + fd = open("/dev/midi", O_WRONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, + "device 1: tried /dev/midi WRITEONLY; returned %d\n", fd); + } + if (fd < 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%2.2d", devno-1); + fd = open(namebuf, O_WRONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, "device %d: tried %s WRITEONLY; returned %d\n", + devno, namebuf, fd); + } + if (fd < 0) + { + sys_setalarm(1000000); + sprintf(namebuf, "/dev/midi%d", devno-1); + fd = open(namebuf, O_WRONLY | O_MIDIFLAG); + if (sys_verbose) + fprintf(stderr, "device %d: tried %s WRITEONLY; returned %d\n", + devno, namebuf, fd); + } + if (fd >= 0) + oss_midioutfd[oss_nmidiout++] = fd; + else post("couldn't open MIDI output device %d", devno); + } + + if (oss_nmidiin < nmidiin || oss_nmidiout < nmidiout || sys_verbose) + post("opened %d MIDI input device(s) and %d MIDI output device(s).", + oss_nmidiin, oss_nmidiout); +} + +#define md_msglen(x) (((x)<0xC0)?2:((x)<0xE0)?1:((x)<0xF0)?2:\ + ((x)==0xF2)?2:((x)<0xF4)?1:0) + +void sys_putmidimess(int portno, int a, int b, int c) +{ + if (portno >= 0 && portno < oss_nmidiout) + { + switch (md_msglen(a)) + { + case 2: + oss_midiout(oss_midioutfd[portno],a); + oss_midiout(oss_midioutfd[portno],b); + oss_midiout(oss_midioutfd[portno],c); + return; + case 1: + oss_midiout(oss_midioutfd[portno],a); + oss_midiout(oss_midioutfd[portno],b); + return; + case 0: + oss_midiout(oss_midioutfd[portno],a); + return; + }; + } +} + +void sys_putmidibyte(int portno, int byte) +{ + if (portno >= 0 && portno < oss_nmidiout) + oss_midiout(oss_midioutfd[portno], byte); +} + +#if 0 /* this is the "select" version which doesn't work with OSS + driver for emu10k1 (it doesn't implement select.) */ +void sys_poll_midi(void) +{ + int i, throttle = 100; + struct timeval timout; + int did = 1, maxfd = 0; + while (did) + { + fd_set readset, writeset, exceptset; + did = 0; + if (throttle-- < 0) + break; + timout.tv_sec = 0; + timout.tv_usec = 0; + + FD_ZERO(&writeset); + FD_ZERO(&readset); + FD_ZERO(&exceptset); + for (i = 0; i < oss_nmidiin; i++) + { + if (oss_midiinfd[i] > maxfd) + maxfd = oss_midiinfd[i]; + FD_SET(oss_midiinfd[i], &readset); + } + select(maxfd+1, &readset, &writeset, &exceptset, &timout); + for (i = 0; i < oss_nmidiin; i++) + if (FD_ISSET(oss_midiinfd[i], &readset)) + { + char c; + int ret = read(oss_midiinfd[i], &c, 1); + if (ret <= 0) + fprintf(stderr, "Midi read error\n"); + else sys_midibytein(i, (c & 0xff)); + did = 1; + } + } +} +#else + + /* this version uses the asynchronous "read()" ... */ +void sys_poll_midi(void) +{ + int i, throttle = 100; + struct timeval timout; + int did = 1, maxfd = 0; + while (did) + { + fd_set readset, writeset, exceptset; + did = 0; + if (throttle-- < 0) + break; + for (i = 0; i < oss_nmidiin; i++) + { + char c; + int ret = read(oss_midiinfd[i], &c, 1); + if (ret < 0) + { + if (errno != EAGAIN) + perror("MIDI"); + } + else if (ret != 0) + { + sys_midibytein(i, (c & 0xff)); + did = 1; + } + } + } +} +#endif + +void linux_close_midi() +{ + int i; + for (i = 0; i < oss_nmidiin; i++) + close(oss_midiinfd[i]); + for (i = 0; i < oss_nmidiout; i++) + close(oss_midioutfd[i]); + oss_nmidiin = oss_nmidiout = 0; +} + +#define MAXAUDIODEV 4 +#define DEFAULTINDEV 1 +#define DEFAULTOUTDEV 1 + +/* ----------------------- public routines ----------------------- */ +void sys_listdevs( void) +{ + post("device listing not implemented in Linux yet\n"); +} + +void sys_open_audio(int naudioindev, int *audioindev, int nchindev, + int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, + int *choutdev, int rate) +{ /* IOhannes */ + int i, *ip; + int defaultchannels = + (linux_whichapi == API_RME ? RME_DEFAULTCH : OSS_DEFAULTCH); + if (rate < 1) + rate=44100; + + if (naudioindev == -1) + { /* not set */ + if (nchindev==-1) + { + nchindev=1; + chindev[0]=defaultchannels; + naudioindev=1; + audioindev[0] = DEFAULTINDEV; + } + else + { + for (i = 0; i < MAXAUDIODEV; i++) + audioindev[i]=i+1; + naudioindev = nchindev; + } + } + else + { + if (nchindev == -1) + { + nchindev = naudioindev; + for (i = 0; i < naudioindev; i++) + chindev[i] = defaultchannels; + } + else if (nchindev > naudioindev) + { + for (i = naudioindev; i < nchindev; i++) + { + if (i == 0) + audioindev[0] = DEFAULTINDEV; + else audioindev[i] = audioindev[i-1] + 1; + } + naudioindev = nchindev; + } + else if (nchindev < naudioindev) + { + for (i = nchindev; i < naudioindev; i++) + { + if (i == 0) + chindev[0] = defaultchannels; + else chindev[i] = chindev[i-1]; + } + naudioindev = nchindev; + } + } + + if (naudiooutdev == -1) + { /* not set */ + if (nchoutdev==-1) + { + nchoutdev=1; + choutdev[0]=defaultchannels; + naudiooutdev=1; + audiooutdev[0] = DEFAULTOUTDEV; + } + else + { + for (i = 0; i < MAXAUDIODEV; i++) + audiooutdev[i] = i+1; + naudiooutdev = nchoutdev; + } + } + else + { + if (nchoutdev == -1) + { + nchoutdev = naudiooutdev; + for (i = 0; i < naudiooutdev; i++) + choutdev[i] = defaultchannels; + } + else if (nchoutdev > naudiooutdev) + { + for (i = naudiooutdev; i < nchoutdev; i++) + { + if (i == 0) + audiooutdev[0] = DEFAULTOUTDEV; + else audiooutdev[i] = audiooutdev[i-1] + 1; + } + naudiooutdev = nchoutdev; + } + else if (nchoutdev < naudiooutdev) + { + for (i = nchoutdev; i < naudiooutdev; i++) + { + if (i == 0) + choutdev[0] = defaultchannels; + else choutdev[i] = choutdev[i-1]; + } + naudiooutdev = nchoutdev; + } + } + + linux_flush_all_underflows_to_zero(); +#ifdef ALSA + if (linux_whichapi == API_ALSA) + alsa_open_audio((naudioindev > 0 ? chindev[0] : 0), + (naudiooutdev > 0 ? choutdev[0] : 0), rate); + else +#endif +#ifdef RME_HAMMERFALL + if (linux_whichapi == API_RME) + rme9652_open_audio((naudioindev > 0 ? chindev[0] : 0), + (naudiooutdev > 0 ? choutdev[0] : 0), rate); + else +#endif + oss_open_audio(naudioindev, audioindev, nchindev, chindev, + naudiooutdev, audiooutdev, nchoutdev, choutdev, rate); +} + +void sys_close_audio(void) +{ + /* set timeout to avoid hanging close() call */ + + sys_setalarm(1000000); + +#ifdef ALSA + if (linux_whichapi == API_ALSA) + alsa_close_audio(); + else +#endif +#ifdef RME_HAMMERFALL + if (linux_whichapi == API_RME) + rme9652_close_audio(); + else +#endif + oss_close_audio(); + + sys_setalarm(0); +} + +void sys_open_midi(int nmidiin, int *midiinvec, + int nmidiout, int *midioutvec) +{ + linux_open_midi(nmidiin, midiinvec, nmidiout, midioutvec); +} + +void sys_close_midi( void) +{ + sys_setalarm(1000000); + linux_close_midi(); + sys_setalarm(0); +} + +int sys_send_dacs(void) +{ + if (linux_meters) + { + int i, n; + float maxsamp; + for (i = 0, n = linux_inchannels * DACBLKSIZE, maxsamp = linux_inmax; + i < n; i++) + { + float f = sys_soundin[i]; + if (f > maxsamp) maxsamp = f; + else if (-f > maxsamp) maxsamp = -f; + } + linux_inmax = maxsamp; + for (i = 0, n = linux_outchannels * DACBLKSIZE, maxsamp = linux_outmax; + i < n; i++) + { + float f = sys_soundout[i]; + if (f > maxsamp) maxsamp = f; + else if (-f > maxsamp) maxsamp = -f; + } + linux_outmax = maxsamp; + } +#ifdef ALSA + if (linux_whichapi == API_ALSA) + return alsa_send_dacs(); +#endif +#ifdef RME_HAMMERFALL + if (linux_whichapi == API_RME) + return rme9652_send_dacs(); +#endif + return oss_send_dacs(); +} + +float sys_getsr(void) +{ + return (sys_dacsr); +} + +int sys_get_outchannels(void) +{ + return (linux_outchannels); +} + +int sys_get_inchannels(void) +{ + return (linux_inchannels); +} + +void sys_audiobuf(int n) +{ + /* set the size, in milliseconds, of the audio FIFO */ + if (n < 5) n = 5; + else if (n > 5000) n = 5000; + sys_schedadvance = n * 1000; +} + +void sys_getmeters(float *inmax, float *outmax) +{ + if (inmax) + { + linux_meters = 1; + *inmax = linux_inmax; + *outmax = linux_outmax; + } + else + linux_meters = 0; + linux_inmax = linux_outmax = 0; +} + +void sys_reportidle(void) +{ +} + +void sys_set_priority(int higher) +{ + struct sched_param par; + int p1 ,p2, p3; +#ifdef _POSIX_PRIORITY_SCHEDULING + + p1 = sched_get_priority_min(SCHED_FIFO); + p2 = sched_get_priority_max(SCHED_FIFO); + p3 = (higher ? p2 - 1 : p2 - 3); + par.sched_priority = p3; + + if (sched_setscheduler(0,SCHED_FIFO,&par) != -1) + fprintf(stderr, "priority %d scheduling enabled.\n", p3); +#endif + +#ifdef _POSIX_MEMLOCK + if (mlockall(MCL_FUTURE) != -1) + fprintf(stderr, "memory locking enabled.\n"); +#endif +} + +void sys_setblocksize(int n) +{ + if (n < 1) + n = 1; + linux_fragsize = n; + oss_blockmode = 1; +} + +/* ------------ linux-specific command-line flags -------------- */ + +void linux_setfrags(int n) +{ + linux_nfragment = n; + oss_blockmode = 1; +} + +void linux_streammode( void) +{ + oss_blockmode = 0; +} + +void linux_32bit( void) +{ + oss_32bit = 1; +} + +void linux_set_sound_api(int which) +{ + linux_whichapi = which; + if (sys_verbose) + post("linux_whichapi %d", linux_whichapi); +} + +#ifdef ALSA99 +void linux_alsa_devno(int devno) +{ + alsa_devno = devno; +} + +#endif + +#ifdef ALSA01 +void linux_alsa_devname(char *devname) +{ + strncpy(alsa_devname, devname, 511); +} + +void linux_alsa_use_plugin(int t) +{ + if (t == 1) + alsa_use_plugin = 1; + else + alsa_use_plugin = 0; +} + +#endif + +/* -------------- Audio I/O using the OSS API ------------------ */ + +typedef struct _multidev { + int fd; + int channels; + int format; +} t_multidev; + +int oss_reset(int fd) { + int err; + if ((err = ioctl(fd,SNDCTL_DSP_RESET)) < 0) + error("OSS: Could not reset"); + return err; +} + + /* The AFMT_S32_BLOCKED format is not defined in standard linux kernels + but is proposed by Guenter Geiger to support extending OSS to handle + 32 bit sample. This is user in Geiger's OSS driver for RME Hammerfall. + I'm not clear why this isn't called AFMT_S32_[SLN]E... */ + +#ifndef AFMT_S32_BLOCKED +#define AFMT_S32_BLOCKED 0x0000400 +#endif + +void oss_configure(t_oss_dev *dev, int srate, int dac, int skipblocksize) +{ /* IOhannes */ + int orig, param, nblk, fd = dev->d_fd, wantformat; + int nchannels = dev->d_nchannels; + int advwas = sys_schedadvance; + + audio_buf_info ainfo; + + /* IOhannes : + * pd is very likely to crash if different formats are used on + multiple soundcards + */ + + /* set resolution - first try 4 byte samples */ + if (oss_32bit && (ioctl(fd,SNDCTL_DSP_GETFMTS,¶m) >= 0) && + (param & AFMT_S32_BLOCKED)) + { + wantformat = AFMT_S32_BLOCKED; + dev->d_bytespersamp = 4; + } + else + { + wantformat = AFMT_S16_NE; + dev->d_bytespersamp = 2; + } + param = wantformat; + + if (sys_verbose) + post("bytes per sample = %d", dev->d_bytespersamp); + if (ioctl(fd, SNDCTL_DSP_SETFMT, ¶m) == -1) + fprintf(stderr,"OSS: Could not set DSP format\n"); + else if (wantformat != param) + fprintf(stderr,"OSS: DSP format: wanted %d, got %d\n", + wantformat, param); + + /* sample rate */ + orig = param = srate; + if (ioctl(fd, SNDCTL_DSP_SPEED, ¶m) == -1) + fprintf(stderr,"OSS: Could not set sampling rate for device\n"); + else if( orig != param ) + fprintf(stderr,"OSS: sampling rate: wanted %d, got %d\n", + orig, param ); + + if (oss_blockmode && !skipblocksize) + { + int fragbytes, logfragsize, nfragment; + /* setting fragment count and size. */ + if (linux_nfragment) /* if nfrags specified, take literally */ + { + nfragment = linux_nfragment; + if (!linux_fragsize) + linux_fragsize = OSS_DEFFRAGSIZE; + sys_schedadvance = ((nfragment * linux_fragsize) * 1.e6) + / (float)srate; + linux_setsr(srate); + } + else + { + if (!linux_fragsize) + { + linux_fragsize = OSS_DEFFRAGSIZE; + while (linux_fragsize > DACBLKSIZE + && linux_fragsize * 4 > linux_advance_samples) + linux_fragsize = linux_fragsize/2; + } + /* post("adv_samples %d", linux_advance_samples); */ + nfragment = (sys_schedadvance * (44100. * 1.e-6)) / linux_fragsize; + } + fragbytes = linux_fragsize * (dev->d_bytespersamp * nchannels); + logfragsize = ilog2(fragbytes); + + if (fragbytes != (1 << logfragsize)) + post("warning: OSS takes only power of 2 blocksize; using %d", + (1 << logfragsize)/(dev->d_bytespersamp * nchannels)); + if (sys_verbose) + post("setting nfrags = %d, fragsize %d\n", nfragment, fragbytes); + + param = orig = (nfragment<<16) + logfragsize; + if (ioctl(fd,SNDCTL_DSP_SETFRAGMENT, ¶m) == -1) + error("OSS: Could not set or read fragment size\n"); + if (param != orig) + { + nfragment = ((param >> 16) & 0xffff); + logfragsize = (param & 0xffff); + post("warning: actual fragments %d, blocksize %d", + nfragment, (1 << logfragsize)); + } + if (sys_verbose) + post("audiobuffer set to %d msec", (int)(0.001 * sys_schedadvance)); + } + + if (dac) + { + /* use "free space" to learn the buffer size. Normally you + should set this to your own desired value; but this seems not + to be implemented uniformly across different sound cards. LATER + we should figure out what to do if the requested scheduler advance + is greater than this buffer size; for now, we just print something + out. */ + + int defect; + if (ioctl(fd, SOUND_PCM_GETOSPACE,&ainfo) < 0) + fprintf(stderr,"OSS: ioctl on output device failed"); + dev->d_bufsize = ainfo.bytes; + + defect = linux_advance_samples * (dev->d_bytespersamp * nchannels) + - dev->d_bufsize - OSS_XFERSIZE(nchannels, dev->d_bytespersamp); + if (defect > 0) + { + if (sys_verbose || defect > (dev->d_bufsize >> 2)) + fprintf(stderr, + "OSS: requested audio buffer size %d limited to %d\n", + linux_advance_samples * (dev->d_bytespersamp * nchannels), + dev->d_bufsize); + linux_advance_samples = + (dev->d_bufsize - OSS_XFERSAMPS(nchannels)) / + (dev->d_bytespersamp *nchannels); + } + } +} + +static int oss_setchannels(int fd, int wantchannels, char *devname) +{ /* IOhannes */ + int param = wantchannels; + + while (param>1) { + int save = param; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, ¶m) == -1) { + error("OSS: SNDCTL_DSP_CHANNELS failed %s",devname); + } else { + if (param == save) return (param); + } + param=save-1; + } + + return (0); +} + +#define O_AUDIOFLAG 0 /* O_NDELAY */ + +int oss_open_audio(int nindev, int *indev, int nchin, int *chin, + int noutdev, int *outdev, int nchout, int *chout, int rate) +{ /* IOhannes */ + int capabilities = 0; + int inchannels = 0, outchannels = 0; + char devname[20]; + int n, i, fd; + char buf[OSS_MAXSAMPLEWIDTH * DACBLKSIZE * OSS_MAXCHPERDEV]; + int num_devs = 0; + int wantmore=0; + int spread = 0; + audio_buf_info ainfo; + + linux_nindevs = linux_noutdevs = 0; + + /* set logical sample rate amd calculate linux_advance_samples. */ + linux_setsr(rate); + + /* mark input devices unopened */ + for (i = 0; i < OSS_MAXDEV; i++) + linux_adcs[i].d_fd = -1; + + /* open output devices */ + wantmore=0; + if (noutdev < 0 || nindev < 0) + bug("linux_open_audio"); + + for (n = 0; n < noutdev; n++) + { + int gotchans, j, inindex = -1; + int thisdevice=outdev[n]; + int wantchannels = (nchout>n) ? chout[n] : wantmore; + fd = -1; + if (!wantchannels) + goto end_out_loop; + + if (thisdevice > 1) + sprintf(devname, "/dev/dsp%d", thisdevice-1); + else sprintf(devname, "/dev/dsp"); + + /* search for input request for same device. Succeed only + if the number of channels matches. */ + for (j = 0; j < nindev; j++) + if (indev[j] == thisdevice && chin[j] == wantchannels) + inindex = j; + + /* if the same device is requested for input and output, + try to open it read/write */ + if (inindex >= 0) + { + sys_setalarm(1000000); + if ((fd = open(devname, O_RDWR | O_AUDIOFLAG)) == -1) + { + post("%s (read/write): %s", devname, strerror(errno)); + post("(now will try write-only...)"); + } + else + { + if (sys_verbose) + post("opened %s for reading and writing\n", devname); + linux_adcs[inindex].d_fd = fd; + } + } + /* if that didn't happen or if it failed, try write-only */ + if (fd == -1) + { + sys_setalarm(1000000); + if ((fd = open(devname, O_WRONLY | O_AUDIOFLAG)) == -1) + { + post("%s (writeonly): %s", + devname, strerror(errno)); + break; + } + if (sys_verbose) + post("opened %s for writing only\n", devname); + } + if (ioctl(fd, SNDCTL_DSP_GETCAPS, &capabilities) == -1) + error("OSS: SNDCTL_DSP_GETCAPS failed %s", devname); + + gotchans = oss_setchannels(fd, + (wantchannels>OSS_MAXCHPERDEV)?OSS_MAXCHPERDEV:wantchannels, + devname); + + if (sys_verbose) + post("opened audio output on %s; got %d channels", + devname, gotchans); + + if (gotchans < 2) + { + /* can't even do stereo? just give up. */ + close(fd); + } + else + { + linux_dacs[linux_noutdevs].d_nchannels = gotchans; + linux_dacs[linux_noutdevs].d_fd = fd; + oss_configure(linux_dacs+linux_noutdevs, rate, 1, 0); + + linux_noutdevs++; + outchannels += gotchans; + if (inindex >= 0) + { + linux_adcs[inindex].d_nchannels = gotchans; + chin[inindex] = gotchans; + } + } + /* LATER think about spreading large numbers of channels over + various dsp's and vice-versa */ + wantmore = wantchannels - gotchans; + end_out_loop: ; + } + + /* open input devices */ + wantmore = 0; + if (nindev==-1) + nindev=4; /* spread channels over default-devices */ + for (n = 0; n < nindev; n++) + { + int gotchans=0; + int thisdevice=indev[n]; + int wantchannels = (nchin>n)?chin[n]:wantmore; + int alreadyopened = 0; + if (!wantchannels) + goto end_in_loop; + + if (thisdevice > 1) + sprintf(devname, "/dev/dsp%d", thisdevice - 1); + else sprintf(devname, "/dev/dsp"); + + sys_setalarm(1000000); + + /* perhaps it's already open from the above? */ + if (linux_dacs[n].d_fd >= 0) + { + fd = linux_dacs[n].d_fd; + alreadyopened = 1; + } + else + { + /* otherwise try to open it here. */ + if ((fd = open(devname, O_RDONLY | O_AUDIOFLAG)) == -1) + { + post("%s (readonly): %s", devname, strerror(errno)); + goto end_in_loop; + } + if (sys_verbose) + post("opened %s for reading only\n", devname); + } + linux_adcs[linux_nindevs].d_fd = fd; + gotchans = oss_setchannels(fd, + (wantchannels>OSS_MAXCHPERDEV)?OSS_MAXCHPERDEV:wantchannels, + devname); + if (sys_verbose) + post("opened audio input device %s; got %d channels", + devname, gotchans); + + if (gotchans < 1) + { + close(fd); + goto end_in_loop; + } + + linux_adcs[linux_nindevs].d_nchannels = gotchans; + + oss_configure(linux_adcs+linux_nindevs, rate, 0, alreadyopened); + + inchannels += gotchans; + linux_nindevs++; + + wantmore = wantchannels-gotchans; + /* LATER think about spreading large numbers of channels over + various dsp's and vice-versa */ + end_in_loop: ; + } + + linux_setch(inchannels, outchannels); + + /* We have to do a read to start the engine. This is + necessary because sys_send_dacs waits until the input + buffer is filled and only reads on a filled buffer. + This is good, because it's a way to make sure that we + will not block. But I wonder why we only have to read + from one of the devices and not all of them??? */ + + if (linux_nindevs) + { + if (sys_verbose) + fprintf(stderr,("OSS: issuing first ADC 'read' ... ")); + read(linux_adcs[0].d_fd, buf, + linux_adcs[0].d_bytespersamp * + linux_adcs[0].d_nchannels * DACBLKSIZE); + if (sys_verbose) + fprintf(stderr, "...done.\n"); + } + sys_setalarm(0); + return (0); +} + +void oss_close_audio( void) +{ + int i; + for (i=0;i + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + { + linux_adcs_read(linux_adcs[dev].d_fd, buf, + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)); + if (ioctl(linux_adcs[dev].d_fd, SOUND_PCM_GETISPACE, &ainfo) < 0) + { + fprintf(stderr, "OSS: ioctl on input device %d, fd %d failed", + dev, linux_adcs[dev].d_fd); + break; + } + linux_adcs[dev].d_space = ainfo.bytes; + } + } + + /* 2. if any output devices are behind, feed them zeros to catch them + up */ + for (dev = 0; dev < linux_noutdevs; dev++) + { + while (linux_dacs[dev].d_space > linux_dacs[dev].d_bufsize - + linux_advance_samples * (linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp)) + { + if (!zeroed) + { + unsigned int i; + for (i = 0; i < OSS_XFERSAMPS(linux_dacs[dev].d_nchannels); + i++) + buf[i] = 0; + zeroed = 1; + } + linux_dacs_write(linux_dacs[dev].d_fd, buf, + OSS_XFERSIZE(linux_dacs[dev].d_nchannels, + linux_dacs[dev].d_bytespersamp)); + if (ioctl(linux_dacs[dev].d_fd, SOUND_PCM_GETOSPACE, &ainfo) < 0) + { + fprintf(stderr, "OSS: ioctl on output device %d, fd %d failed", + dev, linux_dacs[dev].d_fd); + break; + } + linux_dacs[dev].d_space = ainfo.bytes; + } + } + /* 3. if any DAC devices are too far ahead, plan to drop the + number of frames which will let the others catch up. */ + for (dev = 0; dev < linux_noutdevs; dev++) + { + if (linux_dacs[dev].d_space > linux_dacs[dev].d_bufsize - + (linux_advance_samples - 1) * linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp) + { + linux_dacs[dev].d_dropcount = linux_advance_samples - 1 - + (linux_dacs[dev].d_space - linux_dacs[dev].d_bufsize) / + (linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp) ; + } + else linux_dacs[dev].d_dropcount = 0; + } +} + +int oss_send_dacs(void) +{ + float *fp1, *fp2; + long fill; + int i, j, dev, rtnval = SENDDACS_YES; + char buf[OSS_MAXSAMPLEWIDTH * DACBLKSIZE * OSS_MAXCHPERDEV]; + t_oss_int16 *sp; + t_oss_int32 *lp; + /* the maximum number of samples we should have in the ADC buffer */ + int idle = 0; + int thischan; + double timeref, timenow; + + if (!linux_nindevs && !linux_noutdevs) + return (SENDDACS_NO); + + if (!oss_blockmode) + { + /* determine whether we're idle. This is true if either (1) + some input device has less than one buffer to read or (2) some + output device has fewer than (linux_advance_samples) blocks buffered + already. */ + oss_calcspace(); + + for (dev=0; dev < linux_noutdevs; dev++) + if (linux_dacs[dev].d_dropcount || + (linux_dacs[dev].d_bufsize - linux_dacs[dev].d_space > + linux_advance_samples * linux_dacs[dev].d_bytespersamp * + linux_dacs[dev].d_nchannels)) + idle = 1; + for (dev=0; dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space < + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + idle = 1; + } + + if (idle && !oss_blockmode) + { + /* sometimes---rarely---when the ADC available-byte-count is + zero, it's genuine, but usually it's because we're so + late that the ADC has overrun its entire kernel buffer. We + distinguish between the two by waiting 2 msec and asking again. + There should be an error flag we could check instead; look for this + someday... */ + for (dev = 0;dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space == 0) + { + audio_buf_info ainfo; + sys_microsleep(2000); + oss_calcspace(); + if (linux_adcs[dev].d_space != 0) continue; + + /* here's the bad case. Give up and resync. */ + sys_log_error(ERR_DATALATE); + oss_doresync(); + return (SENDDACS_NO); + } + /* check for slippage between devices, either because + data got lost in the driver from a previous late condition, or + because the devices aren't synced. When we're idle, no + input device should have more than one buffer readable and + no output device should have less than linux_advance_samples-1 + */ + + for (dev=0; dev < linux_noutdevs; dev++) + if (!linux_dacs[dev].d_dropcount && + (linux_dacs[dev].d_bufsize - linux_dacs[dev].d_space < + (linux_advance_samples - 2) * + (linux_dacs[dev].d_bytespersamp * + linux_dacs[dev].d_nchannels))) + goto badsync; + for (dev=0; dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space > 3 * + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + goto badsync; + + /* return zero to tell the scheduler we're idle. */ + return (SENDDACS_NO); + badsync: + sys_log_error(ERR_RESYNC); + oss_doresync(); + return (SENDDACS_NO); + + } + + /* do output */ + + timeref = sys_getrealtime(); + for (dev=0, thischan = 0; dev < linux_noutdevs; dev++) + { + int nchannels = linux_dacs[dev].d_nchannels; + if (linux_dacs[dev].d_dropcount) + linux_dacs[dev].d_dropcount--; + else + { + if (linux_dacs[dev].d_bytespersamp == 4) + { + for (i = DACBLKSIZE * nchannels, fp1 = sys_soundout + + DACBLKSIZE*thischan, + lp = (t_oss_int32 *)buf; i--; fp1++, lp++) + { + float f = *fp1 * 2147483648.; + *lp = (f >= 2147483647. ? 2147483647. : + (f < -2147483648. ? -2147483648. : f)); + } + } + else + { + for (i = DACBLKSIZE, fp1 = sys_soundout + + DACBLKSIZE*thischan, + sp = (t_oss_int16 *)buf; i--; fp1++, sp += nchannels) + { + for (j=0, fp2 = fp1; j 32767) s = 32767; + else if (s < -32767) s = -32767; + sp[j] = s; + } + } + } + linux_dacs_write(linux_dacs[dev].d_fd, buf, + OSS_XFERSIZE(nchannels, linux_dacs[dev].d_bytespersamp)); + if ((timenow = sys_getrealtime()) - timeref > 0.002) + { + if (!oss_blockmode) + sys_log_error(ERR_DACSLEPT); + else rtnval = SENDDACS_SLEPT; + } + timeref = timenow; + } + thischan += nchannels; + } + memset(sys_soundout, 0, + linux_outchannels * (sizeof(float) * DACBLKSIZE)); + + /* do input */ + + for (dev = 0, thischan = 0; dev < linux_nindevs; dev++) + { + int nchannels = linux_adcs[dev].d_nchannels; + linux_adcs_read(linux_adcs[dev].d_fd, buf, + OSS_XFERSIZE(nchannels, linux_adcs[dev].d_bytespersamp)); + + if ((timenow = sys_getrealtime()) - timeref > 0.002) + { + if (!oss_blockmode) + sys_log_error(ERR_ADCSLEPT); + else + rtnval = SENDDACS_SLEPT; + } + timeref = timenow; + + if (linux_adcs[dev].d_bytespersamp == 4) + { + for (i = DACBLKSIZE*nchannels, + fp1 = sys_soundin + thischan*DACBLKSIZE, + lp = (t_oss_int32 *)buf; i--; fp1++, lp++) + { + *fp1 = ((float)(*lp))*(float)(1./2147483648.); + } + } + else + { + for (i = DACBLKSIZE,fp1 = sys_soundin + thischan*DACBLKSIZE, + sp = (t_oss_int16 *)buf; i--; fp1++, sp += nchannels) + { + for (j=0;j channelinfo.max_voices) + post("decreasing input channels to maximum of %d\n", + wantinchans = channelinfo.max_voices); + if (alsa_samplewidth == 4 && + !(channelinfo.formats & (1< channelinfo.max_voices) + post("decreasing output channels to maximum of %d\n", + wantoutchans = channelinfo.max_voices); + if (alsa_samplewidth == 4 && + !(channelinfo.formats & (1< linux_outchannels ? linux_inchannels : + linux_outchannels) * DACBLKSIZE; + alsa_buf = malloc(bsize); + if (!alsa_buf) + return (1); + memset(alsa_buf, 0, bsize); + return 0; +} + +void alsa_set_params(t_alsa_dev *dev, int dir, int rate, int voices) +{ + int err; + struct snd_pcm_channel_params params; + + memset(&dev->info, 0, sizeof(dev->info)); + dev->info.channel = dir; + if ((err = snd_pcm_channel_info(dev->handle, &dev->info) < 0)) + { + fprintf(stderr, "PD-ALSA: error getting channel info: %s\n", + snd_strerror(err)); + } + memset(¶ms, 0, sizeof(params)); + params.format.interleave = 1; /* may do non-interleaved later */ + /* format is 2 or 4 bytes per sample depending on what was possible */ + params.format.format = + (alsa_samplewidth == 4 ? SND_PCM_SFMT_S32_LE : SND_PCM_SFMT_S16_LE); + + /*will check this further down -just try for now*/ + params.format.rate = rate; + params.format.voices = voices; + params.start_mode = SND_PCM_START_GO; /* seems most reliable */ + /*do not stop at overrun/underrun*/ + params.stop_mode = SND_PCM_STOP_ROLLOVER; + + params.channel = dir; /* playback|capture */ + params.buf.stream.queue_size = + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * voices; + params.buf.stream.fill = SND_PCM_FILL_SILENCE_WHOLE; + params.mode = SND_PCM_MODE_STREAM; + + if ((err = snd_pcm_channel_params(dev->handle, ¶ms)) < 0) + { + printf("PD-ALSA: error setting parameters %s", snd_strerror(err)); + } + + /* This should clear the buffers but does not. There is often noise at + startup that sounds like crap left in the buffers - maybe in the lib + instead of the driver? Some solution needs to be found. + */ + + if ((err = snd_pcm_channel_prepare(dev->handle, dir)) < 0) + { + printf("PD-ALSA: error preparing channel %s", snd_strerror(err)); + } + dev->setup.channel = dir; + + if ((err = snd_pcm_channel_setup(dev->handle, &dev->setup)) < 0) + { + printf("PD-ALSA: error getting setup %s", snd_strerror(err)); + } + /* for some reason, if you don't writesomething before starting the + converters we get trash on startup */ + if (dir == SND_PCM_CHANNEL_PLAYBACK) + { + char foo[1024]; + int xxx = 1024 - (1024 % (linux_outchannels * alsa_samplewidth)); + int i, r; + for (i = 0; i < xxx; i++) + foo[i] = 0; + if ((r = snd_pcm_write(dev->handle, foo, xxx)) < xxx) + fprintf(stderr, "alsa_write: %s\n", snd_strerror(errno)); + } + snd_pcm_channel_go(dev->handle, dir); +} + +void alsa_close_audio(void) +{ + int i; + for(i = 0; i < n_alsa_dev; i++) + snd_pcm_close(alsa_device[i].handle); +} + +/* #define DEBUG_ALSA_XFER */ + +int alsa_send_dacs(void) +{ + static int16_t *sp; + t_sample *fp, *fp1, *fp2; + int i, j, k, err, devno = 0; + int inputcount = 0, outputcount = 0, inputlate = 0, outputlate = 0; + int result; + snd_pcm_channel_status_t stat; + static int callno = 0; + static int xferno = 0; + int countwas = 0; + double timelast; + static double timenow; + int inchannels = linux_inchannels; + int outchannels = linux_outchannels; + int inbytesperframe = inchannels * alsa_samplewidth; + int outbytesperframe = outchannels * alsa_samplewidth; + int intransfersize = DACBLKSIZE * inbytesperframe; + int outtransfersize = DACBLKSIZE * outbytesperframe; + int alsaerror; + int loggederror = 0; + + if (!inchannels && !outchannels) + return (SENDDACS_NO); + timelast = timenow; + timenow = sys_getrealtime(); + +#ifdef DEBUG_ALSA_XFER + if (timenow - timelast > 0.050) + fprintf(stderr, "(%d)", + (int)(1000 * (timenow - timelast))), fflush(stderr); +#endif + + callno++; + /* get input and output channel status */ + if (inchannels > 0) + { + devno = 0; + stat.channel = SND_PCM_CHANNEL_CAPTURE; + if (alsaerror = snd_pcm_channel_status(alsa_device[devno].handle, + &stat)) + { + fprintf(stderr, "snd_pcm_channel_status (input): %s\n", + snd_strerror(alsaerror)); + return (SENDDACS_NO); + } + inputcount = stat.count; + inputlate = (stat.underrun > 0 || stat.overrun > 0); + } + if (outchannels > 0) + { + devno = 0; + stat.channel = SND_PCM_CHANNEL_PLAYBACK; + if (alsaerror = snd_pcm_channel_status(alsa_device[devno].handle, + &stat)) + { + fprintf(stderr, "snd_pcm_channel_status (output): %s\n", + snd_strerror(alsaerror)); + return (SENDDACS_NO); + } + outputcount = stat.count; + outputlate = (stat.underrun > 0 || stat.overrun > 0); + } + + /* check if input not ready */ + if (inputcount < intransfersize) + { + /* fprintf(stderr, "no adc; count %d, free %d, call %d, xfer %d\n", + stat.count, + stat.free, + callno, xferno); */ + if (outchannels > 0) + { + /* if there's no input but output is hungry, feed output. */ + while (outputcount < (linux_advance_samples + ALSA_JITTER) + * outbytesperframe) + { + if (!loggederror) + sys_log_error(ERR_RESYNC), loggederror = 1; + memset(alsa_buf, 0, outtransfersize); + result = snd_pcm_write(alsa_device[devno].handle, + alsa_buf, outtransfersize); + if (result < outtransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result >= 0 || errno == EAGAIN) + fprintf(stderr, "ALSA: write returned %d of %d\n", + result, outtransfersize); + else fprintf(stderr, "ALSA: write: %s\n", + snd_strerror(errno)); + fprintf(stderr, + "inputcount %d, outputcount %d, outbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * outchannels); +#endif + return (SENDDACS_NO); + } + stat.channel = SND_PCM_CHANNEL_PLAYBACK; + if (alsaerror = + snd_pcm_channel_status(alsa_device[devno].handle, + &stat)) + { + fprintf(stderr, "snd_pcm_channel_status (output): %s\n", + snd_strerror(alsaerror)); + return (SENDDACS_NO); + } + outputcount = stat.count; + } + } + + return SENDDACS_NO; + } + + /* if output buffer has at least linux_advance_samples in it, we're + not ready for this batch. */ + if (outputcount > linux_advance_samples * outbytesperframe) + { + if (inchannels > 0) + { + while (inputcount > (DACBLKSIZE + ALSA_JITTER) * outbytesperframe) + { + if (!loggederror) + sys_log_error(ERR_RESYNC), loggederror = 1; + devno = 0; + result = snd_pcm_read(alsa_device[devno].handle, alsa_buf, + intransfersize); + if (result < intransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result < 0) + fprintf(stderr, + "snd_pcm_read %d %d: %s\n", + callno, xferno, snd_strerror(errno)); + else fprintf(stderr, + "snd_pcm_read %d %d returned only %d\n", + callno, xferno, result); + fprintf(stderr, + "inputcount %d, outputcount %d, inbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * inchannels); +#endif + return (SENDDACS_NO); + } + devno = 0; + stat.channel = SND_PCM_CHANNEL_CAPTURE; + if (alsaerror = + snd_pcm_channel_status(alsa_device[devno].handle, + &stat)) + { + fprintf(stderr, "snd_pcm_channel_status (input): %s\n", + snd_strerror(alsaerror)); + return (SENDDACS_NO); + } + inputcount = stat.count; + inputlate = (stat.underrun > 0 || stat.overrun > 0); + } + return (SENDDACS_NO); + } + } + if (sys_getrealtime() - timenow > 0.002) + { +#ifdef DEBUG_ALSA_XFER + fprintf(stderr, "check %d took %d msec\n", + callno, (int)(1000 * (timenow - timelast))), fflush(stderr); +#endif + sys_log_error(ERR_DACSLEPT); + timenow = sys_getrealtime(); + } + if (inputlate || outputlate) + sys_log_error(ERR_DATALATE); + + /* do output */ + /* this "for" loop won't work for more than one device. */ + for (devno = 0, fp = sys_soundout; devno < (outchannels > 0); devno++, + fp += 128) + { + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + float s1 = *fp2 * INT32_MAX; + ((t_alsa_sample32 *)alsa_buf)[j] = CLIP32(s1); + } + } + } + else + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + int s = *fp2 * 32767.; + if (s > 32767) + s = 32767; + else if (s < -32767) + s = -32767; + ((t_alsa_sample16 *)alsa_buf)[j] = s; + } + } + } + + result = snd_pcm_write(alsa_device[devno].handle, alsa_buf, + outtransfersize); + if (result < outtransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result >= 0 || errno == EAGAIN) + fprintf(stderr, "ALSA: write returned %d of %d\n", + result, outtransfersize); + else fprintf(stderr, "ALSA: write: %s\n", + snd_strerror(errno)); + fprintf(stderr, + "inputcount %d, outputcount %d, outbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * outchannels); +#endif + sys_log_error(ERR_DACSLEPT); + return (SENDDACS_NO); + } + } + /* zero out the output buffer */ + memset(sys_soundout, 0, DACBLKSIZE * sizeof(*sys_soundout) * + linux_outchannels); + if (sys_getrealtime() - timenow > 0.002) + { +#if DEBUG_ALSA_XFER + fprintf(stderr, "output %d took %d msec\n", + callno, (int)(1000 * (timenow - timelast))), fflush(stderr); +#endif + timenow = sys_getrealtime(); + sys_log_error(ERR_DACSLEPT); + } + + /* do input */ + for (devno = 0, fp = sys_soundin; devno < (linux_inchannels > 0); devno++, + fp += 128) + { + result = snd_pcm_read(alsa_device[devno].handle, alsa_buf, + intransfersize); + if (result < intransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result < 0) + fprintf(stderr, + "snd_pcm_read %d %d: %s\n", + callno, xferno, snd_strerror(errno)); + else fprintf(stderr, + "snd_pcm_read %d %d returned only %d\n", + callno, xferno, result); + fprintf(stderr, + "inputcount %d, outputcount %d, inbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * inchannels); +#endif + sys_log_error(ERR_ADCSLEPT); + return (SENDDACS_NO); + } + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += inchannels, fp2++) + *fp2 = (float) ((t_alsa_sample32 *)alsa_buf)[j] + * (1./ INT32_MAX); + } + } + else + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; j += inchannels, fp2++) + *fp2 = (float) ((t_alsa_sample16 *)alsa_buf)[j] + * 3.051850e-05; + } + } + } + xferno++; + if (sys_getrealtime() - timenow > 0.002) + { +#ifdef DEBUG_ALSA_XFER + fprintf(stderr, "routine took %d msec\n", + (int)(1000 * (sys_getrealtime() - timenow))); +#endif + sys_log_error(ERR_ADCSLEPT); + } + return SENDDACS_YES; +} + +#endif /* ALSA99 */ + +/* support for ALSA pcmv2 api by Karl MacMillan */ + +#ifdef ALSA01 + +static void check_error(int err, const char *why) +{ + if (err < 0) + fprintf(stderr, "%s: %s\n", why, snd_strerror(err)); +} + +static int alsa_open_audio(int wantinchans, int wantoutchans, int srate) +{ + int err, inchans = 0, outchans = 0, subunitdir; + char devname[512]; + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + snd_output_t* out; + int frag_size = (linux_fragsize ? linux_fragsize : ALSA_DEFFRAGSIZE); + int nfrags, i; + short* tmp_buf; + unsigned int tmp_uint; + int advwas = sys_schedadvance; + + if (linux_nfragment) + { + nfrags = linux_nfragment; + sys_schedadvance = (frag_size * linux_nfragment * 1.0e6) / srate; + } + else nfrags = sys_schedadvance * (float)srate / (1e6 * frag_size); + + if (sys_verbose || (sys_schedadvance != advwas)) + post("audio buffer set to %d", (int)(0.001 * sys_schedadvance)); + if (wantinchans || wantoutchans) + alsa_checkversion(); + if (wantinchans) + { + err = snd_pcm_open(&alsa_device.inhandle, alsa_devname, + SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + + check_error(err, "snd_pcm_open (input)"); + if (err < 0) + inchans = 0; + else + { + inchans = wantinchans; + snd_pcm_nonblock(alsa_device.inhandle, 1); + } + } + if (wantoutchans) + { + err = snd_pcm_open(&alsa_device.outhandle, alsa_devname, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + check_error(err, "snd_pcm_open (output)"); + if (err < 0) + outchans = 0; + else + { + outchans = wantoutchans; + snd_pcm_nonblock(alsa_device.outhandle, 1); + } + } + if (inchans) + { + if (sys_verbose) + post("opening sound input..."); + err = snd_pcm_hw_params_malloc(&hw_params); + check_error(err, "snd_pcm_hw_params_malloc (input)"); + + // get the default params + err = snd_pcm_hw_params_any(alsa_device.inhandle, hw_params); + check_error(err, "snd_pcm_hw_params_any (input)"); + // set interleaved access - FIXME deal with other access types + err = snd_pcm_hw_params_set_access(alsa_device.inhandle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + check_error(err, "snd_pcm_hw_params_set_access (input)"); + // Try to set 32 bit format first + err = snd_pcm_hw_params_set_format(alsa_device.inhandle, hw_params, + SND_PCM_FORMAT_S32); + if (err < 0) + { + /* fprintf(stderr, + "PD-ALSA: 32 bit format not available - using 16\n"); */ + err = snd_pcm_hw_params_set_format(alsa_device.inhandle, hw_params, + SND_PCM_FORMAT_S16); + check_error(err, "snd_pcm_hw_params_set_format (input)"); + alsa_samplewidth = 2; + } + else + { + alsa_samplewidth = 4; + } + post("Sample width set to %d bytes", alsa_samplewidth); + // set the subformat + err = snd_pcm_hw_params_set_subformat(alsa_device.inhandle, hw_params, + SND_PCM_SUBFORMAT_STD); + check_error(err, "snd_pcm_hw_params_set_subformat (input)"); + // set the number of channels + tmp_uint = inchans; + err = snd_pcm_hw_params_set_channels_min(alsa_device.inhandle, + hw_params, &tmp_uint); + check_error(err, "snd_pcm_hw_params_set_channels (input)"); + if (tmp_uint != (unsigned)inchans) + post("ALSA: set input channels to %d", tmp_uint); + inchans = tmp_uint; + // set the sampling rate + err = snd_pcm_hw_params_set_rate_min(alsa_device.inhandle, hw_params, + &srate, 0); + check_error(err, "snd_pcm_hw_params_set_rate_min (input)"); +#if 0 + err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); + post("input sample rate %d", err); +#endif + // set the period - ie frag size + // post("fragsize a %d", frag_size); + + /* LATER try this to get a recommended period size... + right now, it trips an assertion failure in ALSA lib */ +#if 0 + post("input period was %d, min %d, max %d\n", + snd_pcm_hw_params_get_period_size(hw_params, 0), + snd_pcm_hw_params_get_period_size_min(hw_params, 0), + snd_pcm_hw_params_get_period_size_max(hw_params, 0)); +#endif + err = snd_pcm_hw_params_set_period_size_near(alsa_device.inhandle, + hw_params, + (snd_pcm_uframes_t) + frag_size, 0); + check_error(err, "snd_pcm_hw_params_set_period_size_near (input)"); + // post("fragsize b %d", frag_size); + // set the number of periods - ie numfrags + // post("nfrags a %d", nfrags); + err = snd_pcm_hw_params_set_periods_near(alsa_device.inhandle, + hw_params, nfrags, 0); + check_error(err, "snd_pcm_hw_params_set_periods_near (input)"); + // set the buffer size + err = snd_pcm_hw_params_set_buffer_size_near(alsa_device.inhandle, + hw_params, nfrags * frag_size); + check_error(err, "snd_pcm_hw_params_set_buffer_size_near (input)"); + + err = snd_pcm_hw_params(alsa_device.inhandle, hw_params); + check_error(err, "snd_pcm_hw_params (input)"); + + snd_pcm_hw_params_free(hw_params); + + err = snd_pcm_sw_params_malloc(&sw_params); + check_error(err, "snd_pcm_sw_params_malloc (input)"); + err = snd_pcm_sw_params_current(alsa_device.inhandle, sw_params); + check_error(err, "snd_pcm_sw_params_current (input)"); +#if 1 + err = snd_pcm_sw_params_set_start_mode(alsa_device.inhandle, sw_params, + SND_PCM_START_EXPLICIT); + check_error(err, "snd_pcm_sw_params_set_start_mode (input)"); + err = snd_pcm_sw_params_set_xrun_mode(alsa_device.inhandle, sw_params, + SND_PCM_XRUN_NONE); + check_error(err, "snd_pcm_sw_params_set_xrun_mode (input)"); +#else + err = snd_pcm_sw_params_set_start_threshold(alsa_device.inhandle, + sw_params, nfrags * frag_size); + check_error(err, "snd_pcm_sw_params_set_start_threshold (input)"); + err = snd_pcm_sw_params_set_stop_threshold(alsa_device.inhandle, + sw_params, 1); + check_error(err, "snd_pcm_sw_params_set_stop_threshold (input)"); +#endif + + err = snd_pcm_sw_params_set_avail_min(alsa_device.inhandle, sw_params, + frag_size); + check_error(err, "snd_pcm_sw_params_set_avail_min (input)"); + err = snd_pcm_sw_params(alsa_device.inhandle, sw_params); + check_error(err, "snd_pcm_sw_params (input)"); + + snd_pcm_sw_params_free(sw_params); + + snd_output_stdio_attach(&out, stderr, 0); +#if 0 + if (sys_verbose) + { + snd_pcm_dump_hw_setup(alsa_device.inhandle, out); + snd_pcm_dump_sw_setup(alsa_device.inhandle, out); + } +#endif + } + + if (outchans) + { + int foo; + if (sys_verbose) + post("opening sound output..."); + err = snd_pcm_hw_params_malloc(&hw_params); + check_error(err, "snd_pcm_sw_params (output)"); + + // get the default params + err = snd_pcm_hw_params_any(alsa_device.outhandle, hw_params); + check_error(err, "snd_pcm_hw_params_any (output)"); + // set interleaved access - FIXME deal with other access types + err = snd_pcm_hw_params_set_access(alsa_device.outhandle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + check_error(err, "snd_pcm_hw_params_set_access (output)"); + // Try to set 32 bit format first + err = snd_pcm_hw_params_set_format(alsa_device.outhandle, hw_params, + SND_PCM_FORMAT_S32); + if (err < 0) + { + err = snd_pcm_hw_params_set_format(alsa_device.outhandle, + hw_params,SND_PCM_FORMAT_S16); + check_error(err, "snd_pcm_hw_params_set_format (output)"); + /* fprintf(stderr, + "PD-ALSA: 32 bit format not available - using 16\n"); */ + alsa_samplewidth = 2; + } + else + { + alsa_samplewidth = 4; + } + // set the subformat + err = snd_pcm_hw_params_set_subformat(alsa_device.outhandle, hw_params, + SND_PCM_SUBFORMAT_STD); + check_error(err, "snd_pcm_hw_params_set_subformat (output)"); + // set the number of channels + tmp_uint = outchans; + err = snd_pcm_hw_params_set_channels_min(alsa_device.outhandle, + hw_params, &tmp_uint); + check_error(err, "snd_pcm_hw_params_set_channels (output)"); + if (tmp_uint != (unsigned)outchans) + post("alsa: set output channels to %d", tmp_uint); + outchans = tmp_uint; + // set the sampling rate + err = snd_pcm_hw_params_set_rate_min(alsa_device.outhandle, hw_params, + &srate, 0); + check_error(err, "snd_pcm_hw_params_set_rate_min (output)"); +#if 0 + err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); + post("output sample rate %d", err); +#endif + // set the period - ie frag size +#if 0 + post("output period was %d, min %d, max %d\n", + snd_pcm_hw_params_get_period_size(hw_params, 0), + snd_pcm_hw_params_get_period_size_min(hw_params, 0), + snd_pcm_hw_params_get_period_size_max(hw_params, 0)); +#endif + // post("fragsize c %d", frag_size); + err = snd_pcm_hw_params_set_period_size_near(alsa_device.outhandle, + hw_params, + (snd_pcm_uframes_t) + frag_size, 0); + // post("fragsize d %d", frag_size); + check_error(err, "snd_pcm_hw_params_set_period_size_near (output)"); + // set the number of periods - ie numfrags + err = snd_pcm_hw_params_set_periods_near(alsa_device.outhandle, + hw_params, nfrags, 0); + check_error(err, "snd_pcm_hw_params_set_periods_near (output)"); + // set the buffer size + err = snd_pcm_hw_params_set_buffer_size_near(alsa_device.outhandle, + hw_params, nfrags * frag_size); + + check_error(err, "snd_pcm_hw_params_set_buffer_size_near (output)"); + + err = snd_pcm_hw_params(alsa_device.outhandle, hw_params); + check_error(err, "snd_pcm_hw_params (output)"); + + snd_pcm_hw_params_free(hw_params); + + err = snd_pcm_sw_params_malloc(&sw_params); + check_error(err, "snd_pcm_sw_params_malloc (output)"); + err = snd_pcm_sw_params_current(alsa_device.outhandle, sw_params); + check_error(err, "snd_pcm_sw_params_current (output)"); +#if 1 + err = snd_pcm_sw_params_set_start_mode(alsa_device.outhandle, + sw_params, + SND_PCM_START_EXPLICIT); + check_error(err, "snd_pcm_sw_params_set_start_mode (output)"); + err = snd_pcm_sw_params_set_xrun_mode(alsa_device.outhandle, sw_params, + SND_PCM_XRUN_NONE); + check_error(err, "snd_pcm_sw_params_set_xrun_mode (output)"); +#else + err = snd_pcm_sw_params_set_start_threshold(alsa_device.inhandle, + sw_params, nfrags * frag_size); + check_error(err, "snd_pcm_sw_params_set_start_threshold (output)"); + err = snd_pcm_sw_params_set_stop_threshold(alsa_device.inhandle, + sw_params, 1); + check_error(err, "snd_pcm_sw_params_set_stop_threshold (output)"); +#endif + + err = snd_pcm_sw_params_set_avail_min(alsa_device.outhandle, sw_params, + frag_size); + check_error(err, "snd_pcm_sw_params_set_avail_min (output)"); + err = snd_pcm_sw_params(alsa_device.outhandle, sw_params); + check_error(err, "snd_pcm_sw_params (output)"); + + snd_pcm_sw_params_free(sw_params); + + snd_output_stdio_attach(&out, stderr, 0); +#if 0 + if (sys_verbose) + { + snd_pcm_dump_hw_setup(alsa_device.outhandle, out); + snd_pcm_dump_sw_setup(alsa_device.outhandle, out); + } +#endif + } + + linux_setsr(srate); + linux_setch(inchans, outchans); + + if (inchans) + snd_pcm_prepare(alsa_device.inhandle); + if (outchans) + snd_pcm_prepare(alsa_device.outhandle); + + // if duplex we can link the channels so they start together + if (inchans && outchans) + snd_pcm_link(alsa_device.inhandle, alsa_device.outhandle); + + // set up the buffer + if (outchans > inchans) + alsa_buf = (short *)calloc(sizeof(char) * alsa_samplewidth, DACBLKSIZE + * outchans); + else + alsa_buf = (short *)calloc(sizeof(char) * alsa_samplewidth, DACBLKSIZE + * inchans); + // fill the buffer with silence + if (outchans) + { + i = nfrags + 1; + while (i--) + snd_pcm_writei(alsa_device.outhandle, alsa_buf, frag_size); + } + + // set up the status variables + err = snd_pcm_status_malloc(&in_status); + check_error(err, "snd_pcm_status_malloc"); + err = snd_pcm_status_malloc(&out_status); + check_error(err, "snd_pcm_status_malloc"); + + // start the device +#if 1 + if (outchans) + { + err = snd_pcm_start(alsa_device.outhandle); + check_error(err, "snd_pcm_start"); + } + else if (inchans) + { + err = snd_pcm_start(alsa_device.inhandle); + check_error(err, "snd_pcm_start"); + } +#endif + + return 0; +} + +void alsa_close_audio(void) +{ + int err; + if (linux_inchannels) + { + err = snd_pcm_close(alsa_device.inhandle); + check_error(err, "snd_pcm_close (input)"); + } + if (linux_outchannels) + { + err = snd_pcm_close(alsa_device.outhandle); + check_error(err, "snd_pcm_close (output)"); + } +} + +// #define DEBUG_ALSA_XFER + +int alsa_send_dacs(void) +{ + static int16_t *sp; + static int xferno = 0; + static int callno = 0; + static double timenow; + double timelast; + t_sample *fp, *fp1, *fp2; + int i, j, k, err, devno = 0; + int inputcount = 0, outputcount = 0, inputlate = 0, outputlate = 0; + int result; + int inchannels = linux_inchannels; + int outchannels = linux_outchannels; + unsigned int intransfersize = DACBLKSIZE; + unsigned int outtransfersize = DACBLKSIZE; + + // get the status + if (!inchannels && !outchannels) + { + return SENDDACS_NO; + } + + timelast = timenow; + timenow = sys_getrealtime(); + +#ifdef DEBUG_ALSA_XFER + if (timenow - timelast > 0.050) + fprintf(stderr, "(%d)", + (int)(1000 * (timenow - timelast))), fflush(stderr); +#endif + + callno++; + + if (inchannels) + { + snd_pcm_status(alsa_device.inhandle, in_status); + if (snd_pcm_status_get_avail(in_status) < intransfersize) + return SENDDACS_NO; + } + if (outchannels) + { + snd_pcm_status(alsa_device.outhandle, out_status); + if (snd_pcm_status_get_avail(out_status) < outtransfersize) + return SENDDACS_NO; + } + + /* do output */ + if (outchannels) + { + fp = sys_soundout; + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + float s1 = *fp2 * INT32_MAX; + ((t_alsa_sample32 *)alsa_buf)[j] = CLIP32(s1); + } + } + } + else + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + int s = *fp2 * 32767.; + if (s > 32767) + s = 32767; + else if (s < -32767) + s = -32767; + ((t_alsa_sample16 *)alsa_buf)[j] = s; + } + } + } + + result = snd_pcm_writei(alsa_device.outhandle, alsa_buf, + outtransfersize); + if (result != (int)outtransfersize) + { + #ifdef DEBUG_ALSA_XFER + if (result >= 0 || errno == EAGAIN) + fprintf(stderr, "ALSA: write returned %d of %d\n", + result, outtransfersize); + else fprintf(stderr, "ALSA: write: %s\n", + snd_strerror(errno)); + fprintf(stderr, + "inputcount %d, outputcount %d, outbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * outchannels); + #endif + sys_log_error(ERR_DACSLEPT); + return (SENDDACS_NO); + } + + /* zero out the output buffer */ + memset(sys_soundout, 0, DACBLKSIZE * sizeof(*sys_soundout) * + linux_outchannels); + if (sys_getrealtime() - timenow > 0.002) + { + #ifdef DEBUG_ALSA_XFER + fprintf(stderr, "output %d took %d msec\n", + callno, (int)(1000 * (timenow - timelast))), fflush(stderr); + #endif + timenow = sys_getrealtime(); + sys_log_error(ERR_DACSLEPT); + } + } + /* do input */ + if (linux_inchannels) + { + result = snd_pcm_readi(alsa_device.inhandle, alsa_buf, intransfersize); + if (result < (int)intransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result < 0) + fprintf(stderr, + "snd_pcm_read %d %d: %s\n", + callno, xferno, snd_strerror(errno)); + else fprintf(stderr, + "snd_pcm_read %d %d returned only %d\n", + callno, xferno, result); + fprintf(stderr, + "inputcount %d, outputcount %d, inbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + linux_advance_samples) + * alsa_samplewidth * inchannels); +#endif + sys_log_error(ERR_ADCSLEPT); + return (SENDDACS_NO); + } + fp = sys_soundin; + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; + j += inchannels, fp2++) + *fp2 = (float) ((t_alsa_sample32 *)alsa_buf)[j] + * (1./ INT32_MAX); + } + } + else + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DACBLKSIZE) + { + for (j = i, k = DACBLKSIZE, fp2 = fp1; k--; j += inchannels, + fp2++) + *fp2 = (float) ((t_alsa_sample16 *)alsa_buf)[j] + * 3.051850e-05; + } + } + } + xferno++; + if (sys_getrealtime() - timenow > 0.002) + { +#ifdef DEBUG_ALSA_XFER + fprintf(stderr, "routine took %d msec\n", + (int)(1000 * (sys_getrealtime() - timenow))); +#endif + sys_log_error(ERR_ADCSLEPT); + } + return SENDDACS_YES; +} + +void alsa_resync( void) +{ + int i, result; + if (linux_whichapi != API_ALSA) + { + error("restart-audio: implemented for ALSA only."); + return; + } + memset(alsa_buf, 0, + sizeof(char) * alsa_samplewidth * DACBLKSIZE * linux_outchannels); + for (i = 0; i < 100; i++) + { + result = snd_pcm_writei(alsa_device.outhandle, alsa_buf, + DACBLKSIZE); + if (result != (int)DACBLKSIZE) + break; + } + post("%d written", i); +} + + +#endif /* ALSA01 */ + +/*************************************************** + * Code using the RME_9652 API + */ + + /* + trying native device for future use of native memory map: + because of busmaster if you dont use the dac, you dont need + CPU Power und also no nearly no CPU-Power is used in device + + since always all DAs and ADs are synced (else they wouldnt work) + we use linux_dacs[0], linux_adcs[0] + */ + +#ifdef RME_HAMMERFALL + +#define RME9652_MAX_CHANNELS 26 + +#define RME9652_CH_PER_NATIVE_DEVICE 1 + +static int rme9652_dac_devices[RME9652_MAX_CHANNELS]; +static int rme9652_adc_devices[RME9652_MAX_CHANNELS]; + +static char rme9652_dsp_dac[] = "/dev/rme9652/C0da%d"; +static char rme9652_dsp_adc[] = "/dev/rme9652/C0ad%d"; + +static int num_of_rme9652_dac = 0; +static int num_of_rme9652_adc = 0; + +static int rme_soundindevonset = 1; +static int rme_soundoutdevonset = 1; + +void rme_soundindev(int which) +{ + rme_soundindevonset = which; +} + +void rme_soundoutdev(int which) +{ + rme_soundoutdevonset = which; +} + +void rme9652_configure(int dev, int fd,int srate, int dac) { + int orig, param, nblk; + audio_buf_info ainfo; + orig = param = srate; + + /* samplerate */ + + fprintf(stderr,"RME9652: configuring %d, fd=%d, sr=%d\n, dac=%d\n", + dev,fd,srate,dac); + + if (ioctl(fd,SNDCTL_DSP_SPEED,¶m) == -1) + fprintf(stderr,"RME9652: Could not set sampling rate for device\n"); + else if( orig != param ) + fprintf(stderr,"RME9652: sampling rate: wanted %d, got %d\n", + orig, param ); + + // setting the correct samplerate (could be different than expected) + srate = param; + + + /* setting resolution */ + + /* use ctrlpanel to change, experiment, channels 1 */ + + orig = param = AFMT_S16_NE; + if (ioctl(fd,SNDCTL_DSP_SETFMT,¶m) == -1) + fprintf(stderr,"RME9652: Could not set DSP format\n"); + else if( orig != param ) + fprintf(stderr,"RME9652: DSP format: wanted %d, got %d\n",orig, param ); + + /* setting channels */ + orig = param = RME9652_CH_PER_NATIVE_DEVICE; + + if (ioctl(fd,SNDCTL_DSP_CHANNELS,¶m) == -1) + fprintf(stderr,"RME9652: Could not set channels\n"); + else if( orig != param ) + fprintf(stderr,"RME9652: num channels: wanted %d, got %d\n",orig, param ); + + if (dac) + { + + /* use "free space" to learn the buffer size. Normally you + should set this to your own desired value; but this seems not + to be implemented uniformly across different sound cards. LATER + we should figure out what to do if the requested scheduler advance + is greater than this buffer size; for now, we just print something + out. */ + + if( ioctl(linux_dacs[0].d_fd, SOUND_PCM_GETOSPACE,&ainfo) < 0 ) + fprintf(stderr,"RME: ioctl on output device %d failed",dev); + + linux_dacs[0].d_bufsize = ainfo.bytes; + + fprintf(stderr,"RME: ioctl SOUND_PCM_GETOSPACE says %d buffsize\n", + linux_dacs[0].d_bufsize); + + + if (linux_advance_samples * (RME_SAMPLEWIDTH * + RME9652_CH_PER_NATIVE_DEVICE) + > linux_dacs[0].d_bufsize - RME_BYTESPERCHAN) + { + fprintf(stderr, + "RME: requested audio buffer size %d limited to %d\n", + linux_advance_samples + * (RME_SAMPLEWIDTH * RME9652_CH_PER_NATIVE_DEVICE), + linux_dacs[0].d_bufsize); + linux_advance_samples = + (linux_dacs[0].d_bufsize - RME_BYTESPERCHAN) + / (RME_SAMPLEWIDTH *RME9652_CH_PER_NATIVE_DEVICE); + } + } +} + + +int rme9652_open_audio(int inchans, int outchans,int srate) +{ + int orig; + int tmp; + int inchannels = 0,outchannels = 0; + char devname[20]; + int i; + char buf[RME_SAMPLEWIDTH*RME9652_CH_PER_NATIVE_DEVICE*DACBLKSIZE]; + int num_devs = 0; + audio_buf_info ainfo; + + linux_nindevs = linux_noutdevs = 0; + + if (sys_verbose) + post("RME open"); + /* First check if we can */ + /* open the write ports */ + + for (num_devs=0; outchannels < outchans; num_devs++) + { + int channels = RME9652_CH_PER_NATIVE_DEVICE; + + sprintf(devname, rme9652_dsp_dac, num_devs + rme_soundoutdevonset); + if ((tmp = open(devname,O_WRONLY)) == -1) + { + DEBUG(fprintf(stderr,"RME9652: failed to open %s writeonly\n", + devname);) + break; + } + DEBUG(fprintf(stderr,"RME9652: out device Nr. %d (%d) on %s\n", + linux_noutdevs+1,tmp,devname);) + + if (outchans > outchannels) + { + rme9652_dac_devices[linux_noutdevs] = tmp; + linux_noutdevs++; + outchannels += channels; + } + else close(tmp); + } + if( linux_noutdevs > 0) + linux_dacs[0].d_fd = rme9652_dac_devices[0]; + + /* Second check if we can */ + /* open the read ports */ + + for (num_devs=0; inchannels < inchans; num_devs++) + { + int channels = RME9652_CH_PER_NATIVE_DEVICE; + + sprintf(devname, rme9652_dsp_adc, num_devs+rme_soundindevonset); + + if ((tmp = open(devname,O_RDONLY)) == -1) + { + DEBUG(fprintf(stderr,"RME9652: failed to open %s readonly\n", + devname);) + break; + } + DEBUG(fprintf(stderr,"RME9652: in device Nr. %d (%d) on %s\n", + linux_nindevs+1,tmp,devname);) + + if (inchans > inchannels) + { + rme9652_adc_devices[linux_nindevs] = tmp; + linux_nindevs++; + inchannels += channels; + } + else + close(tmp); + } + if(linux_nindevs > 0) + linux_adcs[0].d_fd = rme9652_adc_devices[0]; + + /* configure soundcards */ + + rme9652_configure(0, linux_adcs[0].d_fd,srate, 0); + rme9652_configure(0, linux_dacs[0].d_fd,srate, 1); + + /* We have to do a read to start the engine. This is + necessary because sys_send_dacs waits until the input + buffer is filled and only reads on a filled buffer. + This is good, because it's a way to make sure that we + will not block */ + + if (linux_nindevs) + { + fprintf(stderr,("RME9652: starting read engine ... ")); + + + for (num_devs=0; num_devs < linux_nindevs; num_devs++) + read(rme9652_adc_devices[num_devs], + buf, RME_SAMPLEWIDTH* RME9652_CH_PER_NATIVE_DEVICE* + DACBLKSIZE); + + + for (num_devs=0; num_devs < linux_noutdevs; num_devs++) + write(rme9652_dac_devices[num_devs], + buf, RME_SAMPLEWIDTH* RME9652_CH_PER_NATIVE_DEVICE* + DACBLKSIZE); + + if(linux_noutdevs) + ioctl(rme9652_dac_devices[0],SNDCTL_DSP_SYNC); + + fprintf(stderr,"done\n"); + } + + linux_setsr(srate); + linux_setch(linux_nindevs, linux_noutdevs); + + num_of_rme9652_dac = linux_noutdevs; + num_of_rme9652_adc = linux_nindevs; + + if(linux_noutdevs)linux_noutdevs=1; + if(linux_nindevs)linux_nindevs=1; + + /* trick RME9652 behaves as one device fromread write pointers */ + return (0); +} + +void rme9652_close_audio( void) +{ + int i; + for (i=0;i>2;i--;) + { + float s1 = *(fp1+=4) * INT32_MAX; + float s2 = *(fp2+=4) * INT32_MAX; + float s3 = *(fp3+=4) * INT32_MAX; + float s4 = *(fp4+=4) * INT32_MAX; + + *(a+=4) = CLIP32(s1); + *(b+=4) = CLIP32(s2); + *(c+=4) = CLIP32(s3); + *(d+=4) = CLIP32(s4); + } + + linux_dacs_write(rme9652_dac_devices[j],buf,RME_BYTESPERCHAN); + } + } + + if ((timenow = sys_getrealtime()) - timeref > 0.02) + sys_log_error(ERR_DACSLEPT); + timeref = timenow; + } + + memset(sys_soundout, 0, + linux_outchannels * (sizeof(float) * DACBLKSIZE)); + + /* do input */ + + if(linux_nindevs) { + + for(j=0;j 0.02) + sys_log_error(ERR_ADCSLEPT); + timeref = timenow; + { + t_rme_sample *a,*b,*c,*d; + float *fp1,*fp2,*fp3,*fp4; + + fp1 = sys_soundin + j*DACBLKSIZE-4; + fp2 = fp1 + 1; + fp3 = fp1 + 2; + fp4 = fp1 + 3; + a = buf-4; + b=a+1; + c=a+2; + d=a+3; + + for (i = (DACBLKSIZE>>2);i--;) + { + *(fp1+=4) = *(a+=4) * (float)(1./INT32_MAX); + *(fp2+=4) = *(b+=4) * (float)(1./INT32_MAX); + *(fp3+=4) = *(c+=4) * (float)(1./INT32_MAX); + *(fp4+=4) = *(d+=4) * (float)(1./INT32_MAX); + } + } + } + } + /* fprintf(stderr,"ready \n");*/ + + return (1); +} + +#endif /* RME_HAMMERFALL */ -- cgit v1.2.1