From 4d84d14ac1aa13958eaa2971b03f7f929a519105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Fri, 8 Feb 2008 13:00:32 +0000 Subject: reorganized svn path=/trunk/; revision=9400 --- desiredata/src/d_soundfile.c | 2059 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2059 insertions(+) create mode 100644 desiredata/src/d_soundfile.c (limited to 'desiredata/src/d_soundfile.c') diff --git a/desiredata/src/d_soundfile.c b/desiredata/src/d_soundfile.c new file mode 100644 index 00000000..2a281869 --- /dev/null +++ b/desiredata/src/d_soundfile.c @@ -0,0 +1,2059 @@ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* this file contains, first, a collection of soundfile access routines, a +sort of soundfile library. Second, the "soundfiler" object is defined which +uses the routines to read or write soundfiles, synchronously, from garrays. +These operations are not to be done in "real time" as they may have to wait +for disk accesses (even the write routine.) Finally, the realtime objects +readsf~ and writesf~ are defined which confine disk operations to a separate +thread so that they can be used in real time. The readsf~ and writesf~ +objects use Posix-like threads. */ + +/* threaded soundfiler by Tim Blechmann */ +// #define THREADED_SF + +#ifndef MSW +#include +#include +#endif +#include +#ifdef MSW +#include +#endif +#include +#include +#include +#include + +#define PD_PLUSPLUS_FACE +#include "desire.h" +#define a_symbol a_w.w_symbol +#define a_float a_w.w_float + +#define MAXSFCHANS 64 + +#ifdef _LARGEFILE64_SOURCE +# define open open64 +# define lseek lseek64 +#endif + +static bool debug=0; + +#define EAT_ARG(ATYPE,VAR) if (argc<1 || argv->a_type != ATYPE) goto usage; else {VAR = *argv++; argc--;} + +/***************** soundfile header structures ************************/ + +typedef unsigned short uint16; +typedef unsigned int uint32; /* long isn't 32-bit on amd64 */ + +#define FORMAT_WAVE 0 +#define FORMAT_AIFF 1 +#define FORMAT_NEXT 2 + +/* the NeXTStep sound header structure; can be big or little endian */ + +struct t_nextstep { + char fileid[4]; /* magic number '.snd' if file is big-endian */ + uint32 onset; /* byte offset of first sample */ + uint32 length; /* length of sound in bytes */ + uint32 format; /* format; see below */ + uint32 sr; /* sample rate */ + uint32 nchans; /* number of channels */ + char info[4]; /* comment */ +}; + +#define NS_FORMAT_LINEAR_16 3 +#define NS_FORMAT_LINEAR_24 4 +#define NS_FORMAT_FLOAT 6 +#define SCALE (1./(1024. * 1024. * 1024. * 2.)) + +/* the WAVE header. All Wave files are little endian. We assume + the "fmt" chunk comes first which is usually the case but perhaps not + always; same for AIFF and the "COMM" chunk. */ + +struct t_wave { + char fileid[4]; /* chunk id 'RIFF' */ + uint32 chunksize; /* chunk size */ + char waveid[4]; /* wave chunk id 'WAVE' */ + char fmtid[4]; /* format chunk id 'fmt ' */ + uint32 fmtchunksize; /* format chunk size */ + uint16 fmttag; /* format tag (WAV_INT etc) */ + uint16 nchannels; /* number of channels */ + uint32 samplespersec; /* sample rate in hz */ + uint32 navgbytespersec; /* average bytes per second */ + uint16 nblockalign; /* number of bytes per frame */ + uint16 nbitspersample; /* number of bits in a sample */ + char datachunkid[4]; /* data chunk id 'data' */ + uint32 datachunksize; /* length of data chunk */ +}; + +struct t_fmt { /* format chunk */ + uint16 fmttag; /* format tag, 1 for PCM */ + uint16 nchannels; /* number of channels */ + uint32 samplespersec; /* sample rate in hz */ + uint32 navgbytespersec; /* average bytes per second */ + uint16 nblockalign; /* number of bytes per frame */ + uint16 nbitspersample; /* number of bits in a sample */ +}; + +struct t_wavechunk { /* ... and the last two items */ + char id[4]; /* data chunk id, e.g., 'data' or 'fmt ' */ + uint32 size; /* length of data chunk */ +}; + +#define WAV_INT 1 +#define WAV_FLOAT 3 + +typedef unsigned char byte; + +/* the AIFF header. I'm assuming AIFC is compatible but don't really know that. */ + +struct t_datachunk { + char id[4]; // data chunk id 'SSND' + uint32 size; // length of data chunk + uint32 offset; // additional offset in bytes + uint32 block; // block size +}; + +struct t_comm { + uint16 nchannels; // number of channels + uint16 nframeshi; // # of sample frames (hi) + uint16 nframeslo; // # of sample frames (lo) + uint16 bitspersamp; // bits per sample + byte samprate[10];// sample rate, 80-bit float! +}; + +/* this version is more convenient for writing them out: */ +struct t_aiff { + char fileid[4]; // chunk id 'FORM' + uint32 chunksize; // chunk size + char aiffid[4]; // aiff chunk id 'AIFF' + char fmtid[4]; // format chunk id 'COMM' + uint32 fmtchunksize; // format chunk size, 18 + uint16 nchannels; // number of channels + uint16 nframeshi; // # of sample frames (hi) + uint16 nframeslo; // # of sample frames (lo) + uint16 bitspersamp; // bits per sample + byte samprate[10]; // sample rate, 80-bit float! +}; + +struct t_param { + int bytespersample; + int bigendian; + int nchannels; + long bytelimit; + int bytesperchannel() {return bytespersample * nchannels;} +}; + +#define AIFFHDRSIZE 38 /* probably not what sizeof() gives */ +#define AIFFPLUS (AIFFHDRSIZE + 16) /* header size including SSND chunk hdr */ +#define WHDR1 sizeof(t_nextstep) +#define WHDR2 (sizeof(t_wave) > WHDR1 ? sizeof (t_wave) : WHDR1) +#define WRITEHDRSIZE (AIFFPLUS > WHDR2 ? AIFFPLUS : WHDR2) +#define READHDRSIZE (16 > WHDR2 + 2 ? 16 : WHDR2 + 2) + +#ifdef MSW +#include +#define BINCREATE (_O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY) +#else +#define BINCREATE (O_WRONLY | O_CREAT | O_TRUNC) +#endif + +/* this routine returns 1 if the high order byte comes at the lower +address on our architecture (big-endianness.). It's 1 for Motorola, 0 for Intel: */ + +extern int garray_ambigendian(); + +/* byte swappers */ + +static uint32 swap4(uint32 n, int doit) { + if (doit) return ((n & 0xff) << 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24); + else return n; +} + +static uint16 swap2(uint32 n, int doit) { + if (doit) return ((n & 0xff) << 8) | ((n & 0xff00) >> 8); + else return n; +} + +static void swapstring(char *foo, int doit) { + if (doit) { + char a = foo[0], b = foo[1], c = foo[2], d = foo[3]; + foo[0] = d; foo[1] = c; foo[2] = b; foo[3] = a; + } +} + +/******************** soundfile access routines **********************/ +/* This routine opens a file, looks for either a nextstep or "wave" header, +* seeks to end of it, and fills in bytes per sample and number of channels. +* Only 2- and 3-byte fixed-point samples and 4-byte floating point samples +* are supported. If "headersize" is nonzero, the +* caller should supply the number of channels, endinanness, and bytes per +* sample; the header is ignored. Otherwise, the routine tries to read the +* header and fill in the properties. +*/ + +int open_soundfile_via_fd(int fd, int headersize, t_param *p, long skipframes) { + int swap, sysrtn; + errno = 0; + t_param q; + q.bytelimit = 0x7fffffff; + if (headersize >= 0) { /* header detection overridden */ + q = *p; + } else { + char buf[MAXPDSTRING]; + int bytesread = read(fd, buf, READHDRSIZE); + int format; + if (bytesread < 4) goto badheader; + if (!strncmp(buf, ".snd", 4)) {format = FORMAT_NEXT; q.bigendian = 1;} + else if (!strncmp(buf, "dns.", 4)) {format = FORMAT_NEXT; q.bigendian = 0;} + else if (!strncmp(buf, "RIFF", 4)) { + if (bytesread < 12 || strncmp(buf + 8, "WAVE", 4)) goto badheader; + format = FORMAT_WAVE; q.bigendian = 0; + } + else if (!strncmp(buf, "FORM", 4)) { + if (bytesread < 12 || strncmp(buf + 8, "AIFF", 4)) goto badheader; + format = FORMAT_AIFF; q.bigendian = 1; + } else goto badheader; + swap = (q.bigendian != garray_ambigendian()); + if (format == FORMAT_NEXT) { /* nextstep header */ + if (bytesread < (int)sizeof(t_nextstep)) goto badheader; + q.nchannels = swap4(((t_nextstep *)buf)->nchans, swap); + format = swap4(((t_nextstep *)buf)->format, swap); + headersize = swap4(((t_nextstep *)buf)->onset, swap); + if (format == NS_FORMAT_LINEAR_16) q.bytespersample = 2; + else if (format == NS_FORMAT_LINEAR_24) q.bytespersample = 3; + else if (format == NS_FORMAT_FLOAT) q.bytespersample = 4; + else goto badheader; + q.bytelimit = 0x7fffffff; + } else if (format == FORMAT_WAVE) { /* wave header */ + /* This is awful. You have to skip over chunks, + except that if one happens to be a "fmt" chunk, you want to + find out the format from that one. The case where the + "fmt" chunk comes after the audio isn't handled. */ + headersize = 12; + if (bytesread < 20) goto badheader; + /* First we guess a number of channels, etc., in case there's + no "fmt" chunk to follow. */ + q.nchannels = 1; + q.bytespersample = 2; + /* copy the first chunk header to beginnning of buffer. */ + memcpy(buf, buf + headersize, sizeof(t_wavechunk)); + /* read chunks in loop until we get to the data chunk */ + while (strncmp(((t_wavechunk *)buf)->id, "data", 4)) { + long chunksize = swap4(((t_wavechunk *)buf)->size, swap), seekto = headersize + chunksize + 8, seekout; + if (!strncmp(((t_wavechunk *)buf)->id, "fmt ", 4)) { + long commblockonset = headersize + 8; + seekout = lseek(fd, commblockonset, SEEK_SET); + if (seekout != commblockonset) goto badheader; + if (read(fd, buf, sizeof(t_fmt)) < (int) sizeof(t_fmt)) goto badheader; + q.nchannels = swap2(((t_fmt *)buf)->nchannels, swap); + format = swap2(((t_fmt *)buf)->nbitspersample, swap); + if (format == 16) q.bytespersample = 2; + else if (format == 24) q.bytespersample = 3; + else if (format == 32) q.bytespersample = 4; + else goto badheader; + } + seekout = lseek(fd, seekto, SEEK_SET); + if (seekout != seekto) goto badheader; + if (read(fd, buf, sizeof(t_wavechunk)) < (int) sizeof(t_wavechunk)) goto badheader; + headersize = seekto; + } + q.bytelimit = swap4(((t_wavechunk *)buf)->size, swap); + headersize += 8; + } else { + /* AIFF. same as WAVE; actually predates it. Disgusting. */ + headersize = 12; + if (bytesread < 20) goto badheader; + /* First we guess a number of channels, etc., in case there's no COMM block to follow. */ + q.nchannels = 1; + q.bytespersample = 2; + /* copy the first chunk header to beginnning of buffer. */ + memcpy(buf, buf + headersize, sizeof(t_datachunk)); + /* read chunks in loop until we get to the data chunk */ + while (strncmp(((t_datachunk *)buf)->id, "SSND", 4)) { + long chunksize = swap4(((t_datachunk *)buf)->size, swap), seekto = headersize + chunksize + 8, seekout; + if (!strncmp(((t_datachunk *)buf)->id, "COMM", 4)) { + long commblockonset = headersize + 8; + seekout = lseek(fd, commblockonset, SEEK_SET); + if (seekout != commblockonset) goto badheader; + if (read(fd, buf, sizeof(t_comm)) < (int) sizeof(t_comm)) goto badheader; + q.nchannels = swap2(((t_comm *)buf)->nchannels, swap); + format = swap2(((t_comm *)buf)->bitspersamp, swap); + if (format == 16) q.bytespersample = 2; + else if (format == 24) q.bytespersample = 3; + else goto badheader; + } + seekout = lseek(fd, seekto, SEEK_SET); + if (seekout != seekto) goto badheader; + if (read(fd, buf, sizeof(t_datachunk)) < (int) sizeof(t_datachunk)) goto badheader; + headersize = seekto; + } + q.bytelimit = swap4(((t_datachunk *)buf)->size, swap); + headersize += 8; + } + } + /* seek past header and any sample frames to skip */ + sysrtn = lseek(fd, q.bytesperchannel() * skipframes + headersize, 0); + if (sysrtn != q.bytesperchannel() * skipframes + headersize) return -1; + q.bytelimit -= q.bytesperchannel() * skipframes; + if (q.bytelimit < 0) q.bytelimit = 0; + *p = q; + return fd; +badheader: + /* the header wasn't recognized. We're threadable here so let's not print out the error... */ + errno = EIO; + return -1; +} + +/* open a soundfile, using open_via_path(). This is used by readsf~ in + a not-perfectly-threadsafe way. LATER replace with a thread-hardened version of open_soundfile_via_canvas() */ +static int open_soundfile(const char *dirname, const char *filename, int headersize, t_param *p, long skipframes) { + char *buf, *bufptr; + int fd = open_via_path2(dirname, filename, "", &buf, &bufptr, 1); + if (fd < 0) return -1; + free(buf); + return open_soundfile_via_fd(fd, headersize, p, skipframes); +} + +/* open a soundfile, using open_via_canvas(). This is used by readsf~ in + a not-perfectly-threadsafe way. LATER replace with a thread-hardened version of open_soundfile_via_canvas() */ +static int open_soundfile_via_canvas(t_canvas *canvas, const char *filename, int headersize, t_param *p, long skipframes) { + char *buf, *bufptr; + int fd = canvas_open2(canvas, filename, "", &buf, &bufptr, 1); + if (fd < 0) return -1; + free(buf); + return open_soundfile_via_fd(fd, headersize, p, skipframes); +} + +static void soundfile_xferin(int sfchannels, int nvecs, float **vecs, + long itemsread, unsigned char *buf, int nitems, int bytespersample, int bigendian) { + unsigned char *sp, *sp2; + int nchannels = (sfchannels < nvecs ? sfchannels : nvecs); + int bytesperframe = bytespersample * sfchannels; + sp = buf; + for (int i=0; i < nchannels; i++, sp += bytespersample) { + int j; + sp2=sp; + float *fp=vecs[i] + itemsread; + #define LOOP for (j=0; j -skip -bytes -normalize -nextstep -wave -big -little + the routine which actually does the work should LATER also be called from garray_write16. + Parse arguments for writing. The "obj" argument is only for flagging + errors. For streaming to a file the "normalize", "onset" and "nframes" + arguments shouldn't be set but the calling routine flags this. */ +static int soundfiler_writeargparse(void *obj, int *p_argc, t_atom **p_argv, t_symbol **p_filesym, +int *p_filetype, int *p_bytespersamp, int *p_swap, int *p_bigendian, +int *p_normalize, long *p_onset, long *p_nframes, float *p_rate) { + int argc = *p_argc; + t_atom *argv = *p_argv; + int bytespersample = 2, bigendian = 0, endianness = -1, swap, filetype = -1, normalize = 0; + long onset = 0, nframes = 0x7fffffff; + t_symbol *filesym; + float rate = -1; + while (argc > 0 && argv->a_type == A_SYMBOL && *argv->a_symbol->name == '-') { + char *flag = argv->a_symbol->name + 1; + argc--; argv++; + if (!strcmp(flag, "skip")) { + EAT_ARG(A_FLOAT,onset); if (onset<0) goto usage; + } else if (!strcmp(flag, "nframes")) { + EAT_ARG(A_FLOAT,nframes); if (nframes<0) goto usage; + } else if (!strcmp(flag, "bytes")) { + EAT_ARG(A_FLOAT,bytespersample); if (bytespersample<2 || bytespersample>4) goto usage; + } else if (!strcmp(flag, "normalize")) {normalize = 1; + } else if (!strcmp(flag, "wave")) {filetype = FORMAT_WAVE; + } else if (!strcmp(flag, "nextstep")) {filetype = FORMAT_NEXT; + } else if (!strcmp(flag, "aiff")) {filetype = FORMAT_AIFF; + } else if (!strcmp(flag, "big")) {endianness = 1; + } else if (!strcmp(flag, "little")) {endianness = 0; + } else if (!strcmp(flag, "r") || !strcmp(flag, "rate")) { + EAT_ARG(A_FLOAT,rate); if (rate<0) goto usage; + } else goto usage; + } + if (!argc || argv->a_type != A_SYMBOL) goto usage; + filesym = argv->a_symbol; + /* check if format not specified and fill in */ + if (filetype < 0) { + const char *s = filesym->name + strlen(filesym->name); + if (strlen(filesym->name) >= 5 && !strcasecmp(s-4, ".aif" )) filetype = FORMAT_AIFF; + if (strlen(filesym->name) >= 6 && !strcasecmp(s-5, ".aiff")) filetype = FORMAT_AIFF; + if (strlen(filesym->name) >= 5 && !strcasecmp(s-4, ".snd" )) filetype = FORMAT_NEXT; + if (strlen(filesym->name) >= 4 && !strcasecmp(s-3, ".au" )) filetype = FORMAT_NEXT; + if (filetype < 0) filetype = FORMAT_WAVE; + } + /* don't handle AIFF floating point samples */ + if (bytespersample == 4) { + if (filetype == FORMAT_AIFF) { + error("AIFF floating-point file format unavailable"); + goto usage; + } + } + /* for WAVE force little endian; for nextstep use machine native */ + if (filetype == FORMAT_WAVE) { + bigendian = 0; + if (endianness == 1) error("WAVE file forced to little endian"); + } else if (filetype == FORMAT_AIFF) { + bigendian = 1; + if (endianness == 0) error("AIFF file forced to big endian"); + } else if (endianness == -1) { + bigendian = garray_ambigendian(); + } else bigendian = endianness; + swap = (bigendian != garray_ambigendian()); + argc--; argv++; + *p_argc = argc; + *p_argv = argv; + *p_filesym = filesym; + *p_filetype = filetype; + *p_bytespersamp = bytespersample; + *p_swap = swap; + *p_normalize = normalize; + *p_onset = onset; + *p_nframes = nframes; + *p_bigendian = bigendian; + *p_rate = rate; + return 0; +usage: + return -1; +} + +static bool strcaseends(const char *a, const char *b) {return strcasecmp(a+strlen(a)-strlen(b),b)==0;} + +static int create_soundfile(t_canvas *canvas, const char *filename, int filetype, int nframes, int bytespersample, +int bigendian, int nchannels, int swap, float samplerate) { + char filenamebuf[strlen(filename)+10]; + char headerbuf[WRITEHDRSIZE]; + int fd, headersize = 0; + strcpy(filenamebuf, filename); + if (filetype == FORMAT_NEXT) { + t_nextstep *nexthdr = (t_nextstep *)headerbuf; + if (!strcaseends(filenamebuf,".snd")) strcat(filenamebuf, ".snd"); + if (bigendian) strncpy(nexthdr->fileid, bigendian?".snd":"dns.", 4); + nexthdr->onset = swap4(sizeof(*nexthdr), swap); + nexthdr->length = 0; + nexthdr->format = swap4(bytespersample == 3 ? NS_FORMAT_LINEAR_24 : bytespersample == 4 ? NS_FORMAT_FLOAT : NS_FORMAT_LINEAR_16, swap); + nexthdr->sr = swap4((size_t)samplerate, swap); + nexthdr->nchans = swap4((size_t)nchannels, swap); + strcpy(nexthdr->info, "Pd "); + swapstring(nexthdr->info, swap); + headersize = sizeof(t_nextstep); + } else if (filetype == FORMAT_AIFF) { + long datasize = nframes * nchannels * bytespersample; + long longtmp; + static unsigned char dogdoo[] = {0x40, 0x0e, 0xac, 0x44, 0, 0, 0, 0, 0, 0, 'S', 'S', 'N', 'D'}; + t_aiff *aiffhdr = (t_aiff *)headerbuf; + if (!strcaseends(filenamebuf,".aif") && !strcaseends(filenamebuf,".aiff")) strcat(filenamebuf, ".aif"); + strncpy(aiffhdr->fileid, "FORM", 4); + aiffhdr->chunksize = swap4(datasize + sizeof(*aiffhdr) + 4, swap); + strncpy(aiffhdr->aiffid, "AIFF", 4); + strncpy(aiffhdr->fmtid, "COMM", 4); + aiffhdr->fmtchunksize = swap4(18, swap); + aiffhdr->nchannels = swap2(nchannels, swap); + longtmp = swap4(nframes, swap); + memcpy(&aiffhdr->nframeshi, &longtmp, 4); + aiffhdr->bitspersamp = swap2(8 * bytespersample, swap); + memcpy(aiffhdr->samprate, dogdoo, sizeof(dogdoo)); + longtmp = swap4(datasize, swap); + memcpy(aiffhdr->samprate + sizeof(dogdoo), &longtmp, 4); + memset(aiffhdr->samprate + sizeof(dogdoo) + 4, 0, 8); + headersize = AIFFPLUS; + /* fix by matju for häfeli, 2007.07.04, but really, dogdoo should be removed */ + while (samplerate >= 0x10000) {aiffhdr->samprate[1]++; samplerate/=2;} + aiffhdr->samprate[2] = (long)samplerate>>8; + aiffhdr->samprate[3] = (long)samplerate; + } else { /* WAVE format */ + long datasize = nframes * nchannels * bytespersample; + if (!strcaseends(filenamebuf,".wav")) strcat(filenamebuf, ".wav"); + t_wave *wavehdr = (t_wave *)headerbuf; + strncpy(wavehdr->fileid, "RIFF", 4); + wavehdr->chunksize = swap4(datasize + sizeof(*wavehdr) - 8, swap); + strncpy(wavehdr->waveid, "WAVE", 4); + strncpy(wavehdr->fmtid, "fmt ", 4); + wavehdr->fmtchunksize = swap4(16, swap); + wavehdr->fmttag = swap2((bytespersample == 4 ? WAV_FLOAT : WAV_INT), swap); + wavehdr->nchannels = swap2(nchannels, swap); + wavehdr->samplespersec = swap4(size_t(samplerate), swap); + wavehdr->navgbytespersec = swap4((int)(samplerate * nchannels * bytespersample), swap); + wavehdr->nblockalign = swap2(nchannels * bytespersample, swap); + wavehdr->nbitspersample = swap2(8 * bytespersample, swap); + strncpy(wavehdr->datachunkid, "data", 4); + wavehdr->datachunksize = swap4(datasize, swap); + headersize = sizeof(t_wave); + } + char *buf2 = canvas_makefilename(canvas, filenamebuf,0,0); + sys_bashfilename(buf2,buf2); + if ((fd = open(buf2, BINCREATE, 0666)) < 0) {free(buf2); return -1;} + if (write(fd, headerbuf, headersize) < headersize) { + close (fd); + return -1; + } + return fd; +} + +static void soundfile_finishwrite(void *obj, char *filename, int fd, +int filetype, long nframes, long itemswritten, int bytesperframe, int swap) { + if (itemswritten < nframes) { + if (nframes < 0x7fffffff) + error("soundfiler_write: %d out of %d bytes written", itemswritten, nframes); + /* try to fix size fields in header */ + if (filetype == FORMAT_WAVE) { + long datasize = itemswritten * bytesperframe, v; + if (lseek(fd, ((char *)(&((t_wave *)0)->chunksize)) - (char *)0, SEEK_SET) == 0) goto baddonewrite; + v = swap4(datasize + sizeof(t_wave) - 8, swap); + if (write(fd, (char *)&v, 4) < 4) goto baddonewrite; + if (lseek(fd, ((char *)(&((t_wave *)0)->datachunksize)) - (char *)0, SEEK_SET) == 0) goto baddonewrite; + v = swap4(datasize, swap); + if (write(fd, (char *)&v, 4) < 4) goto baddonewrite; + } + if (filetype == FORMAT_AIFF) { + long v; + if (lseek(fd, ((char *)(&((t_aiff *)0)->nframeshi)) - (char *)0, SEEK_SET) == 0) goto baddonewrite; + v = swap4(itemswritten, swap); + if (write(fd, (char *)&v,4) < 4) goto baddonewrite; + if (lseek(fd, ((char *)(&((t_aiff *)0)->chunksize)) - (char *)0, SEEK_SET) == 0) goto baddonewrite; + v = swap4(itemswritten*bytesperframe+AIFFHDRSIZE, swap); + if (write(fd, (char *)&v,4) < 4) goto baddonewrite; + if (lseek(fd, (AIFFHDRSIZE+4), SEEK_SET) == 0) goto baddonewrite; + v = swap4(itemswritten*bytesperframe, swap); + if (write(fd, (char *)&v,4) < 4) goto baddonewrite; + } + if (filetype == FORMAT_NEXT) { + /* do it the lazy way: just set the size field to 'unknown size'*/ + uint32 nextsize = 0xffffffff; + if (lseek(fd, 8, SEEK_SET) == 0) goto baddonewrite; + if (write(fd, &nextsize, 4) < 4) goto baddonewrite; + } + } + return; +baddonewrite: + error("%s: %s", filename, strerror(errno)); +} + +static void soundfile_xferout(int nchannels, float **vecs, unsigned char *buf, int nitems, long onset, int bytespersample, +int bigendian, float normalfactor) { + unsigned char *sp=buf, *sp2; + float *fp; + int bytesperframe = bytespersample * nchannels; + #define LOOP for (int j=0; j>8; sp2[1] = xx; + } else LOOP { + int xx = clip(int(32768. + *fp * ff) - 0x8000,-0x7fff,+0x7fff); + sp2[1] = xx>>8; sp2[0] = xx; + } + } else if (bytespersample == 3) { + float ff = normalfactor * 8388608.; + if (bigendian) LOOP { + int xx = clip(int(8388608. + *fp * ff) - 0x800000,-0x7fffff,+0x7fffff); + sp2[0] = xx>>16; sp2[1] = xx>>8; sp2[2] = xx; + } else LOOP { + int xx = clip(int(8388608. + *fp * ff) - 0x800000,-0x7fffff,+0x7fffff); + sp2[2] = xx>>16; sp2[1] = xx>>8; sp2[0] = xx; + } + } else if (bytespersample == 4) { + if (bigendian) LOOP { + float f2 = *fp * normalfactor; long xx = *(long *)&f2; + sp2[0] = xx >> 24; sp2[1] = xx >> 16; sp2[2] = xx >> 8; sp2[3] = xx; + } else LOOP { + float f2 = *fp * normalfactor; long xx = *(long *)&f2; + sp2[3] = xx >> 24; sp2[2] = xx >> 16; sp2[1] = xx >> 8; sp2[0] = xx; + } + } + } + #undef LOOP +} + +/* ------- soundfiler - reads and writes soundfiles to/from "garrays" ---- */ + +#define DEFMAXSIZE (16*1024*1024*4) /* default maximum 16 million floats per channel */ +#define SAMPBUFSIZE 1024 + +static t_class *soundfiler_class; + +struct t_soundfiler : t_object {t_canvas *canvas;}; + +#ifdef THREADED_SF +#include +#if (_POSIX_MEMLOCK - 0) >= 200112L +#include +#else +#define munlockall() /* ignore */ +#define mlockall() /* ignore */ +#endif /* _POSIX_MEMLOCK */ + +static pthread_t sf_thread_id; /* id of soundfiler thread */ + +struct t_sfprocess { + void (*process)(t_soundfiler *,t_symbol *, int, t_atom *); /* function to call */ + t_soundfiler *x; + int argc; + t_atom *argv; + struct t_sfprocess *next; /* next object in queue */ + pthread_mutex_t mutex; +}; + +/* this is the queue for all soundfiler objects */ +struct t_sfqueue { + t_sfprocess *begin; + t_sfprocess *end; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +static t_sfqueue *soundfiler_queue; + +/* we fill the queue */ +void soundfiler_queue_add(void (* process) (t_soundfiler *,t_symbol *,int,t_atom *), void * x, int argc, t_atom * argv) { + /* preparing entry */ + t_sfprocess * last_entry = (t_sfprocess*)getbytes(sizeof(t_sfprocess)); + if (debug) post("adding process to queue"); + pthread_mutex_init(&(last_entry->mutex), NULL); + pthread_mutex_lock(&(last_entry->mutex)); + last_entry->process = process; + last_entry->x = (t_soundfiler *)x; + last_entry->argc = argc; + last_entry->argv = (t_atom *)copybytes(argv, argc * sizeof(t_atom)); + last_entry->next = NULL; + pthread_mutex_unlock(&(last_entry->mutex)); + /* add new entry to queue */ + pthread_mutex_lock(&(soundfiler_queue->mutex)); + if (soundfiler_queue->begin==NULL) { + soundfiler_queue->begin=last_entry; + soundfiler_queue->end=last_entry; + } else { + pthread_mutex_lock(&(soundfiler_queue->end->mutex)); + soundfiler_queue->end->next=last_entry; + pthread_mutex_unlock(&(soundfiler_queue->end->mutex)); + soundfiler_queue->end=last_entry; + } + if ( soundfiler_queue->begin == soundfiler_queue->end ) { + if (debug) post("signaling"); + pthread_mutex_unlock(&(soundfiler_queue->mutex)); + /* and signal the helper thread */ + pthread_cond_signal(&(soundfiler_queue->cond)); + } else { + if (debug) post("not signaling"); + pthread_mutex_unlock(&(soundfiler_queue->mutex)); + } + return; +} + +/* global soundfiler thread ... sleeping until signaled */ +void soundfiler_thread() { + t_sfprocess *me; + t_sfprocess *next; + if (debug) post("soundfiler_thread ID: %d", pthread_self()); + while (1) { + if (debug) post("Soundfiler sleeping"); + pthread_cond_wait(&soundfiler_queue->cond, &soundfiler_queue->mutex); + if (debug) post("Soundfiler awake"); + /* work on the queue */ + while (soundfiler_queue->begin!=NULL) { + post("soundfiler: locked queue"); + /* locking process */ + pthread_mutex_lock(&(soundfiler_queue->begin->mutex)); + me = soundfiler_queue->begin; + pthread_mutex_unlock(&(me->mutex)); + pthread_mutex_unlock(&(soundfiler_queue->mutex)); + if (debug) post("soundfiler: mutex unlocked, running process"); + /* running the specific function */ + me->process(me->x, NULL, me->argc, me->argv); + if (debug) post("soundfiler: process done, locking mutex"); + pthread_mutex_lock(&(soundfiler_queue->mutex)); + pthread_mutex_lock(&(me->mutex)); + free(me->argv); + /* the process struct */ + next=me->next; + soundfiler_queue->begin=next; + free(me); + } + soundfiler_queue->end=NULL; + } +} + +extern int sys_hipriority; /* real-time flag, true if priority boosted */ + +/* create soundfiler thread */ +void sys_start_sfthread() { + pthread_attr_t sf_attr; + struct sched_param sf_param; + int status; + // initialize queue + soundfiler_queue = (t_sfqueue *)getbytes(sizeof(t_sfqueue)); + pthread_mutex_init(&soundfiler_queue->mutex,NULL); + pthread_cond_init(&soundfiler_queue->cond,NULL); + soundfiler_queue->begin=soundfiler_queue->end=NULL; +/* pthread_mutex_unlock(&(soundfiler_queue->mutex)); */ + // initialize thread + pthread_attr_init(&sf_attr); + sf_param.sched_priority=sched_get_priority_min(SCHED_OTHER); + pthread_attr_setschedparam(&sf_attr,&sf_param); +/* pthread_attr_setinheritsched(&sf_attr,PTHREAD_EXPLICIT_SCHED); */ +#ifdef UNIX + if (sys_hipriority == 1 && getuid() == 0) { + sf_param.sched_priority=sched_get_priority_min(SCHED_RR); + pthread_attr_setschedpolicy(&sf_attr,SCHED_RR); + } else { +/* pthread_attr_setschedpolicy(&sf_attr,SCHED_OTHER); */ +/* sf_param.sched_priority=sched_get_priority_min(SCHED_OTHER); */ + } +#endif /* UNIX */ + //start thread + status = pthread_create(&sf_thread_id, &sf_attr, (void *(*)(void *)) soundfiler_thread,NULL); + if (status != 0) error("Couldn't create soundfiler thread: %d",status); + else post("global soundfiler thread launched, priority: %d", sf_param.sched_priority); +} + +static void soundfiler_t_write( t_soundfiler *x, t_symbol *s, int argc, t_atom *argv); +static void soundfiler_t_write_addq(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + soundfiler_queue_add(soundfiler_t_write,(void *)x,argc, argv); +} +static void soundfiler_t_read( t_soundfiler *x, t_symbol *s, int argc, t_atom *argv); +static void soundfiler_t_read_addq(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + soundfiler_queue_add(soundfiler_t_read,(void *)x,argc, argv); +} + +/* soundfiler_read + usage: read [flags] filename table ... + flags: -skip ... frames to skip in file + -nframes -onset ... onset in table to read into (NOT DONE YET) + -raw -resize -maxsize + TB: adapted for threaded use */ +static t_int soundfiler_read_update_garray(t_int *w); +static t_int soundfiler_read_update_graphics(t_int *w); +static t_int soundfiler_read_output(t_int *w); +static void soundfiler_t_read(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + t_param p; + int headersize = -1; + p.nchannels = 0; p.bytespersample = 0; p.bigendian = 0; + int resize = 0, i, j; + long skipframes = 0, nframes = 0, finalsize = 0, maxsize = DEFMAXSIZE, itemsread = 0; + p.bytelimit = 0x7fffffff; + int fd = -1; + char endianness, *filename; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; /* the old array */ + t_float *nvecs[MAXSFCHANS]; /* the new array */ + int vecsize[MAXSFCHANS]; /* the old array size */ + char sampbuf[SAMPBUFSIZE]; + int bufframes, nitems; + FILE *fp; + pthread_cond_t resume_after_callback = PTHREAD_COND_INITIALIZER; + pthread_mutex_t resume_after_callback_mutex = PTHREAD_MUTEX_INITIALIZER; /* dummy */ + t_int* outargs; + while (argc > 0 && argv->a_type == A_SYMBOL && *argv->a_symbol->name == '-') { + char *flag = argv->a_symbol->name + 1; + argc--; argv++; + if (!strcmp(flag, "skip")) { + EAT_ARG(A_FLOAT,skipframes); if (skipframes<0) goto usage; + } else if (!strcmp(flag, "nframes")) { + EAT_ARG(A_FLOAT,nframes); if (nframes<0) goto usage; + } else if (!strcmp(flag, "raw")) { + EAT_ARG(A_FLOAT,headersize); if (headersize<0) goto usage; + EAT_ARG(A_FLOAT,p.nchannels); if (p.nchannels<1) goto usage; + EAT_ARG(A_FLOAT,p.bytespersample); if (p.bytespersample<2 || p.bytespersample>4) goto usage; + EAT_ARG(A_SYMBOL,endianness); if (endianness!='b' && endianness!='l' && endianness!='n') goto usage; + if (endianness == 'b') p.bigendian = 1; + else if (endianness == 'l') p.bigendian = 0; + else p.bigendian = garray_ambigendian(); + } else if (!strcmp(flag, "resize")) { + resize = 1; + } else if (!strcmp(flag, "maxsize")) { + EAT_ARG(A_FLOAT,maxsize); if (maxsize<0) goto usage; + resize = 1; /* maxsize implies resize. */ + } else goto usage; + } + if (argc < 2 || argc > MAXSFCHANS + 1 || argv[0].a_type != A_SYMBOL) goto usage; + filename = argv[0].a_symbol->name; + argc--; argv++; + for (int i=0; iname); + goto done; + } else if (!garray_getfloatarray(garrays[i], &vecsize[i], &vecs[i])) + error("%s: bad template for tabwrite", argv[i].a_symbol->name); + if (finalsize && finalsize != vecsize[i] && !resize) { + post("soundfiler_read: arrays have different lengths; resizing..."); + resize = 1; + } + finalsize = vecsize[i]; + } + fd = open_soundfile(canvas_getdir(x->canvas)->name, filename, headersize, &p, skipframes); + if (fd < 0) { + error("soundfiler_read: %s: %s", filename, (errno == EIO ? "unknown or bad header format" : strerror(errno))); + goto done; + } + if (resize) { + /* figure out what to resize to */ + long poswas, eofis, framesinfile; + poswas = lseek(fd, 0, SEEK_CUR); + eofis = lseek(fd, 0, SEEK_END); + if (poswas < 0 || eofis < 0) {error("lseek failed"); goto done;} + lseek(fd, poswas, SEEK_SET); + framesinfile = (eofis - poswas) / p.bytesperchannel(); + if (framesinfile > maxsize) { + error("soundfiler_read: truncated to %d elements", maxsize); + framesinfile = maxsize; + } + framesinfile = min(framesinfile, p.bytelimit / p.bytesperchannel()); + finalsize = framesinfile; + } + if (!finalsize) finalsize = 0x7fffffff; + finalsize = min(finalsize, p.bytelimit / p.bytesperchannel()); + fp = fdopen(fd, "rb"); + bufframes = SAMPBUFSIZE / p.bytesperchannel(); + if (debug) { + post("buffers: %d", argc); + post("channels: %d", p.nchannels); + } + munlockall(); + /* allocate memory for new array */ + if (resize) + for (int i=0; i p.nchannels) memset(nvecs[i],0,vecsize[i] * sizeof(t_float)); + if (debug) post("transfer soundfile"); + for (itemsread = 0; itemsread < finalsize; ) { + int thisread = finalsize - itemsread; + thisread = (thisread > bufframes ? bufframes : thisread); + nitems = fread(sampbuf, p.bytesperchannel(), thisread, fp); + if (nitems <= 0) break; + soundfile_xferin(p.nchannels, argc, nvecs, itemsread, (unsigned char *)sampbuf, nitems, p.bytespersample, p.bigendian); + itemsread += nitems; + } + if (debug) post("zeroing remaining elements"); + /* zero out remaining elements of vectors */ + for (int i=0; i -nframes -resize -maxsize ..."); + post("-raw ."); +done: + if (fd>=0) close(fd); + mlockall(MCL_FUTURE); + outargs = (t_int*)getbytes(2*sizeof(t_int)); + outargs[0] = (t_int)x->outlet; + outargs[1] = (t_int)itemsread; + sys_callback(&soundfiler_read_output, outargs, 2); +} + +/* idle callback for threadsafe synchronisation */ +static t_int soundfiler_read_update_garray(t_int *w) { + t_garray *garray = (t_garray*)w[0]; + t_int nvec = w[1]; + t_int finalsize = w[2]; + pthread_cond_t *conditional = (pthread_cond_t*) w[3]; + t_array *a = garray_getarray(garray); + a->vec = (char *) nvec; + a->n = finalsize; + if (garray->usedindsp) canvas_update_dsp(); + /* signal helper thread */ + pthread_cond_broadcast(conditional); + return 0; +} + +static t_int soundfiler_read_update_graphics(t_int *w) { + t_garray *garray = (t_garray*) w[0]; + t_canvas *gl; + int n = w[1]; + /* if this is the only array in the graph, reset the graph's coordinates */ + if (debug) post("redraw array %p", garray); + gl = garray->canvas; + if (gl->list == garray && !garray->next) { + vmess(gl, gensym("bounds"), "ffff", 0., gl->y1, double(n > 1 ? n-1 : 1), gl->y2); + /* close any dialogs that might have the wrong info now... */ + } else garray_redraw(garray); + return 0; +} + +static t_int soundfiler_read_output(t_int * w) { + t_outlet* outlet = (t_outlet*) w[0]; + float itemsread = (float) w[1]; + if (debug) post("bang %p", outlet); + outlet_float (outlet, itemsread); + return 0; +} + +/* this is broken out from soundfiler_write below so garray_write can call it too... not done yet though. */ +long soundfiler_t_dowrite(void *obj, t_canvas *canvas, int argc, t_atom *argv) { + int bytespersample, bigendian, swap, filetype, normalize, nchannels; + long onset, nframes, itemswritten = 0; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; + char sampbuf[SAMPBUFSIZE]; + int bufframes; + int fd = -1; + float normfactor, biggest = 0, samplerate; + t_symbol *filesym; + if (soundfiler_writeargparse(obj, &argc, &argv, &filesym, &filetype, + &bytespersample, &swap, &bigendian, &normalize, &onset, &nframes, &samplerate)) + goto usage; + nchannels = argc; + if (nchannels < 1 || nchannels > MAXSFCHANS) goto usage; + if (samplerate < 0) samplerate = sys_getsr(); + for (int i=0; iname); + goto fail; + } + else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i])) + error("%s: bad template for tabwrite", argv[i].a_symbol->name); + if (nframes > vecsize - onset) + nframes = vecsize - onset; + for (int j=0; j biggest) biggest = +vecs[i][j]; + else if (-vecs[i][j] > biggest) biggest = -vecs[i][j]; + } + } + if (nframes <= 0) { + error("soundfiler_write: no samples at onset %ld", onset); + goto fail; + } + if ((fd = create_soundfile(canvas, filesym->name, filetype, nframes, bytespersample, bigendian, nchannels, swap, samplerate)) < 0) { + post("%s: %s", filesym->name, strerror(errno)); + goto fail; + } + if (!normalize) { + if ((bytespersample != 4) && (biggest > 1)) { + post("%s: normalizing max amplitude %f to 1", filesym->name, biggest); + normalize = 1; + } else post("%s: biggest amplitude = %f", filesym->name, biggest); + } + if (normalize) normfactor = (biggest > 0 ? 32767./(32768. * biggest) : 1); + else normfactor = 1; + bufframes = SAMPBUFSIZE / (nchannels * bytespersample); + for (itemswritten = 0; itemswritten < nframes; ) { + int thiswrite = nframes - itemswritten, nbytes; + thiswrite = (thiswrite > bufframes ? bufframes : thiswrite); + soundfile_xferout(argc, vecs, (unsigned char *)sampbuf, thiswrite, onset, bytespersample, bigendian, normfactor); + nbytes = write(fd, sampbuf, nchannels * bytespersample * thiswrite); + if (nbytes < nchannels * bytespersample * thiswrite) { + post("%s: %s", filesym->name, strerror(errno)); + if (nbytes > 0) itemswritten += nbytes / (nchannels * bytespersample); + break; + } + itemswritten += thiswrite; + onset += thiswrite; + } + if (fd >= 0) { + soundfile_finishwrite(obj, filesym->name, fd, filetype, nframes, itemswritten, nchannels * bytespersample, swap); + close (fd); + } + return itemswritten; +usage: + error("usage: write [flags] filename tablename..."); + post("flags: -skip -nframes -bytes -wave -aiff -nextstep ..."); + post("-big -little -normalize"); + post("(defaults to a 16-bit wave file)."); +fail: + if (fd >= 0) close(fd); + return 0; +} + +static void soundfiler_t_write(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + long bozo = soundfiler_t_dowrite(x, x->canvas, argc, argv); + sys_lock(); + outlet_float(x->outlet, (float)bozo); + sys_lock(); +} + +static void soundfiler_t_resize(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv); +static void soundfiler_t_resize_addq(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + soundfiler_queue_add(soundfiler_t_resize,(void *)x,argc, argv); +} + +/* TB: soundfiler_t_resize ... usage: resize table size; adapted from garray_resize */ +static void soundfiler_t_resize(t_soundfiler *y, t_symbol *s, int argc, t_atom *argv) { + int was, elemsize; /* array contains was elements of size elemsize */ + t_float *vec; /* old array */ + t_canvas *gl; + int n; /* resize of n elements */ + char *nvec; /* new array */ + t_garray *x = (t_garray *)pd_findbyclass(argv[0].a_symbol, garray_class); + t_array *a = garray_getarray(x); + if (!x) {error("%s: no such table", argv[0].a_symbol->name); goto usage;} + vec = (t_float*) a->vec; + was = a->n; + if ((argv+1)->a_type == A_FLOAT) { + n = (int) (argv+1)->a_float; + } else goto usage; + if (n == was) return; + if (n < 1) n = 1; + elemsize = template_findbyname(a->templatesym)->t_n * sizeof(t_word); + munlockall(); + if (was > n) { + nvec = (char *)copyalignedbytes(a->vec, was * elemsize); + } else { + nvec = (char *)getalignedbytes(n * elemsize); + memcpy (nvec, a->vec, was * elemsize); + memset(nvec + was*elemsize, 0, (n - was) * elemsize); + } + if (!nvec) {error("array resize failed: out of memory"); mlockall(MCL_FUTURE); return;} + /* TB: we'll have to be sure that no one is accessing the array */ + sys_lock(); + a->vec = nvec; + a->n = n; + if (x->usedindsp) canvas_update_dsp(); + sys_unlock(); + /* if this is the only array in the graph, reset the graph's coordinates */ + gl = x->canvas; + if (gl->list == x && !x->next) { + vmess(gl, gensym("bounds"), "ffff", 0., gl->y1, (double)(n > 1 ? n-1 : 1), gl->y2); + /* close any dialogs that might have the wrong info now... */ + } else garray_redraw(x); + freealignedbytes (vec, was * elemsize); + mlockall(MCL_FUTURE); + sys_lock(); + outlet_float(y->outlet, (float)atom_getintarg(1,argc,argv)); + sys_unlock(); + return; +usage: + error("usage: resize tablename size"); +} + +static void soundfiler_t_const(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv); +static void soundfiler_t_const_addq(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + soundfiler_queue_add(soundfiler_t_const,(void *)x,argc, argv); +} + +/* TB: soundfiler_t_const ... usage: const table value */ +static void soundfiler_t_const(t_soundfiler *y, t_symbol *s, int argc, t_atom *argv) { + int size, elemsize; /* array contains was elements of size elemsize */ + t_float *vec; /* old array */ + t_canvas *gl; + int val; /* value */ + char *nvec; /* new array */ + t_garray * x = (t_garray *)pd_findbyclass(argv[0].a_symbol, garray_class); + t_array *a = garray_getarray(x); + if (!x) {error("%s: no such table", argv[0].a_symbol->name); goto usage;} + vec = (t_float*) a->vec; + size = a->n; + if ((argv+1)->a_type == A_FLOAT) { + val = (int) (argv+1)->a_float; + } else goto usage; + elemsize = template_findbyname(a->templatesym)->t_n * sizeof(t_word); + /* allocating memory */ + munlockall(); + nvec = (char *)getalignedbytes(size * elemsize); + if (!nvec) { + error("array resize failed: out of memory"); + mlockall(MCL_FUTURE); + return; + } + /* setting array */ + for (int i=0; i!=size; ++i) nvec[i]=val; + /* TB: we'll have to be sure that no one is accessing the array */ + sys_lock(); + a->vec = nvec; + if (x->usedindsp) canvas_update_dsp(); + sys_unlock(); + /* if this is the only array in the graph, reset the graph's coordinates */ + gl = x->canvas; + if (gl->list == x && !x->next) { + vmess(gl, gensym("bounds"), "ffff", 0., gl->y1, (double)(size > 1 ? size-1 : 1), gl->y2); + /* close any dialogs that might have the wrong info now... */ + } else garray_redraw(x); + freealignedbytes (vec, size * elemsize); + mlockall(MCL_FUTURE); + sys_lock(); + outlet_float(y->outlet, size); + sys_unlock(); + return; + usage: + error("usage: const tablename value"); +} + +#endif /* THREADED_SF */ + +static t_soundfiler *soundfiler_new() { + t_soundfiler *x = (t_soundfiler *)pd_new(soundfiler_class); + x->canvas = canvas_getcurrent(); + outlet_new(x,&s_float); +#ifdef THREADED_SF + post("warning: threaded soundfiler is not synchronous"); +#endif /* THREADED_SF */ + return x; +} + +/* soundfiler_read ... + usage: read [flags] filename table ... + flags: -skip ... frames to skip in file + -nframes -onset ... onset in table to read into (NOT DONE YET) + -raw -resize -maxsize */ +static void soundfiler_read(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + t_param p; + p.bytespersample = 0; + p.bigendian = 0; + p.nchannels = 0; + p.bytelimit = 0x7fffffff; + int headersize = -1, resize = 0; + long skipframes = 0, nframes = 0, finalsize = 0, maxsize = DEFMAXSIZE, itemsread = 0; + int fd = -1; + char endianness, *filename; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; + char sampbuf[SAMPBUFSIZE]; + int bufframes, nitems; + FILE *fp; + while (argc > 0 && argv->a_type == A_SYMBOL && *argv->a_symbol->name == '-') { + char *flag = argv->a_symbol->name + 1; + argc--; argv++; + if (!strcmp(flag, "skip")) { + EAT_ARG(A_FLOAT,skipframes); if (skipframes<0) goto usage; + } else if (!strcmp(flag, "nframes")) { + EAT_ARG(A_FLOAT,nframes); if (nframes<0) goto usage; + } else if (!strcmp(flag, "raw")) { + EAT_ARG(A_FLOAT,headersize); if (headersize<0) goto usage; + EAT_ARG(A_FLOAT,p.nchannels); if (p.nchannels<1) goto usage; + EAT_ARG(A_FLOAT,p.bytespersample); if (p.bytespersample<2 || p.bytespersample>4) goto usage; + EAT_ARG(A_SYMBOL,endianness); if (endianness!='b' && endianness!='l' && endianness!='n') goto usage; + if (endianness == 'b') p.bigendian = 1; + else if (endianness == 'l') p.bigendian = 0; + else p.bigendian = garray_ambigendian(); + } else if (!strcmp(flag, "resize")) { + resize = 1; + } else if (!strcmp(flag, "maxsize")) { + EAT_ARG(A_FLOAT,maxsize); if (maxsize<0) goto usage; + resize = 1; /* maxsize implies resize. */ + } else goto usage; + } + if (argc < 2 || argc > MAXSFCHANS + 1 || argv[0].a_type != A_SYMBOL) goto usage; + filename = argv[0].a_symbol->name; + argc--; argv++; + for (int i=0; iname); + goto done; + } else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i])) + error("%s: bad template for tabwrite", argv[i].a_symbol->name); + if (finalsize && finalsize != vecsize && !resize) { + post("soundfiler_read: arrays have different lengths; resizing..."); + resize = 1; + } + finalsize = vecsize; + } + fd = open_soundfile_via_canvas(x->canvas, filename, headersize, &p, skipframes); + if (fd < 0) { + error("soundfiler_read: %s: %s", filename, (errno == EIO ? "unknown or bad header format" : strerror(errno))); + goto done; + } + if (resize) { + /* figure out what to resize to */ + long poswas, eofis, framesinfile; + poswas = lseek(fd, 0, SEEK_CUR); + eofis = lseek(fd, 0, SEEK_END); + if (poswas < 0 || eofis < 0) {error("lseek failed"); goto done;} + lseek(fd, poswas, SEEK_SET); + framesinfile = (eofis - poswas) / (p.nchannels * p.bytespersample); + if (framesinfile > maxsize) { + error("soundfiler_read: truncated to %d elements", maxsize); + framesinfile = maxsize; + } + framesinfile = min(framesinfile, p.bytelimit / (p.nchannels * p.bytespersample)); + finalsize = framesinfile; + for (int i=0; i -nframes -resize -maxsize ..."); + post("-raw ."); +done: + if (fd >= 0) close (fd); + outlet_float(x->outlet, (float)itemsread); +} + +/* this is broken out from soundfiler_write below so garray_write can + call it too... not done yet though. */ +long soundfiler_dowrite(void *obj, t_canvas *canvas, int argc, t_atom *argv) { + int bytespersample, bigendian, swap, filetype, normalize, nchannels; + long onset, nframes, itemswritten = 0; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; + char sampbuf[SAMPBUFSIZE]; + int bufframes; + int fd = -1; + float normfactor, biggest = 0, samplerate; + t_symbol *filesym; + if (soundfiler_writeargparse(obj, &argc, &argv, &filesym, &filetype, + &bytespersample, &swap, &bigendian, &normalize, &onset, &nframes, + &samplerate)) + goto usage; + nchannels = argc; + if (nchannels < 1 || nchannels > MAXSFCHANS) goto usage; + if (samplerate < 0) samplerate = sys_getsr(); + for (int i=0; iname); + goto fail; + } + else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i])) + error("%s: bad template for tabwrite", argv[i].a_symbol->name); + nframes = min(nframes, vecsize - onset); + for (int j=0; j biggest) biggest = +vecs[i][j]; + else if (-vecs[i][j] > biggest) biggest = -vecs[i][j]; + } + } + if (nframes <= 0) { + error("soundfiler_write: no samples at onset %ld", onset); + goto fail; + } + if ((fd = create_soundfile(canvas, filesym->name, filetype, nframes, bytespersample, bigendian, nchannels, swap, samplerate)) < 0) { + post("%s: %s", filesym->name, strerror(errno)); + goto fail; + } + if (!normalize) { + if ((bytespersample != 4) && (biggest > 1)) { + post("%s: normalizing max amplitude %f to 1", filesym->name, biggest); + normalize = 1; + } else post("%s: biggest amplitude = %f", filesym->name, biggest); + } + if (normalize) normfactor = (biggest > 0 ? 32767./(32768. * biggest) : 1); else normfactor = 1; + bufframes = SAMPBUFSIZE / (nchannels * bytespersample); + for (itemswritten = 0; itemswritten < nframes; ) { + int thiswrite = nframes - itemswritten, nbytes; + thiswrite = (thiswrite > bufframes ? bufframes : thiswrite); + soundfile_xferout(argc, vecs, (unsigned char *)sampbuf, thiswrite, onset, bytespersample, bigendian, normfactor); + nbytes = write(fd, sampbuf, nchannels * bytespersample * thiswrite); + if (nbytes < nchannels * bytespersample * thiswrite) { + post("%s: %s", filesym->name, strerror(errno)); + if (nbytes > 0) itemswritten += nbytes / (nchannels * bytespersample); + break; + } + itemswritten += thiswrite; + onset += thiswrite; + } + if (fd >= 0) { + soundfile_finishwrite(obj, filesym->name, fd, filetype, nframes, itemswritten, nchannels * bytespersample, swap); + close (fd); + } + return itemswritten; +usage: + error("usage: write [flags] filename tablename..."); + post("flags: -skip -nframes -bytes -wave -aiff -nextstep ..."); + post("-big -little -normalize"); + post("(defaults to a 16-bit wave file)."); +fail: + if (fd >= 0) close(fd); + return 0; +} + +static void soundfiler_write(t_soundfiler *x, t_symbol *s, int argc, t_atom *argv) { + long bozo = soundfiler_dowrite(x, x->canvas, argc, argv); + outlet_float(x->outlet, (float)bozo); +} + +static void soundfiler_setup() { + t_class *c = soundfiler_class = class_new2("soundfiler", (t_newmethod)soundfiler_new, 0, sizeof(t_soundfiler), 0, ""); +#ifdef THREADED_SF + class_addmethod2(c, (t_method)soundfiler_t_read_addq, "read", "*"); +/* class_addmethod2(c, (t_method)soundfiler_t_write_addq, "write", "*"); */ + class_addmethod2(c, (t_method)soundfiler_t_resize_addq, "resize", "*"); + class_addmethod2(c, (t_method)soundfiler_t_const_addq, "const", "*"); +#else + class_addmethod2(c, (t_method)soundfiler_read, "read", "*"); +#endif /* THREADED_SF */ + class_addmethod2(c, (t_method)soundfiler_write, "write", "*"); +} + +/************************* readsf object ******************************/ + +/* READSF uses the Posix threads package; for the moment we're Linux +only although this should be portable to the other platforms. + +Each instance of readsf~ owns a "child" thread for doing the unix (MSW?) file +reading. The parent thread signals the child each time: + (1) a file wants opening or closing; + (2) we've eaten another 1/16 of the shared buffer (so that the + child thread should check if it's time to read some more.) +The child signals the parent whenever a read has completed. Signalling +is done by setting "conditions" and putting data in mutex-controlled common +areas. +*/ + +#define MAXBYTESPERSAMPLE 4 +#define MAXVECSIZE 128 + +#define READSIZE 65536 +#define WRITESIZE 65536 +#define DEFBUFPERCHAN 262144 +#define MINBUFSIZE (4 * READSIZE) +#define MAXBUFSIZE 16777216 /* arbitrary; just don't want to hang malloc */ + +#define REQUEST_NOTHING 0 +#define REQUEST_OPEN 1 +#define REQUEST_CLOSE 2 +#define REQUEST_QUIT 3 +#define REQUEST_BUSY 4 + +#define STATE_IDLE 0 +#define STATE_STARTUP 1 +#define STATE_STREAM 2 + +static t_class *readsf_class; + +struct t_readsf : t_object { + t_canvas *canvas; + t_clock *clock; + char *buf; /* soundfile buffer */ + int bufsize; /* buffer size in bytes */ + int noutlets; /* number of audio outlets */ + t_sample *(outvec[MAXSFCHANS]); /* audio vectors */ + int vecsize; /* vector size for transfers */ + t_outlet *bangout; /* bang-on-done outlet */ + int state; /* opened, running, or idle */ + float insamplerate; /* sample rate of input signal if known */ + /* parameters to communicate with subthread */ + int requestcode; /* pending request from parent to I/O thread */ + char *filename; /* file to open (string is permanently allocated) */ + int fileerror; /* slot for "errno" return */ + int skipheaderbytes; /* size of header we'll skip */ + t_param p; + float samplerate; /* sample rate of soundfile */ + long onsetframes; /* number of sample frames to skip */ + int fd; /* filedesc */ + int fifosize; /* buffer size appropriately rounded down */ + int fifohead; /* index of next byte to get from file */ + int fifotail; /* index of next byte the ugen will read */ + int eof; /* true if fifohead has stopped changing */ + int sigcountdown; /* counter for signalling child for more data */ + int sigperiod; /* number of ticks per signal */ + int filetype; /* writesf~ only; type of file to create */ + int itemswritten; /* writesf~ only; items writen */ + int swap; /* writesf~ only; true if byte swapping */ + float f; /* writesf~ only; scalar for signal inlet */ + pthread_mutex_t mutex; + pthread_cond_t requestcondition; + pthread_cond_t answercondition; + pthread_t childthread; +}; + + +/************** the child thread which performs file I/O ***********/ + +#if 1 +#define sfread_cond_wait pthread_cond_wait +#define sfread_cond_signal pthread_cond_signal +#else +#include /* debugging version... */ +#include +static void readsf_fakewait(pthread_mutex_t *b) { + struct timeval timout; + timout.tv_sec = 0; + timout.tv_usec = 1000000; + pthread_mutex_unlock(b); + select(0, 0, 0, 0, &timout); + pthread_mutex_lock(b); +} + +#define sfread_cond_wait(a,b) readsf_fakewait(b) +#define sfread_cond_signal(a) +#endif + +static void *readsf_child_main(void *zz) { + t_readsf *x = (t_readsf *)zz; + pthread_mutex_lock(&x->mutex); + while (1) { + int fd, fifohead; + char *buf; + if (x->requestcode == REQUEST_NOTHING) { + sfread_cond_signal(&x->answercondition); + sfread_cond_wait(&x->requestcondition, &x->mutex); + } else if (x->requestcode == REQUEST_OPEN) { + int sysrtn, wantbytes; + /* copy file stuff out of the data structure so we can + relinquish the mutex while we're in open_soundfile(). */ + long onsetframes = x->onsetframes; + t_param p; + p.bytelimit = 0x7fffffff; + int skipheaderbytes = x->skipheaderbytes; + p.bytespersample = x->p.bytespersample; + p.bigendian = x->p.bigendian; + /* alter the request code so that an ensuing "open" will get noticed. */ + x->requestcode = REQUEST_BUSY; + x->fileerror = 0; + /* if there's already a file open, close it */ + if (x->fd >= 0) { + fd = x->fd; + pthread_mutex_unlock(&x->mutex); + close (fd); + pthread_mutex_lock(&x->mutex); + x->fd = -1; + if (x->requestcode != REQUEST_BUSY) goto lost; + } + /* open the soundfile with the mutex unlocked */ + pthread_mutex_unlock(&x->mutex); + fd = open_soundfile(canvas_getdir(x->canvas)->name, x->filename, skipheaderbytes, &p, onsetframes); + pthread_mutex_lock(&x->mutex); + /* copy back into the instance structure. */ + x->p = p; + x->fd = fd; + if (fd < 0) { + x->fileerror = errno; + x->eof = 1; + goto lost; + } + /* check if another request has been made; if so, field it */ + if (x->requestcode != REQUEST_BUSY) goto lost; + x->fifohead = 0; + /* set fifosize from bufsize. fifosize must be a multiple of the number of bytes eaten for each DSP + tick. We pessimistically assume MAXVECSIZE samples per tick since that could change. There could be a + problem here if the vector size increases while a soundfile is being played... */ + x->fifosize = x->bufsize - (x->bufsize % (x->p.bytesperchannel() * MAXVECSIZE)); + /* arrange for the "request" condition to be signalled 16 times per buffer */ + x->sigcountdown = x->sigperiod = x->fifosize / (16 * x->p.bytesperchannel() * x->vecsize); + /* in a loop, wait for the fifo to get hungry and feed it */ + while (x->requestcode == REQUEST_BUSY) { + int fifosize = x->fifosize; + if (x->eof) break; + if (x->fifohead >= x->fifotail) { + /* if the head is >= the tail, we can immediately read to the end of the fifo. Unless, that is, we + would read all the way to the end of the buffer and the "tail" is zero; this would fill the buffer completely + which isn't allowed because you can't tell a completely full buffer from an empty one. */ + if (x->fifotail || (fifosize - x->fifohead > READSIZE)) { + wantbytes = min(min(fifosize - x->fifohead, READSIZE),(int)x->p.bytelimit); + } else { + sfread_cond_signal(&x->answercondition); + sfread_cond_wait(&x->requestcondition, &x->mutex); + continue; + } + } else { + /* otherwise check if there are at least READSIZE bytes to read. If not, wait and loop back. */ + wantbytes = x->fifotail - x->fifohead - 1; + if (wantbytes < READSIZE) { + sfread_cond_signal(&x->answercondition); + sfread_cond_wait(&x->requestcondition, &x->mutex); + continue; + } else wantbytes = READSIZE; + wantbytes = min(wantbytes,(int)x->p.bytelimit); + } + fd = x->fd; + buf = x->buf; + fifohead = x->fifohead; + pthread_mutex_unlock(&x->mutex); + sysrtn = read(fd, buf + fifohead, wantbytes); + pthread_mutex_lock(&x->mutex); + if (x->requestcode != REQUEST_BUSY) break; + if (sysrtn < 0) {x->fileerror = errno; break;} + else if (sysrtn == 0) {x->eof = 1; break;} + else { + x->fifohead += sysrtn; + x->p.bytelimit -= sysrtn; + if (x->p.bytelimit <= 0) {x->eof = 1; break;} + if (x->fifohead == fifosize) x->fifohead = 0; + } + /* signal parent in case it's waiting for data */ + sfread_cond_signal(&x->answercondition); + } + lost: + if (x->requestcode == REQUEST_BUSY) x->requestcode = REQUEST_NOTHING; + /* fell out of read loop: close file if necessary, set EOF and signal once more */ + if (x->fd >= 0) { + fd = x->fd; + pthread_mutex_unlock(&x->mutex); + close (fd); + pthread_mutex_lock(&x->mutex); + x->fd = -1; + } + sfread_cond_signal(&x->answercondition); + } else if (x->requestcode == REQUEST_CLOSE || x->requestcode == REQUEST_QUIT) { + if (x->fd >= 0) { + fd = x->fd; + pthread_mutex_unlock(&x->mutex); + close (fd); + pthread_mutex_lock(&x->mutex); + x->fd = -1; + } + x->requestcode = REQUEST_NOTHING; + sfread_cond_signal(&x->answercondition); + } + if (x->requestcode == REQUEST_QUIT) break; + } + pthread_mutex_unlock(&x->mutex); + return 0; +} + +/******** the object proper runs in the calling (parent) thread ****/ + +static void readsf_tick(t_readsf *x); + +static void *readsf_new(t_floatarg fnchannels, t_floatarg fbufsize) { + int nchannels = int(fnchannels), bufsize = int(fbufsize); + if (nchannels < 1) nchannels = 1; + else if (nchannels > MAXSFCHANS) nchannels = MAXSFCHANS; + if (bufsize <= 0) bufsize = DEFBUFPERCHAN * nchannels; + else if (bufsize < MINBUFSIZE) bufsize = MINBUFSIZE; + else if (bufsize > MAXBUFSIZE) bufsize = MAXBUFSIZE; + char *buf = (char *)getbytes(bufsize); + if (!buf) return 0; + t_readsf *x = (t_readsf *)pd_new(readsf_class); + for (int i=0; inoutlets = nchannels; + x->bangout = outlet_new(x,&s_bang); + pthread_mutex_init(&x->mutex, 0); + pthread_cond_init(&x->requestcondition, 0); + pthread_cond_init(&x->answercondition, 0); + x->vecsize = MAXVECSIZE; + x->state = STATE_IDLE; + x->clock = clock_new(x, (t_method)readsf_tick); + x->canvas = canvas_getcurrent(); + x->p.bytespersample = 2; + x->p.nchannels = 1; + x->fd = -1; + x->buf = buf; + x->bufsize = bufsize; + x->fifosize = x->fifohead = x->fifotail = x->requestcode = 0; + pthread_create(&x->childthread, 0, readsf_child_main, x); + return x; +} + +static void readsf_tick(t_readsf *x) {outlet_bang(x->bangout);} + +static t_int *readsf_perform(t_int *w) { + t_readsf *x = (t_readsf *)(w[1]); + int vecsize = x->vecsize, noutlets = x->noutlets; + if (x->state == STATE_STREAM) { + int wantbytes; + pthread_mutex_lock(&x->mutex); + wantbytes = vecsize * x->p.bytesperchannel(); + while (!x->eof && x->fifohead >= x->fifotail && x->fifohead < x->fifotail + wantbytes-1) { + sfread_cond_signal(&x->requestcondition); + sfread_cond_wait(&x->answercondition, &x->mutex); + } + if (x->eof && x->fifohead >= x->fifotail && x->fifohead < x->fifotail + wantbytes-1) { + if (x->fileerror) { + error("dsp: %s: %s", x->filename, x->fileerror == EIO ? "unknown or bad header format" : strerror(x->fileerror)); + } + clock_delay(x->clock, 0); + x->state = STATE_IDLE; + /* if there's a partial buffer left, copy it out. */ + int xfersize = (x->fifohead - x->fifotail + 1) / x->p.bytesperchannel(); + if (xfersize) { + soundfile_xferin(x->p.nchannels, noutlets, x->outvec, 0, + (unsigned char *)(x->buf + x->fifotail), xfersize, x->p.bytespersample, x->p.bigendian); + vecsize -= xfersize; + } + /* then zero out the (rest of the) output */ + for (int i=0; ioutvec[i] + xfersize; + for (int j=vecsize; j--; ) *fp++ = 0; + } + sfread_cond_signal(&x->requestcondition); + pthread_mutex_unlock(&x->mutex); + return w+2; + } + soundfile_xferin(x->p.nchannels, noutlets, x->outvec, 0, (unsigned char *)(x->buf + x->fifotail), vecsize, x->p.bytespersample, x->p.bigendian); + x->fifotail += wantbytes; + if (x->fifotail >= x->fifosize) x->fifotail = 0; + if ((--x->sigcountdown) <= 0) { + sfread_cond_signal(&x->requestcondition); + x->sigcountdown = x->sigperiod; + } + pthread_mutex_unlock(&x->mutex); + } else { + for (int i=0; ioutvec[i]; + for (int j=vecsize; j--; ) *fp++=0; + } + } + return w+2; +} + +static void readsf_start(t_readsf *x) { + /* start making output. If we're in the "startup" state change + to the "running" state. */ + if (x->state == STATE_STARTUP) x->state = STATE_STREAM; + else error("readsf: start requested with no prior 'open'"); +} + +static void readsf_stop(t_readsf *x) { + /* LATER rethink whether you need the mutex just to set a variable? */ + pthread_mutex_lock(&x->mutex); + x->state = STATE_IDLE; + x->requestcode = REQUEST_CLOSE; + sfread_cond_signal(&x->requestcondition); + pthread_mutex_unlock(&x->mutex); +} + +static void readsf_float(t_readsf *x, t_floatarg f) { + if (f != 0) readsf_start(x); else readsf_stop(x); +} + +/* open method. Called as: open filename [skipframes headersize channels bytespersample endianness] + (if headersize is zero, header is taken to be automatically detected; thus, use the special "-1" to mean a truly headerless file.) */ +static void readsf_open(t_readsf *x, t_symbol *s, int argc, t_atom *argv) { + t_symbol *filesym = atom_getsymbolarg(0, argc, argv); + t_float onsetframes = atom_getfloatarg(1, argc, argv); + t_float headerbytes = atom_getfloatarg(2, argc, argv); + t_float channels = atom_getfloatarg(3, argc, argv); + t_float bytespersample = atom_getfloatarg(4, argc, argv); + t_symbol *endian = atom_getsymbolarg(5, argc, argv); + if (!*filesym->name) return; + pthread_mutex_lock(&x->mutex); + x->requestcode = REQUEST_OPEN; + x->filename = filesym->name; + x->fifotail = 0; + x->fifohead = 0; + if (*endian->name == 'b') x->p.bigendian = 1; + else if (*endian->name == 'l') x->p.bigendian = 0; + else if (*endian->name) error("endianness neither 'b' nor 'l'"); + else x->p.bigendian = garray_ambigendian(); + x->onsetframes = max(long(onsetframes),0L); + x->skipheaderbytes = int(headerbytes > 0 ? headerbytes : headerbytes == 0 ? -1 : 0); + x->p.nchannels = max(int(channels),1); + x->p.bytespersample = max(int(bytespersample),2); + x->eof = 0; + x->fileerror = 0; + x->state = STATE_STARTUP; + sfread_cond_signal(&x->requestcondition); + pthread_mutex_unlock(&x->mutex); +} + +static void readsf_dsp(t_readsf *x, t_signal **sp) { + int i, noutlets = x->noutlets; + pthread_mutex_lock(&x->mutex); + x->vecsize = sp[0]->n; + x->sigperiod = (x->fifosize / (x->p.bytesperchannel() * x->vecsize)); + for (i = 0; i < noutlets; i++) x->outvec[i] = sp[i]->v; + pthread_mutex_unlock(&x->mutex); + dsp_add(readsf_perform, 1, x); +} + +static void readsf_print(t_readsf *x) { + post("state %d", x->state); + post("fifo head %d", x->fifohead); + post("fifo tail %d", x->fifotail); + post("fifo size %d", x->fifosize); + post("fd %d", x->fd); + post("eof %d", x->eof); +} + +static void readsf_free(t_readsf *x) { + /* request QUIT and wait for acknowledge */ + void *threadrtn; + pthread_mutex_lock(&x->mutex); + x->requestcode = REQUEST_QUIT; + sfread_cond_signal(&x->requestcondition); + while (x->requestcode != REQUEST_NOTHING) { + sfread_cond_signal(&x->requestcondition); + sfread_cond_wait(&x->answercondition, &x->mutex); + } + pthread_mutex_unlock(&x->mutex); + if (pthread_join(x->childthread, &threadrtn)) error("readsf_free: join failed"); + pthread_cond_destroy(&x->requestcondition); + pthread_cond_destroy(&x->answercondition); + pthread_mutex_destroy(&x->mutex); + free(x->buf); + clock_free(x->clock); +} + +static void readsf_setup() { + t_class *c = readsf_class = class_new2("readsf~", (t_newmethod)readsf_new, (t_method)readsf_free, sizeof(t_readsf), 0, "FF"); + class_addfloat(c, (t_method)readsf_float); + class_addmethod2(c, (t_method)readsf_start, "start",""); + class_addmethod2(c, (t_method)readsf_stop, "stop",""); + class_addmethod2(c, (t_method)readsf_dsp, "dsp",""); + class_addmethod2(c, (t_method)readsf_open, "open","*"); + class_addmethod2(c, (t_method)readsf_print, "print",""); +} + +/******************************* writesf *******************/ + +static t_class *writesf_class; +typedef t_readsf t_writesf; /* just re-use the structure */ + +/************** the child thread which performs file I/O ***********/ + +static void *writesf_child_main(void *zz) { + t_writesf *x = (t_writesf*)zz; + pthread_mutex_lock(&x->mutex); + while (1) { + if (x->requestcode == REQUEST_NOTHING) { + sfread_cond_signal(&x->answercondition); + sfread_cond_wait(&x->requestcondition, &x->mutex); + } else if (x->requestcode == REQUEST_OPEN) { + int fd, sysrtn, writebytes; + /* copy file stuff out of the data structure so we can relinquish the mutex while we're in open_soundfile(). */ + int filetype = x->filetype; + char *filename = x->filename; + t_canvas *canvas = x->canvas; + float samplerate = x->samplerate; + /* alter the request code so that an ensuing "open" will get noticed. */ + x->requestcode = REQUEST_BUSY; + x->fileerror = 0; + /* if there's already a file open, close it. This should never happen since + writesf_open() calls stop if needed and then waits until we're idle. */ + if (x->fd >= 0) { + int bytesperframe = x->p.bytesperchannel(); + char *filename = x->filename; + int fd = x->fd; + int filetype = x->filetype; + int itemswritten = x->itemswritten; + int swap = x->swap; + pthread_mutex_unlock(&x->mutex); + soundfile_finishwrite(x, filename, fd, filetype, 0x7fffffff, itemswritten, bytesperframe, swap); + close (fd); + pthread_mutex_lock(&x->mutex); + x->fd = -1; + if (x->requestcode != REQUEST_BUSY) continue; + } + /* open the soundfile with the mutex unlocked */ + pthread_mutex_unlock(&x->mutex); + fd = create_soundfile(canvas, filename, filetype, 0, + x->p.bytespersample, x->p.bigendian, x->p.nchannels, garray_ambigendian() != x->p.bigendian, samplerate); + pthread_mutex_lock(&x->mutex); + if (fd < 0) { + x->fd = -1; + x->eof = 1; + x->fileerror = errno; + x->requestcode = REQUEST_NOTHING; + continue; + } + /* check if another request has been made; if so, field it */ + if (x->requestcode != REQUEST_BUSY) continue; + x->fd = fd; + x->fifotail = 0; + x->itemswritten = 0; + x->swap = garray_ambigendian() != x->p.bigendian; + /* in a loop, wait for the fifo to have data and write it to disk */ + while (x->requestcode == REQUEST_BUSY || (x->requestcode == REQUEST_CLOSE && x->fifohead != x->fifotail)) { + int fifosize = x->fifosize, fifotail; + char *buf = x->buf; + /* if the head is < the tail, we can immediately write + from tail to end of fifo to disk; otherwise we hold off + writing until there are at least WRITESIZE bytes in the + buffer */ + if (x->fifohead < x->fifotail || x->fifohead >= x->fifotail + WRITESIZE + || (x->requestcode == REQUEST_CLOSE && x->fifohead != x->fifotail)) { + writebytes = (x->fifohead < x->fifotail ? fifosize : x->fifohead) - x->fifotail; + if (writebytes > READSIZE) writebytes = READSIZE; + } else { + sfread_cond_signal(&x->answercondition); + sfread_cond_wait(&x->requestcondition,&x->mutex); + continue; + } + fifotail = x->fifotail; + fd = x->fd; + pthread_mutex_unlock(&x->mutex); + sysrtn = write(fd, buf + fifotail, writebytes); + pthread_mutex_lock(&x->mutex); + if (x->requestcode != REQUEST_BUSY && x->requestcode != REQUEST_CLOSE) break; + if (sysrtn < writebytes) {x->fileerror = errno; break;} + else { + x->fifotail += sysrtn; + if (x->fifotail == fifosize) x->fifotail = 0; + } + x->itemswritten += sysrtn / x->p.bytesperchannel(); + /* signal parent in case it's waiting for data */ + sfread_cond_signal(&x->answercondition); + } + } else if (x->requestcode == REQUEST_CLOSE || x->requestcode == REQUEST_QUIT) { + if (x->fd >= 0) { + int bytesperframe = x->p.bytesperchannel(); + char *filename = x->filename; + int fd = x->fd; + int filetype = x->filetype; + int itemswritten = x->itemswritten; + int swap = x->swap; + pthread_mutex_unlock(&x->mutex); + soundfile_finishwrite(x, filename, fd, filetype, 0x7fffffff, itemswritten, bytesperframe, swap); + close(fd); + pthread_mutex_lock(&x->mutex); + x->fd = -1; + } + x->requestcode = REQUEST_NOTHING; + sfread_cond_signal(&x->answercondition); + } + if (x->requestcode == REQUEST_QUIT) break; + } + pthread_mutex_unlock(&x->mutex); + return 0; +} + +/******** the object proper runs in the calling (parent) thread ****/ + +static void *writesf_new(t_floatarg fnchannels, t_floatarg fbufsize) { + int nchannels = int(fnchannels), bufsize = int(fbufsize), i; + if (nchannels < 1) nchannels = 1; + else if (nchannels > MAXSFCHANS) nchannels = MAXSFCHANS; + if (bufsize <= 0) bufsize = DEFBUFPERCHAN * nchannels; + else if (bufsize < MINBUFSIZE) bufsize = MINBUFSIZE; + else if (bufsize > MAXBUFSIZE) bufsize = MAXBUFSIZE; + char *buf = (char *)getbytes(bufsize); + if (!buf) return 0; + t_writesf *x = (t_writesf *)pd_new(writesf_class); + for (i = 1; i < nchannels; i++) inlet_new(x,x, &s_signal, &s_signal); + x->f = 0; + x->p.nchannels = nchannels; + pthread_mutex_init(&x->mutex, 0); + pthread_cond_init(&x->requestcondition, 0); + pthread_cond_init(&x->answercondition, 0); + x->vecsize = MAXVECSIZE; + x->insamplerate = x->samplerate = 0; + x->state = STATE_IDLE; + x->clock = 0; /* no callback needed here */ + x->canvas = canvas_getcurrent(); + x->p.bytespersample = 2; + x->fd = -1; + x->buf = buf; + x->bufsize = bufsize; + x->fifosize = x->fifohead = x->fifotail = x->requestcode = 0; + pthread_create(&x->childthread, 0, writesf_child_main, x); + return x; +} + +static t_int *writesf_perform(t_int *w) { + t_writesf *x = (t_writesf *)(w[1]); + if (x->state == STATE_STREAM) { + int wantbytes; + pthread_mutex_lock(&x->mutex); + wantbytes = x->vecsize * x->p.bytesperchannel(); + while (x->fifotail > x->fifohead && x->fifotail < x->fifohead + wantbytes + 1) { + sfread_cond_signal(&x->requestcondition); + sfread_cond_wait(&x->answercondition, &x->mutex); + /* resync local cariables -- bug fix thanks to Shahrokh */ + wantbytes = x->vecsize * x->p.bytesperchannel(); + } + soundfile_xferout(x->p.nchannels, x->outvec, (unsigned char *)(x->buf + x->fifohead), x->vecsize, 0, x->p.bytespersample, x->p.bigendian, 1.); + x->fifohead += wantbytes; + if (x->fifohead >= x->fifosize) x->fifohead = 0; + if ((--x->sigcountdown) <= 0) { + sfread_cond_signal(&x->requestcondition); + x->sigcountdown = x->sigperiod; + } + pthread_mutex_unlock(&x->mutex); + } + return w+2; +} + +static void writesf_start(t_writesf *x) { + /* start making output. If we're in the "startup" state change to the "running" state. */ + if (x->state == STATE_STARTUP) x->state = STATE_STREAM; + else error("writesf: start requested with no prior 'open'"); +} + +static void writesf_stop(t_writesf *x) { + /* LATER rethink whether you need the mutex just to set a Svariable? */ + pthread_mutex_lock(&x->mutex); + x->state = STATE_IDLE; + x->requestcode = REQUEST_CLOSE; + sfread_cond_signal(&x->requestcondition); + pthread_mutex_unlock(&x->mutex); +} + +/* open method. Called as: open [args] filename with args as in soundfiler_writeargparse(). */ +static void writesf_open(t_writesf *x, t_symbol *s, int argc, t_atom *argv) { + t_symbol *filesym; + int filetype, bytespersample, swap, bigendian, normalize; + long onset, nframes; + float samplerate; + if (x->state != STATE_IDLE) writesf_stop(x); + if (soundfiler_writeargparse(x, &argc, &argv, &filesym, &filetype, &bytespersample, &swap, &bigendian, + &normalize, &onset, &nframes, &samplerate)) { + error("writesf~: usage: open [-bytes [234]] [-wave,-nextstep,-aiff] ..."); + post("... [-big,-little] [-rate ####] filename"); + return; + } + if (normalize || onset || (nframes != 0x7fffffff)) + error("normalize/onset/nframes argument to writesf~: ignored"); + if (argc) error("extra argument(s) to writesf~: ignored"); + pthread_mutex_lock(&x->mutex); + while (x->requestcode != REQUEST_NOTHING) { + sfread_cond_signal(&x->requestcondition); + sfread_cond_wait(&x->answercondition, &x->mutex); + } + x->p.bytespersample = max(bytespersample,2); + x->swap = swap; + x->p.bigendian = bigendian; + x->filename = filesym->name; + x->filetype = filetype; + x->itemswritten = 0; + x->requestcode = REQUEST_OPEN; + x->fifotail = 0; + x->fifohead = 0; + x->eof = 0; + x->fileerror = 0; + x->state = STATE_STARTUP; + x->samplerate = samplerate>0 ? samplerate : x->insamplerate>0 ? x->insamplerate : sys_getsr(); + /* set fifosize from bufsize. fifosize must be a multiple of the number of bytes eaten for each DSP tick. */ + x->fifosize = x->bufsize - x->bufsize % (x->p.bytesperchannel() * MAXVECSIZE); + /* arrange for the "request" condition to be signalled 16 times per buffer */ + x->sigcountdown = x->sigperiod = x->fifosize / (16 * x->p.bytesperchannel() * x->vecsize); + sfread_cond_signal(&x->requestcondition); + pthread_mutex_unlock(&x->mutex); +} + +static void writesf_dsp(t_writesf *x, t_signal **sp) { + int ninlets = x->p.nchannels; + pthread_mutex_lock(&x->mutex); + x->vecsize = sp[0]->n; + x->sigperiod = x->fifosize / (x->p.bytesperchannel() * x->vecsize); + for (int i=0; ioutvec[i] = sp[i]->v; + x->insamplerate = sp[0]->sr; + pthread_mutex_unlock(&x->mutex); + dsp_add(writesf_perform, 1, x); +} + +static void writesf_print(t_writesf *x) { + post("state %d", x->state); + post("fifo head %d", x->fifohead); + post("fifo tail %d", x->fifotail); + post("fifo size %d", x->fifosize); + post("fd %d", x->fd); + post("eof %d", x->eof); +} + +static void writesf_free(t_writesf *x) { + /* request QUIT and wait for acknowledge */ + void *threadrtn; + pthread_mutex_lock(&x->mutex); + x->requestcode = REQUEST_QUIT; + /* post("stopping writesf thread..."); */ + sfread_cond_signal(&x->requestcondition); + while (x->requestcode != REQUEST_NOTHING) { + /* post("signalling..."); */ + sfread_cond_signal(&x->requestcondition); + sfread_cond_wait(&x->answercondition, &x->mutex); + } + pthread_mutex_unlock(&x->mutex); + if (pthread_join(x->childthread, &threadrtn)) error("writesf_free: join failed"); + /* post("... done."); */ + pthread_cond_destroy(&x->requestcondition); + pthread_cond_destroy(&x->answercondition); + pthread_mutex_destroy(&x->mutex); + free(x->buf); +} + +static void writesf_setup() { + t_class *c = writesf_class = class_new2("writesf~", (t_newmethod)writesf_new, (t_method)writesf_free, sizeof(t_writesf), 0, "FF"); + class_addmethod2(c, (t_method)writesf_start, "start", ""); + class_addmethod2(c, (t_method)writesf_stop, "stop", ""); + class_addmethod2(c, (t_method)writesf_dsp, "dsp", ""); + class_addmethod2(c, (t_method)writesf_open, "open", "*"); + class_addmethod2(c, (t_method)writesf_print, "print", ""); + CLASS_MAINSIGNALIN(c, t_writesf, f); +} + +/* ------------------------ global setup routine ------------------------- */ + +void d_soundfile_setup() { + if (sizeof(uint16)!=2) bug("uint16 should really be 16 bits"); + if (sizeof(uint32)!=4) bug("uint32 should really be 32 bits"); + soundfiler_setup(); + readsf_setup(); + writesf_setup(); +} -- cgit v1.2.1