diff options
Diffstat (limited to 'threadlib/src/threadedsf.c')
-rwxr-xr-x | threadlib/src/threadedsf.c | 1919 |
1 files changed, 1919 insertions, 0 deletions
diff --git a/threadlib/src/threadedsf.c b/threadlib/src/threadedsf.c new file mode 100755 index 0000000..c9d0b9f --- /dev/null +++ b/threadlib/src/threadedsf.c @@ -0,0 +1,1919 @@ +/* +* +* threadedsf +* +* this is a little bit hacked version of the +* threaded soundfiler of pd_devel_0.38 by Tim Blechmann +* +* (c) 2005, Georg Holzmann, <grh@mur.at> +*/ + +/* 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 */ + + +#include "threadlib.h" + +#ifndef MSW +#include <unistd.h> +#include <fcntl.h> +#endif +#ifdef MSW +#include <io.h> +#endif +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "g_canvas.h" + +#define MAXSFCHANS 64 + + +// forward declarations + +struct _garray +{ + t_gobj x_gobj; + t_scalar *x_scalar; /* scalar "containing" the array */ + t_glist *x_glist; /* containing glist */ + t_symbol *x_name; /* unexpanded name (possibly with leading '$') */ + t_symbol *x_realname; /* expanded name (symbol we're bound to) */ + char x_usedindsp; /* true if some DSP routine is using this */ + char x_saveit; /* true if we should save this with parent */ + char x_listviewing; /* true if list view window is open */ +}; + +t_array *garray_getarray(t_garray *x); +int garray_ambigendian(void); + + + +/***************** 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 */ + +typedef struct _nextstep +{ + char ns_fileid[4]; /* magic number '.snd' if file is big-endian */ + uint32 ns_onset; /* byte offset of first sample */ + uint32 ns_length; /* length of sound in bytes */ + uint32 ns_format; /* format; see below */ + uint32 ns_sr; /* sample rate */ + uint32 ns_nchans; /* number of channels */ + char ns_info[4]; /* comment */ +} t_nextstep; + +#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. */ + +typedef struct _wave +{ + char w_fileid[4]; /* chunk id 'RIFF' */ + uint32 w_chunksize; /* chunk size */ + char w_waveid[4]; /* wave chunk id 'WAVE' */ + char w_fmtid[4]; /* format chunk id 'fmt ' */ + uint32 w_fmtchunksize; /* format chunk size */ + uint16 w_fmttag; /* format tag (WAV_INT etc) */ + uint16 w_nchannels; /* number of channels */ + uint32 w_samplespersec; /* sample rate in hz */ + uint32 w_navgbytespersec; /* average bytes per second */ + uint16 w_nblockalign; /* number of bytes per frame */ + uint16 w_nbitspersample; /* number of bits in a sample */ + char w_datachunkid[4]; /* data chunk id 'data' */ + uint32 w_datachunksize; /* length of data chunk */ +} t_wave; + +typedef struct _fmt /* format chunk */ +{ + uint16 f_fmttag; /* format tag, 1 for PCM */ + uint16 f_nchannels; /* number of channels */ + uint32 f_samplespersec; /* sample rate in hz */ + uint32 f_navgbytespersec; /* average bytes per second */ + uint16 f_nblockalign; /* number of bytes per frame */ + uint16 f_nbitspersample; /* number of bits in a sample */ +} t_fmt; + +typedef struct _wavechunk /* ... and the last two items */ +{ + char wc_id[4]; /* data chunk id, e.g., 'data' or 'fmt ' */ + uint32 wc_size; /* length of data chunk */ +} t_wavechunk; + +#define WAV_INT 1 +#define WAV_FLOAT 3 + +/* the AIFF header. I'm assuming AIFC is compatible but don't really know + that. */ + +typedef struct _datachunk +{ + char dc_id[4]; /* data chunk id 'SSND' */ + uint32 dc_size; /* length of data chunk */ +} t_datachunk; + +typedef struct _comm +{ + uint16 c_nchannels; /* number of channels */ + uint16 c_nframeshi; /* # of sample frames (hi) */ + uint16 c_nframeslo; /* # of sample frames (lo) */ + uint16 c_bitspersamp; /* bits per sample */ + unsigned char c_samprate[10]; /* sample rate, 80-bit float! */ +} t_comm; + + /* this version is more convenient for writing them out: */ +typedef struct _aiff +{ + char a_fileid[4]; /* chunk id 'FORM' */ + uint32 a_chunksize; /* chunk size */ + char a_aiffid[4]; /* aiff chunk id 'AIFF' */ + char a_fmtid[4]; /* format chunk id 'COMM' */ + uint32 a_fmtchunksize; /* format chunk size, 18 */ + uint16 a_nchannels; /* number of channels */ + uint16 a_nframeshi; /* # of sample frames (hi) */ + uint16 a_nframeslo; /* # of sample frames (lo) */ + uint16 a_bitspersamp; /* bits per sample */ + unsigned char a_samprate[10]; /* sample rate, 80-bit float! */ +} t_aiff; + +#define AIFFHDRSIZE 38 /* probably not what sizeof() gives */ + + +#define AIFFPLUS (AIFFHDRSIZE + 8) /* header size including first 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) + +#define OBUFSIZE MAXPDSTRING /* assume MAXPDSTRING is bigger than headers */ + +#ifdef MSW +#include <fcntl.h> +#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(void); + +/* 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(const char *dirname, const char *filename, int headersize, + int *p_bytespersamp, int *p_bigendian, int *p_nchannels, long *p_bytelimit, + long skipframes) +{ + char buf[OBUFSIZE], *bufptr; + int fd, format, nchannels, bigendian, bytespersamp, swap, sysrtn; + long bytelimit = 0x7fffffff; + errno = 0; + fd = open_via_path(dirname, filename, + "", buf, &bufptr, MAXPDSTRING, 1); + if (fd < 0) + return (-1); + if (headersize >= 0) /* header detection overridden */ + { + bigendian = *p_bigendian; + nchannels = *p_nchannels; + bytespersamp = *p_bytespersamp; + bytelimit = *p_bytelimit; + } + else + { + int bytesread = read(fd, buf, READHDRSIZE); + int format; + if (bytesread < 4) + goto badheader; + if (!strncmp(buf, ".snd", 4)) + format = FORMAT_NEXT, bigendian = 1; + else if (!strncmp(buf, "dns.", 4)) + format = FORMAT_NEXT, bigendian = 0; + else if (!strncmp(buf, "RIFF", 4)) + { + if (bytesread < 12 || strncmp(buf + 8, "WAVE", 4)) + goto badheader; + format = FORMAT_WAVE, bigendian = 0; + } + else if (!strncmp(buf, "FORM", 4)) + { + if (bytesread < 12 || strncmp(buf + 8, "AIFF", 4)) + goto badheader; + format = FORMAT_AIFF, bigendian = 1; + } + else + goto badheader; + swap = (bigendian != garray_ambigendian()); + if (format == FORMAT_NEXT) /* nextstep header */ + { + uint32 param; + if (bytesread < (int)sizeof(t_nextstep)) + goto badheader; + nchannels = swap4(((t_nextstep *)buf)->ns_nchans, swap); + format = swap4(((t_nextstep *)buf)->ns_format, swap); + headersize = swap4(((t_nextstep *)buf)->ns_onset, swap); + if (format == NS_FORMAT_LINEAR_16) + bytespersamp = 2; + else if (format == NS_FORMAT_LINEAR_24) + bytespersamp = 3; + else if (format == NS_FORMAT_FLOAT) + bytespersamp = 4; + else goto badheader; + 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. */ + nchannels = 1; + bytespersamp = 2; + /* copy the first chunk header to beginnning of buffer. */ + memcpy(buf, buf + headersize, sizeof(t_wavechunk)); + /* post("chunk %c %c %c %c", + ((t_wavechunk *)buf)->wc_id[0], + ((t_wavechunk *)buf)->wc_id[1], + ((t_wavechunk *)buf)->wc_id[2], + ((t_wavechunk *)buf)->wc_id[3]); */ + /* read chunks in loop until we get to the data chunk */ + while (strncmp(((t_wavechunk *)buf)->wc_id, "data", 4)) + { + long chunksize = swap4(((t_wavechunk *)buf)->wc_size, + swap), seekto = headersize + chunksize + 8, seekout; + + if (!strncmp(((t_wavechunk *)buf)->wc_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; + nchannels = swap2(((t_fmt *)buf)->f_nchannels, swap); + format = swap2(((t_fmt *)buf)->f_nbitspersample, swap); + if (format == 16) + bytespersamp = 2; + else if (format == 24) + bytespersamp = 3; + else if (format == 32) + bytespersamp = 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; + /* post("new chunk %c %c %c %c at %d", + ((t_wavechunk *)buf)->wc_id[0], + ((t_wavechunk *)buf)->wc_id[1], + ((t_wavechunk *)buf)->wc_id[2], + ((t_wavechunk *)buf)->wc_id[3], seekto); */ + headersize = seekto; + } + bytelimit = swap4(((t_wavechunk *)buf)->wc_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. */ + nchannels = 1; + bytespersamp = 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)->dc_id, "SSND", 4)) + { + long chunksize = swap4(((t_datachunk *)buf)->dc_size, + swap), seekto = headersize + chunksize + 8, seekout; + /* post("chunk %c %c %c %c seek %d", + ((t_datachunk *)buf)->dc_id[0], + ((t_datachunk *)buf)->dc_id[1], + ((t_datachunk *)buf)->dc_id[2], + ((t_datachunk *)buf)->dc_id[3], seekto); */ + if (!strncmp(((t_datachunk *)buf)->dc_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; + nchannels = swap2(((t_comm *)buf)->c_nchannels, swap); + format = swap2(((t_comm *)buf)->c_bitspersamp, swap); + if (format == 16) + bytespersamp = 2; + else if (format == 24) + bytespersamp = 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; + } + bytelimit = swap4(((t_datachunk *)buf)->dc_size, swap); + headersize += 8; + } + } + /* seek past header and any sample frames to skip */ + sysrtn = lseek(fd, nchannels * bytespersamp * skipframes + headersize, 0); + if (sysrtn != nchannels * bytespersamp * skipframes + headersize) + return (-1); + bytelimit -= nchannels * bytespersamp * skipframes; + if (bytelimit < 0) + bytelimit = 0; + /* copy sample format back to caller */ + *p_bigendian = bigendian; + *p_nchannels = nchannels; + *p_bytespersamp = bytespersamp; + *p_bytelimit = bytelimit; + return (fd); +badheader: + /* the header wasn't recognized. We're threadable here so let's not + print out the error... */ + errno = EIO; + return (-1); +} + +static void soundfile_xferin(int sfchannels, int nvecs, float **vecs, + long itemsread, unsigned char *buf, int nitems, int bytespersamp, + int bigendian) +{ + int i, j; + unsigned char *sp, *sp2; + float *fp; + int nchannels = (sfchannels < nvecs ? sfchannels : nvecs); + int bytesperframe = bytespersamp * sfchannels; + for (i = 0, sp = buf; i < nchannels; i++, sp += bytespersamp) + { + if (bytespersamp == 2) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[0] << 24) | (sp2[1] << 16)); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[1] << 24) | (sp2[0] << 16)); + } + } + else if (bytespersamp == 3) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[0] << 24) | (sp2[1] << 16) + | (sp2[2] << 8)); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[2] << 24) | (sp2[1] << 16) + | (sp2[0] << 8)); + } + } + else if (bytespersamp == 4) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *(long *)fp = ((sp2[0] << 24) | (sp2[1] << 16) + | (sp2[2] << 8) | sp2[3]); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *(long *)fp = ((sp2[3] << 24) | (sp2[2] << 16) + | (sp2[1] << 8) | sp2[0]); + } + } + } + /* zero out other outputs */ + for (i = sfchannels; i < nvecs; i++) + for (j = nitems, fp = vecs[i]; j--; ) + *fp++ = 0; + +} + + /* soundfiler_write ... + + usage: write [flags] filename table ... + flags: + -nframes <frames> + -skip <frames> + -bytes <bytes per sample> + -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 bytespersamp = 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_w.w_symbol->s_name == '-') + { + char *flag = argv->a_w.w_symbol->s_name + 1; + if (!strcmp(flag, "skip")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((onset = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "nframes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((nframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "bytes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((bytespersamp = argv[1].a_w.w_float) < 2) || + bytespersamp > 4) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "normalize")) + { + normalize = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "wave")) + { + filetype = FORMAT_WAVE; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "nextstep")) + { + filetype = FORMAT_NEXT; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "aiff")) + { + filetype = FORMAT_AIFF; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "big")) + { + endianness = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "little")) + { + endianness = 0; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "r") || !strcmp(flag, "rate")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((rate = argv[1].a_w.w_float) <= 0)) + goto usage; + argc -= 2; argv += 2; + } + else goto usage; + } + if (!argc || argv->a_type != A_SYMBOL) + goto usage; + filesym = argv->a_w.w_symbol; + + /* check if format not specified and fill in */ + if (filetype < 0) + { + if (strlen(filesym->s_name) >= 5 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".aif") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".AIF"))) + filetype = FORMAT_AIFF; + if (strlen(filesym->s_name) >= 6 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".aiff") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".AIFF"))) + filetype = FORMAT_AIFF; + if (strlen(filesym->s_name) >= 5 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".snd") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".SND"))) + filetype = FORMAT_NEXT; + if (strlen(filesym->s_name) >= 4 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".au") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".AU"))) + filetype = FORMAT_NEXT; + if (filetype < 0) + filetype = FORMAT_WAVE; + } + /* don't handle AIFF floating point samples */ + if (bytespersamp == 4) + { + if (filetype == FORMAT_AIFF) + { + pd_error(obj, "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) + pd_error(obj, "WAVE file forced to little endian"); + } + else if (filetype == FORMAT_AIFF) + { + bigendian = 1; + if (endianness == 0) + pd_error(obj, "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 = bytespersamp; + *p_swap = swap; + *p_normalize = normalize; + *p_onset = onset; + *p_nframes = nframes; + *p_bigendian = bigendian; + *p_rate = rate; + return (0); +usage: + return (-1); +} + +static int create_soundfile(t_canvas *canvas, const char *filename, + int filetype, int nframes, int bytespersamp, + int bigendian, int nchannels, int swap, float samplerate) +{ + char filenamebuf[MAXPDSTRING], buf2[MAXPDSTRING]; + char headerbuf[WRITEHDRSIZE]; + t_wave *wavehdr = (t_wave *)headerbuf; + t_nextstep *nexthdr = (t_nextstep *)headerbuf; + t_aiff *aiffhdr = (t_aiff *)headerbuf; + int fd, headersize = 0; + + strncpy(filenamebuf, filename, MAXPDSTRING-10); + filenamebuf[MAXPDSTRING-10] = 0; + + if (filetype == FORMAT_NEXT) + { + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".snd")) + strcat(filenamebuf, ".snd"); + if (bigendian) + strncpy(nexthdr->ns_fileid, ".snd", 4); + else strncpy(nexthdr->ns_fileid, "dns.", 4); + nexthdr->ns_onset = swap4(sizeof(*nexthdr), swap); + nexthdr->ns_length = 0; + nexthdr->ns_format = swap4((bytespersamp == 3 ? NS_FORMAT_LINEAR_24 : + (bytespersamp == 4 ? NS_FORMAT_FLOAT : NS_FORMAT_LINEAR_16)), swap); + nexthdr->ns_sr = swap4(samplerate, swap); + nexthdr->ns_nchans = swap4(nchannels, swap); + strcpy(nexthdr->ns_info, "Pd "); + swapstring(nexthdr->ns_info, swap); + headersize = sizeof(t_nextstep); + } + else if (filetype == FORMAT_AIFF) + { + long datasize = nframes * nchannels * bytespersamp; + long longtmp; + static unsigned char dogdoo[] = + {0x40, 0x0e, 0xac, 0x44, 0, 0, 0, 0, 0, 0, 'S', 'S', 'N', 'D'}; + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".aif") && + strcmp(filenamebuf + strlen(filenamebuf)-5, ".aiff")) + strcat(filenamebuf, ".aif"); + strncpy(aiffhdr->a_fileid, "FORM", 4); + aiffhdr->a_chunksize = swap4(datasize + sizeof(*aiffhdr) + 4, swap); + strncpy(aiffhdr->a_aiffid, "AIFF", 4); + strncpy(aiffhdr->a_fmtid, "COMM", 4); + aiffhdr->a_fmtchunksize = swap4(18, swap); + aiffhdr->a_nchannels = swap2(nchannels, swap); + longtmp = swap4(nframes, swap); + memcpy(&aiffhdr->a_nframeshi, &longtmp, 4); + aiffhdr->a_bitspersamp = swap2(8 * bytespersamp, swap); + memcpy(aiffhdr->a_samprate, dogdoo, sizeof(dogdoo)); + longtmp = swap4(datasize, swap); + memcpy(aiffhdr->a_samprate + sizeof(dogdoo), &longtmp, 4); + headersize = AIFFPLUS; + } + else /* WAVE format */ + { + long datasize = nframes * nchannels * bytespersamp; + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".wav")) + strcat(filenamebuf, ".wav"); + strncpy(wavehdr->w_fileid, "RIFF", 4); + wavehdr->w_chunksize = swap4(datasize + sizeof(*wavehdr) - 8, swap); + strncpy(wavehdr->w_waveid, "WAVE", 4); + strncpy(wavehdr->w_fmtid, "fmt ", 4); + wavehdr->w_fmtchunksize = swap4(16, swap); + wavehdr->w_fmttag = + swap2((bytespersamp == 4 ? WAV_FLOAT : WAV_INT), swap); + wavehdr->w_nchannels = swap2(nchannels, swap); + wavehdr->w_samplespersec = swap4(samplerate, swap); + wavehdr->w_navgbytespersec = + swap4((int)(samplerate * nchannels * bytespersamp), swap); + wavehdr->w_nblockalign = swap2(nchannels * bytespersamp, swap); + wavehdr->w_nbitspersample = swap2(8 * bytespersamp, swap); + strncpy(wavehdr->w_datachunkid, "data", 4); + wavehdr->w_datachunksize = swap4(datasize, swap); + headersize = sizeof(t_wave); + } + + canvas_makefilename(canvas, filenamebuf, buf2, MAXPDSTRING); + sys_bashfilename(buf2, buf2); + if ((fd = open(buf2, BINCREATE, 0666)) < 0) + 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) + pd_error(obj, "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, mofo; + + if (lseek(fd, + ((char *)(&((t_wave *)0)->w_chunksize)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(datasize + sizeof(t_wave) - 8, swap); + if (write(fd, (char *)(&mofo), 4) < 4) + goto baddonewrite; + if (lseek(fd, + ((char *)(&((t_wave *)0)->w_datachunksize)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(datasize, swap); + if (write(fd, (char *)(&mofo), 4) < 4) + goto baddonewrite; + } + if (filetype == FORMAT_AIFF) + { + long mofo; + if (lseek(fd, + ((char *)(&((t_aiff *)0)->a_nframeshi)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(nframes, swap); + if (write(fd, (char *)(&mofo), 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: + post("%s: %s", filename, strerror(errno)); +} + +static void soundfile_xferout(int nchannels, float **vecs, + unsigned char *buf, int nitems, long onset, int bytespersamp, + int bigendian, float normalfactor) +{ + int i, j; + unsigned char *sp, *sp2; + float *fp; + int bytesperframe = bytespersamp * nchannels; + long xx; + for (i = 0, sp = buf; i < nchannels; i++, sp += bytespersamp) + { + if (bytespersamp == 2) + { + float ff = normalfactor * 32768.; + if (bigendian) + { + for (j = 0, sp2 = sp, fp = vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 32768. + (*fp * ff); + xx -= 32768; + if (xx < -32767) + xx = -32767; + if (xx > 32767) + xx = 32767; + sp2[0] = (xx >> 8); + sp2[1] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 32768. + (*fp * ff); + xx -= 32768; + if (xx < -32767) + xx = -32767; + if (xx > 32767) + xx = 32767; + sp2[1] = (xx >> 8); + sp2[0] = xx; + } + } + } + else if (bytespersamp == 3) + { + float ff = normalfactor * 8388608.; + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 8388608. + (*fp * ff); + xx -= 8388608; + if (xx < -8388607) + xx = -8388607; + if (xx > 8388607) + xx = 8388607; + sp2[0] = (xx >> 16); + sp2[1] = (xx >> 8); + sp2[2] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 8388608. + (*fp * ff); + xx -= 8388608; + if (xx < -8388607) + xx = -8388607; + if (xx > 8388607) + xx = 8388607; + sp2[2] = (xx >> 16); + sp2[1] = (xx >> 8); + sp2[0] = xx; + } + } + } + else if (bytespersamp == 4) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + float f2 = *fp * normalfactor; + xx = *(long *)&f2; + sp2[0] = (xx >> 24); sp2[1] = (xx >> 16); + sp2[2] = (xx >> 8); sp2[3] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + float f2 = *fp * normalfactor; + xx = *(long *)&f2; + sp2[3] = (xx >> 24); sp2[2] = (xx >> 16); + sp2[1] = (xx >> 8); sp2[0] = xx; + } + } + } + } +} + + + +/* ------- threadedsf - reads and writes soundfiles to/from "garrays" ---- */ + +// in pd's soundfiler maxsize is 4000000 +// threaded we should be able to hande a bit more +#define DEFMAXSIZE 40000000 +#define SAMPBUFSIZE 1024 + + +static t_class *threadedsf_class; + +typedef struct _threadedsf +{ + t_object x_obj; + t_canvas *x_canvas; +} t_threadedsf; + +#include <sched.h> + +#ifdef _POSIX_MEMLOCK +#include <sys/mman.h> +#endif /* _POSIX_MEMLOCK */ + +#ifdef DEBUG +#define SFDEBUG +#endif + +static pthread_t sf_thread_id; /* id of soundfiler thread */ +//static pthread_attr_t sf_attr; + +typedef struct _sfprocess +{ + void (* process) (t_threadedsf *,t_symbol *, + int, t_atom *); /* function to call */ + t_threadedsf * x; /* soundfiler */ + int argc; + t_atom * argv; + struct _sfprocess * next; /* next object in queue */ + pthread_mutex_t mutex; +} t_sfprocess; + +/* this is the queue for all threadedsf objects */ +typedef struct _sfqueue +{ + t_sfprocess * begin; + t_sfprocess * end; + pthread_mutex_t mutex; + pthread_cond_t cond; +} t_sfqueue; + +static t_sfqueue * threadedsf_queue; + + +/* we fill the queue */ +void threadedsf_queue_add(void (* process) (t_threadedsf *,t_symbol *, + int,t_atom *), void * x, + int argc, t_atom * argv) +{ + /* preparing entry */ + t_sfprocess * last_entry = (t_sfprocess*)getbytes(sizeof(t_sfprocess)); + +#ifdef SFDEBUG + post("adding process to queue"); +#endif + + pthread_mutex_init(&(last_entry->mutex), NULL); + pthread_mutex_lock(&(last_entry->mutex)); + last_entry->process = process; + last_entry->x = x; + last_entry->argc = argc; + last_entry->argv = copybytes (argv, argc * sizeof(t_atom)); + last_entry->next = NULL; + pthread_mutex_unlock(&(last_entry->mutex)); + + /* add new entry to queue */ + pthread_mutex_lock(&(threadedsf_queue->mutex)); + + if (threadedsf_queue->begin==NULL) + { + threadedsf_queue->begin=last_entry; + threadedsf_queue->end=last_entry; + } + else + { + pthread_mutex_lock(&(threadedsf_queue->end->mutex)); + threadedsf_queue->end->next=last_entry; + pthread_mutex_unlock(&(threadedsf_queue->end->mutex)); + threadedsf_queue->end=last_entry; + } + + + if ( threadedsf_queue->begin == threadedsf_queue->end ) + { +#ifdef DEBUG + post("signaling"); +#endif + pthread_mutex_unlock(&(threadedsf_queue->mutex)); + + /* and signal the helper thread */ + pthread_cond_signal(&(threadedsf_queue->cond)); + } + else + { +#ifdef DEBUG + post("not signaling"); +#endif + pthread_mutex_unlock(&(threadedsf_queue->mutex)); + } + return; +} + +/* global threadedsf thread ... sleeping until signaled */ +void threadedsf_thread(void) +{ + t_sfprocess * me; + t_sfprocess * next; + +#ifdef DEBUG + post("threadedsf_thread ID: %d", pthread_self()); +#endif + while (1) + { +#ifdef DEBUG + post("Soundfiler sleeping"); +#endif + pthread_cond_wait(&threadedsf_queue->cond, &threadedsf_queue->mutex); +#ifdef DEBUG + post("Soundfiler awake"); +#endif + + /* work on the queue */ + while (threadedsf_queue->begin!=NULL) + { + post("threadedsf: locked queue"); + /* locking process */ + pthread_mutex_lock(&(threadedsf_queue->begin->mutex)); + + me = threadedsf_queue->begin; + + pthread_mutex_unlock(&(me->mutex)); + pthread_mutex_unlock(&(threadedsf_queue->mutex)); +#ifdef DEBUG + post("threadedsf: mutex unlocked, running process"); +#endif + + /* running the specific function */ + me->process(me->x, NULL, me->argc, me->argv); +#ifdef DEBUG + post("threadedsf: process done, locking mutex"); +#endif + + pthread_mutex_lock(&(threadedsf_queue->mutex)); + pthread_mutex_lock(&(me->mutex)); + + /* freeing the argument vector */ + freebytes(me->argv, sizeof(t_atom) * me->argc); + + /* the process struct */ + next=me->next; + threadedsf_queue->begin=next; + freebytes(me, sizeof(t_sfprocess)); + }; + threadedsf_queue->end=NULL; + } +} + +extern int sys_hipriority; /* real-time flag, true if priority boosted */ + +/* create soundfiler thread */ +void sys_start_sfthread(void) +{ + pthread_attr_t sf_attr; + struct sched_param sf_param; + + int status; + + //initialize queue + threadedsf_queue = getbytes (sizeof(t_sfqueue)); + + pthread_mutex_init (&threadedsf_queue->mutex,NULL); + pthread_cond_init (&threadedsf_queue->cond,NULL); + + threadedsf_queue->begin=threadedsf_queue->end=NULL; +/* pthread_mutex_unlock(&(threadedsf_queue->mutex)); */ + + // initialize thread + pthread_attr_init(&sf_attr); + +//#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + 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 */ + +//#endif /* _POSIX_THREAD_PRIORITY_SCHEDULING */ + + //start thread + status = pthread_create(&sf_thread_id, &sf_attr, + (void *) threadedsf_thread,NULL); + if (status != 0) + error("Couldn't create threadedsf thread: %d",status); +#ifdef DEBUG + else + post("global threadedsf thread launched, priority: %d", + sf_param.sched_priority); +#endif +} + +static void threadedsf_t_write(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_write_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_write,(void *)x,argc, argv); +} + +static void threadedsf_t_read(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_read_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_read,(void *)x,argc, argv); +} + + + /* threadedsf_read ... + + usage: read [flags] filename table ... + flags: + -skip <frames> ... frames to skip in file + -nframes <frames> + -onset <frames> ... onset in table to read into (NOT DONE YET) + -raw <headersize channels bytes endian> + -resize + -maxsize <max-size> + + TB: adapted for threaded use + */ + +/* callback prototypes */ +static t_int threadedsf_read_update_garray(t_int * w); +static t_int threadedsf_read_update_graphics(t_int * w); +static t_int threadedsf_read_output(t_int * w); + + +static void threadedsf_t_read(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + int headersize = -1, channels = 0, bytespersamp = 0, bigendian = 0, + resize = 0, i, j; + long skipframes = 0, + nframes = 0, + finalsize = 0, /* new size */ + maxsize = DEFMAXSIZE, + itemsread = 0, + 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_w.w_symbol->s_name == '-') + { + char *flag = argv->a_w.w_symbol->s_name + 1; + if (!strcmp(flag, "skip")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((skipframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "nframes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((nframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "raw")) + { + if (argc < 5 || + argv[1].a_type != A_FLOAT || + ((headersize = argv[1].a_w.w_float) < 0) || + argv[2].a_type != A_FLOAT || + ((channels = argv[2].a_w.w_float) < 1) || + (channels > MAXSFCHANS) || + argv[3].a_type != A_FLOAT || + ((bytespersamp = argv[3].a_w.w_float) < 2) || + (bytespersamp > 4) || + argv[4].a_type != A_SYMBOL || + ((endianness = argv[4].a_w.w_symbol->s_name[0]) != 'b' + && endianness != 'l' && endianness != 'n')) + goto usage; + if (endianness == 'b') + bigendian = 1; + else if (endianness == 'l') + bigendian = 0; + else + bigendian = garray_ambigendian(); + argc -= 5; argv += 5; + } + else if (!strcmp(flag, "resize")) + { + resize = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "maxsize")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((maxsize = argv[1].a_w.w_float) < 0)) + goto usage; + resize = 1; /* maxsize implies resize. */ + argc -= 2; argv += 2; + } + else goto usage; + } + if (argc < 2 || argc > MAXSFCHANS + 1 || argv[0].a_type != A_SYMBOL) + goto usage; + filename = argv[0].a_w.w_symbol->s_name; + argc--; argv++; + + for (i = 0; i < argc; i++) + { + if (argv[i].a_type != A_SYMBOL) + goto usage; + if (!(garrays[i] = + (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class))) + { + pd_error(x, "%s: no such table", argv[i].a_w.w_symbol->s_name); + goto done; + } + else if (!garray_getfloatarray(garrays[i], &vecsize[i], &vecs[i])) + error("%s: bad template for tabwrite", + argv[i].a_w.w_symbol->s_name); + if (finalsize && finalsize != vecsize[i] && !resize) + { + post("threadedsf_read: arrays have different lengths; resizing..."); + resize = 1; + } + finalsize = vecsize[i]; + } + fd = open_soundfile(canvas_getdir(x->x_canvas)->s_name, filename, + headersize, &bytespersamp, &bigendian, &channels, &bytelimit, + skipframes); + + if (fd < 0) + { + pd_error(x, "threadedsf_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) + { + pd_error(x, "lseek failed"); + goto done; + } + lseek(fd, poswas, SEEK_SET); + framesinfile = (eofis - poswas) / (channels * bytespersamp); + if (framesinfile > maxsize) + { + pd_error(x, "threadedsf_read: truncated to %d elements", maxsize); + framesinfile = maxsize; + } + if (framesinfile > bytelimit / (channels * bytespersamp)) + framesinfile = bytelimit / (channels * bytespersamp); + finalsize = framesinfile; + + } + if (!finalsize) finalsize = 0x7fffffff; + if (finalsize > bytelimit / (channels * bytespersamp)) + finalsize = bytelimit / (channels * bytespersamp); + fp = fdopen(fd, "rb"); + bufframes = SAMPBUFSIZE / (channels * bytespersamp); + +#ifdef SFDEBUG + post("buffers: %d", argc); + post("channels: %d", channels); +#endif + +#ifdef _POSIX_MEMLOCK + munlockall(); +#endif + + /* allocate memory for new array */ + if (resize) + for (i = 0; i < argc; i++) + { +#ifdef SFDEBUG + post("allocating buffer %d",i); +#endif + nvecs[i]=getbytes (finalsize * sizeof(t_float)); + /* if we are out of memory, free it again and quit */ + if (nvecs[i]==0) + { + pd_error(x, "resize failed"); + for (j=0; j!=i;++j) + /* if the resizing fails, we'll have to free all arrays again */ + freebytes (nvecs[i],finalsize * sizeof(t_float)); + goto done; + } + /* zero samples */ + if(i > channels) + { + memset(nvecs[i],0,vecsize[i] * sizeof(t_float)); + } + } + else + for (i = 0; i < argc; i++) + { + nvecs[i] = getbytes (vecsize[i] * sizeof(t_float)); + /* if we are out of memory, free it again and quit */ + if (nvecs[i]==0) + { + pd_error(x, "resize failed"); + for (j=0; j!=i;++j) + /* if the resizing fails, we'll have to free all arrays again */ + freebytes (nvecs[i],vecsize[i] * sizeof(t_float)); + goto done; + } + /* zero samples */ + if(i > channels) + memset(nvecs[i],0,vecsize[i] * sizeof(t_float)); + } + +#ifdef SFDEBUG + post("transfer soundfile"); +#endif + + for (itemsread = 0; itemsread < finalsize; ) + { + int thisread = finalsize - itemsread; + thisread = (thisread > bufframes ? bufframes : thisread); + nitems = fread(sampbuf, channels * bytespersamp, thisread, fp); + if (nitems <= 0) break; + soundfile_xferin(channels, argc, nvecs, itemsread, + (unsigned char *)sampbuf, nitems, bytespersamp, bigendian); + itemsread += nitems; + } + +#ifdef SFDEBUG + post("zeroing remaining elements"); +#endif + /* zero out remaining elements of vectors */ + for (i = 0; i < argc; i++) + { + for (j = itemsread; j < finalsize; j++) + nvecs[i][j] = 0; + } + + /* set idle callback to switch pointers */ +#ifdef SFDEBUG + post("locked"); +#endif + for (i = 0; i < argc; i++) + { + t_int* w = (t_int*)getbytes(4*sizeof(t_int)); + + w[0] = (t_int)(garrays[i]); + w[1] = (t_int)nvecs[i]; + w[2] = (t_int)finalsize; + w[3] = (t_int)(&resume_after_callback); + + h_set_callback(&threadedsf_read_update_garray, w, 4); + + pthread_cond_wait(&resume_after_callback, &resume_after_callback_mutex); + } + +#ifdef SFDEBUG + post("unlocked, doing graphics updates"); +#endif + + /* do all graphics updates + * run this in the main thread via callback */ + for (i = 0; i < argc; i++) + { + t_int* w = (t_int*)getbytes(2*sizeof(t_int)); + + w[0] = (t_int)(garrays[i]); + w[1] = (t_int)finalsize; + + h_set_callback(&threadedsf_read_update_graphics, w, 2); + } + + /* free the old arrays */ + for (i = 0; i < argc; i++) + freebytes(vecs[i], vecsize[i] * sizeof(t_float)); + + fclose(fp); + fd = -1; + goto done; + + usage: + pd_error(x, "usage: read [flags] filename tablename..."); + post("flags: -skip <n> -nframes <n> -resize -maxsize <n> ..."); + post("-raw <headerbytes> <channels> <bytespersamp> <endian (b, l, or n)>."); + + done: + if (fd >= 0) + close (fd); + +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + + outargs = (t_int*)getbytes(2*sizeof(t_int)); + outargs[0] = (t_int)x->x_obj.ob_outlet; + outargs[1] = (t_int)itemsread; + h_set_callback(&threadedsf_read_output, outargs, 2); +} + +/* idle callback for threadsafe synchronisation */ +static t_int threadedsf_read_update_garray(t_int * w) +{ + t_garray* garray = (t_garray*)w[0]; + t_array *data = garray_getarray(garray); + t_int nvec = w[1]; + t_int finalsize = w[2]; + pthread_cond_t * conditional = (pthread_cond_t*) w[3]; + +#ifdef SFDEBUG + post("lock array %p", garray); +#endif + + // no garray_locks in current pd + //garray_lock(garray); + + data->a_vec = (char *) nvec; + data->a_n = finalsize; + if (garray->x_usedindsp) canvas_update_dsp(); + +#ifdef SFDEBUG + post("unlock array %p", garray); +#endif + + // no garray_locks in current pd + //garray_unlock(garray); + + /* signal helper thread */ + pthread_cond_broadcast(conditional); + + return 0; +} + +static t_int threadedsf_read_update_graphics(t_int * w) +{ + t_garray* garray = (t_garray*) w[0]; + t_glist *gl; + int n = w[1]; + /* if this is the only array in the graph, + reset the graph's coordinates */ + +#ifdef SFDEBUG + post("redraw array %p", garray); +#endif + + gl = garray->x_glist; + if (gl->gl_list == &garray->x_gobj && !garray->x_gobj.g_next) + { + vmess(&gl->gl_pd, gensym("bounds"), "ffff", + 0., gl->gl_y1, (double)(n > 1 ? n-1 : 1), gl->gl_y2); + /* close any dialogs that might have the wrong info now... */ + gfxstub_deleteforkey(gl); + } + else + garray_redraw(garray); + + return 0; +} + + +static t_int threadedsf_read_output(t_int * w) +{ + t_outlet* outlet = (t_outlet*) w[0]; + float itemsread = (float) w[1]; + +#ifdef SFDEBUG + post("bang %p", outlet); +#endif + + outlet_float (outlet, itemsread); + + return 0; +} + + + + + /* this is broken out from threadedsf_write below so garray_write can + call it too... not done yet though. */ + +long threadedsf_t_dowrite(void *obj, t_canvas *canvas, + int argc, t_atom *argv) +{ + int bytespersamp, bigendian, + swap, filetype, normalize, i, j, 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, + &bytespersamp, &swap, &bigendian, &normalize, &onset, &nframes, + &samplerate)) + goto usage; + nchannels = argc; + if (nchannels < 1 || nchannels > MAXSFCHANS) + goto usage; + if (samplerate < 0) + samplerate = sys_getsr(); + for (i = 0; i < nchannels; i++) + { + int vecsize; + if (argv[i].a_type != A_SYMBOL) + goto usage; + if (!(garrays[i] = + (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class))) + { + pd_error(obj, "%s: no such table", argv[i].a_w.w_symbol->s_name); + goto fail; + } + else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i])) + error("%s: bad template for tabwrite", + argv[i].a_w.w_symbol->s_name); + if (nframes > vecsize - onset) + nframes = vecsize - onset; + + for (j = 0; j < vecsize; j++) + { + if (vecs[i][j] > biggest) + biggest = vecs[i][j]; + else if (-vecs[i][j] > biggest) + biggest = -vecs[i][j]; + } + } + if (nframes <= 0) + { + pd_error(obj, "threadedsf_write: no samples at onset %ld", onset); + goto fail; + } + + if ((fd = create_soundfile(canvas, filesym->s_name, filetype, + nframes, bytespersamp, bigendian, nchannels, + swap, samplerate)) < 0) + { + post("%s: %s\n", filesym->s_name, strerror(errno)); + goto fail; + } + if (!normalize) + { + if ((bytespersamp != 4) && (biggest > 1)) + { + post("%s: normalizing max amplitude %f to 1", filesym->s_name, biggest); + normalize = 1; + } + else post("%s: biggest amplitude = %f", filesym->s_name, biggest); + } + if (normalize) + normfactor = (biggest > 0 ? 32767./(32768. * biggest) : 1); + else normfactor = 1; + + bufframes = SAMPBUFSIZE / (nchannels * bytespersamp); + + for (itemswritten = 0; itemswritten < nframes; ) + { + int thiswrite = nframes - itemswritten, nbytes; + thiswrite = (thiswrite > bufframes ? bufframes : thiswrite); + soundfile_xferout(argc, vecs, (unsigned char *)sampbuf, thiswrite, + onset, bytespersamp, bigendian, normfactor); + nbytes = write(fd, sampbuf, nchannels * bytespersamp * thiswrite); + if (nbytes < nchannels * bytespersamp * thiswrite) + { + post("%s: %s", filesym->s_name, strerror(errno)); + if (nbytes > 0) + itemswritten += nbytes / (nchannels * bytespersamp); + break; + } + itemswritten += thiswrite; + onset += thiswrite; + } + if (fd >= 0) + { + soundfile_finishwrite(obj, filesym->s_name, fd, + filetype, nframes, itemswritten, nchannels * bytespersamp, swap); + close (fd); + } + return ((float)itemswritten); +usage: + pd_error(obj, "usage: write [flags] filename tablename..."); + post("flags: -skip <n> -nframes <n> -bytes <n> -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 threadedsf_t_write(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + long bozo = threadedsf_t_dowrite(x, x->x_canvas, + argc, argv); + sys_lock(); + outlet_float(x->x_obj.ob_outlet, (float)bozo); + sys_lock(); +} + +static void threadedsf_t_resize(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_resize_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_resize,(void *)x,argc, argv); +} + + + /* TB: threadedsf_t_resize ... + usage: resize table size + adapted from garray_resize + */ +static void threadedsf_t_resize(t_threadedsf *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_glist *gl; + int n; /* resize of n elements */ + char *nvec; /* new array */ + + t_garray * x = (t_garray *)pd_findbyclass(argv[0].a_w.w_symbol, garray_class); + t_array *data = garray_getarray(x); // TODO muss der im lock sein ? + + if (!(x)) + { + pd_error(y, "%s: no such table", argv[0].a_w.w_symbol->s_name); + goto usage; + } + + vec = (t_float*) data->a_vec; + was = data->a_n; + + if ((argv+1)->a_type == A_FLOAT) + { + n = (int) (argv+1)->a_w.w_float; + } + else + { + goto usage; + } + + if (n == was) + { + return; + } + + if (n < 1) n = 1; + elemsize = template_findbyname(data->a_templatesym)->t_n * sizeof(t_word); + +#ifdef _POSIX_MEMLOCK + munlockall(); +#endif + if (was > n) + { + nvec = (char*)copybytes(data->a_vec, was * elemsize); + } + else + { + nvec = getbytes(n * elemsize); + memcpy (nvec, data->a_vec, was * elemsize); + + /* LATER should check t_resizebytes result */ + memset(nvec + was*elemsize, + 0, (n - was) * elemsize); + } + if (!nvec) + { + pd_error(x, "array resize failed: out of memory"); +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + return; + } + + + /* TB: we'll have to be sure that no one is accessing the array */ + sys_lock(); + + // no garray_locks in current pd +//#ifdef GARRAY_THREAD_LOCK + //garray_lock(x); +//#endif + + data->a_vec = nvec; + data->a_n = n; + + // no garray_locks in current pd +//#ifdef GARRAY_THREAD_LOCK + //garray_unlock(x); +//#endif + + if (x->x_usedindsp) canvas_update_dsp(); + sys_unlock(); + + + /* if this is the only array in the graph, + reset the graph's coordinates */ + gl = x->x_glist; + if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next) + { + vmess(&gl->gl_pd, gensym("bounds"), "ffff", + 0., gl->gl_y1, (double)(n > 1 ? n-1 : 1), gl->gl_y2); + /* close any dialogs that might have the wrong info now... */ + gfxstub_deleteforkey(gl); + } + else garray_redraw(x); + + freebytes (vec, was * elemsize); +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + sys_lock(); + outlet_float(y->x_obj.ob_outlet, (float)atom_getintarg(1,argc,argv)); + sys_unlock(); + return; + + usage: + pd_error(x, "usage: resize tablename size"); +} + + +//static void threadedsf_t_const(t_threadedsf *x, t_symbol *s, +// int argc, t_atom *argv); +// +//static void threadedsf_t_const_addq(t_threadedsf *x, t_symbol *s, +// int argc, t_atom *argv) +//{ +// threadedsf_queue_add(threadedsf_t_const,(void *)x,argc, argv); +//} + + +/* TB: threadedsf_t_const ... + usage: const table value +*/ +//static void threadedsf_t_const(t_threadedsf *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_glist *gl; +// int val; /* value */ +// char *nvec; /* new array */ +// int i; +// +// t_garray * x = (t_garray *)pd_findbyclass(argv[0].a_w.w_symbol, garray_class); +// t_array *data = garray_getarray(x); // TODO muss der im lock sein ? +// +// if (!(x)) +// { +// pd_error(y, "%s: no such table", argv[0].a_w.w_symbol->s_name); +// goto usage; +// } +// +// +// +// vec = (t_float*) data->a_vec; +// size = data->a_n; +// +// if ((argv+1)->a_type == A_FLOAT) +// { +// val = (int) (argv+1)->a_w.w_float; +// } +// else +// { +// goto usage; +// } +// +//#ifdef SFDEBUG +// post("array size: %d; new value: %d",size,val); +//#endif +// +// elemsize = template_findbyname(data->a_templatesym)->t_n * sizeof(t_word); +// +// +// /* allocating memory */ +//#ifdef _POSIX_MEMLOCK +// munlockall(); +//#endif +// nvec = getbytes(size * elemsize); +// +// if (!nvec) +// { +// pd_error(x, "array resize failed: out of memory"); +//#ifdef _POSIX_MEMLOCK +// mlockall(MCL_FUTURE); +//#endif +// return; +// } +// +// /* setting array */ +// for (i=0; i!=size; ++i) +// { +// nvec[i]=val; +// } +// +// /* TB: we'll have to be sure that no one is accessing the array */ +// sys_lock(); +//#ifdef GARRAY_THREAD_LOCK +// garray_lock(x); +//#endif +// data->a_vec = nvec; +//#ifdef GARRAY_THREAD_LOCK +// garray_unlock(x); +//#endif +// if (x->x_usedindsp) canvas_update_dsp(); +// sys_unlock(); +// +// +// /* if this is the only array in the graph, +// reset the graph's coordinates */ +// gl = x->x_glist; +// if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next) +// { +// vmess(&gl->gl_pd, gensym("bounds"), "ffff", +// 0., gl->gl_y1, (double)(size > 1 ? size-1 : 1), gl->gl_y2); +// /* close any dialogs that might have the wrong info now... */ +// gfxstub_deleteforkey(gl); +// } +// else garray_redraw(x); +// +// freebytes (vec, size * elemsize); +//#ifdef _POSIX_MEMLOCK +// mlockall(MCL_FUTURE); +//#endif +// sys_lock(); +// outlet_float(y->x_obj.ob_outlet, size); +// sys_unlock(); +// return; +// +// usage: +// pd_error(x, "usage: const tablename value"); +//} + + +static t_threadedsf *threadedsf_new(void) +{ + t_threadedsf *x = (t_threadedsf *)pd_new(threadedsf_class); + x->x_canvas = canvas_getcurrent(); + outlet_new(&x->x_obj, &s_float); + return (x); +} + + +void threadedsf_setup(void) +{ + threadedsf_class = class_new(gensym("threadedsf"), (t_newmethod)threadedsf_new, + 0, sizeof(t_threadedsf), 0, 0); + + class_addmethod(threadedsf_class, (t_method)threadedsf_t_read_addq, + gensym("read"), A_GIMME, 0); + class_addmethod(threadedsf_class, (t_method)threadedsf_t_write_addq, + gensym("write"), A_GIMME, 0); + class_addmethod(threadedsf_class, (t_method)threadedsf_t_resize_addq, + gensym("resize"), A_GIMME, 0); +// class_addmethod(threadedsf_class, (t_method)threadedsf_t_const_addq, +// gensym("const"), A_GIMME, 0); +} |