forked from len0rd/rockbox
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30944 a1c6a512-1295-4272-9138-f99709370657
2466 lines
72 KiB
C
2466 lines
72 KiB
C
/* 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. */
|
|
|
|
#ifdef ROCKBOX
|
|
#include "plugin.h"
|
|
#include "../../pdbox.h"
|
|
#else /* ROCKBOX */
|
|
#ifdef UNIX
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
#include <pthread.h>
|
|
#ifdef MSW
|
|
#include <io.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#endif /* ROCKBOX */
|
|
|
|
#include "m_pd.h"
|
|
|
|
#define MAXSFCHANS 64
|
|
|
|
/***************** soundfile header structures ************************/
|
|
|
|
typedef unsigned short uint16;
|
|
typedef unsigned long uint32;
|
|
|
|
#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 unsigned word;
|
|
typedef unsigned long dword;
|
|
|
|
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;
|
|
#ifdef ROCKBOX
|
|
int fd, nchannels, bigendian, bytespersamp, swap, sysrtn;
|
|
#else
|
|
int fd, format, nchannels, bigendian, bytespersamp, swap, sysrtn;
|
|
#endif
|
|
long bytelimit = 0x7fffffff;
|
|
#ifndef ROCKBOX
|
|
errno = 0;
|
|
#endif
|
|
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 */
|
|
{
|
|
#ifndef ROCKBOX
|
|
uint32 param;
|
|
#endif
|
|
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... */
|
|
#ifndef ROCKBOX
|
|
errno = EIO;
|
|
#endif
|
|
return (-1);
|
|
}
|
|
|
|
static void soundfile_xferin(int sfchannels, int nvecs, t_sample **vecs,
|
|
long itemsread, unsigned char *buf, int nitems, int bytespersamp,
|
|
int bigendian)
|
|
{
|
|
int i, j;
|
|
unsigned char *sp, *sp2;
|
|
t_sample *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++)
|
|
#ifdef ROCKBOX_BIG_ENDIAN
|
|
{
|
|
short xx = (sp2[1] << 8) | sp2[0];
|
|
*fp = xx << (fix1-16);
|
|
}
|
|
#else
|
|
*fp = ((short*)sp2)[0]<<(fix1-16);
|
|
#endif
|
|
}
|
|
}
|
|
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;
|
|
t_datachunk *aiffdc = (t_datachunk *)headerbuf + sizeof(t_aiff);
|
|
static unsigned char AIFF_splrate[] = {0x40, 0x0e, 0xac, 0x44, 0, 0, 0, 0, 0, 0};
|
|
static unsigned char datachunk_ID[] = {'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, AIFF_splrate, sizeof(AIFF_splrate));
|
|
memcpy(aiffdc->dc_id, datachunk_ID, sizeof(datachunk_ID));
|
|
longtmp = swap4(datasize, swap);
|
|
memcpy(&aiffdc->dc_size, &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:
|
|
#ifdef ROCKBOX
|
|
post("%s: error", filename);
|
|
#else
|
|
post("%s: %s", filename, strerror(errno));
|
|
#endif
|
|
}
|
|
|
|
static void soundfile_xferout(int nchannels, t_sample **vecs,
|
|
unsigned char *buf, int nitems, long onset, int bytespersamp,
|
|
int bigendian, float normalfactor)
|
|
{
|
|
int i, j;
|
|
unsigned char *sp, *sp2;
|
|
t_sample *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++)
|
|
{
|
|
#ifdef ROCKBOX
|
|
union f2i f2i;
|
|
f2i.f = *fp * normalfactor;
|
|
xx = f2i.i;
|
|
#else /* ROCKBOX */
|
|
float f2 = *fp * normalfactor;
|
|
xx = *(long *)&f2;
|
|
#endif /* ROCKBOX */
|
|
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++)
|
|
{
|
|
#ifdef ROCKBOX
|
|
union f2i f2i;
|
|
f2i.f = *fp * normalfactor;
|
|
xx = f2i.i;
|
|
#else /* ROCKBOX */
|
|
float f2 = *fp * normalfactor;
|
|
xx = *(long *)&f2;
|
|
#endif /* ROCKBOX */
|
|
sp2[3] = (xx >> 24); sp2[2] = (xx >> 16);
|
|
sp2[1] = (xx >> 8); sp2[0] = xx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* ------- soundfiler - reads and writes soundfiles to/from "garrays" ---- */
|
|
#define DEFMAXSIZE 4000000 /* default maximum 16 MB per channel */
|
|
#define SAMPBUFSIZE 1024
|
|
|
|
|
|
static t_class *soundfiler_class;
|
|
|
|
typedef struct _soundfiler
|
|
{
|
|
t_object x_obj;
|
|
t_canvas *x_canvas;
|
|
} t_soundfiler;
|
|
|
|
static t_soundfiler *soundfiler_new(void)
|
|
{
|
|
t_soundfiler *x = (t_soundfiler *)pd_new(soundfiler_class);
|
|
x->x_canvas = canvas_getcurrent();
|
|
outlet_new(&x->x_obj, &s_float);
|
|
return (x);
|
|
}
|
|
|
|
/* soundfiler_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>
|
|
*/
|
|
|
|
static void soundfiler_read(t_soundfiler *x, t_symbol *s,
|
|
int argc, t_atom *argv)
|
|
{
|
|
#ifdef ROCKBOX
|
|
(void) s;
|
|
#endif
|
|
int headersize = -1, channels = 0, bytespersamp = 0, bigendian = 0,
|
|
resize = 0, i, j;
|
|
#ifdef ROCKBOX
|
|
long skipframes = 0, nframes = 0, finalsize = 0,
|
|
#else
|
|
long skipframes = 0, nframes = 0, finalsize = 0, itemsleft,
|
|
#endif
|
|
maxsize = DEFMAXSIZE, itemsread = 0, bytelimit = 0x7fffffff;
|
|
int fd = -1;
|
|
char endianness, *filename;
|
|
t_garray *garrays[MAXSFCHANS];
|
|
t_sample *vecs[MAXSFCHANS];
|
|
char sampbuf[SAMPBUFSIZE];
|
|
int bufframes, nitems;
|
|
#ifdef ROCKBOX
|
|
int fp;
|
|
#else
|
|
FILE *fp;
|
|
#endif
|
|
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++)
|
|
{
|
|
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(x, "%s: no such table", argv[i].a_w.w_symbol->s_name);
|
|
goto done;
|
|
}
|
|
else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i]))
|
|
error("%s: bad template for tabwrite",
|
|
argv[i].a_w.w_symbol->s_name);
|
|
if (finalsize && finalsize != vecsize && !resize)
|
|
{
|
|
post("soundfiler_read: arrays have different lengths; resizing...");
|
|
resize = 1;
|
|
}
|
|
finalsize = vecsize;
|
|
}
|
|
fd = open_soundfile(canvas_getdir(x->x_canvas)->s_name, filename,
|
|
headersize, &bytespersamp, &bigendian, &channels, &bytelimit,
|
|
skipframes);
|
|
|
|
if (fd < 0)
|
|
{
|
|
#ifdef ROCKBOX
|
|
pd_error(x, "soundfiler_read: %s: %s",
|
|
filename,
|
|
"unknown or bad header format");
|
|
#else
|
|
pd_error(x, "soundfiler_read: %s: %s", filename, (errno == EIO ?
|
|
"unknown or bad header format" : strerror(errno)));
|
|
#endif
|
|
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, "soundfiler_read: truncated to %d elements", maxsize);
|
|
framesinfile = maxsize;
|
|
}
|
|
if (framesinfile > bytelimit / (channels * bytespersamp))
|
|
framesinfile = bytelimit / (channels * bytespersamp);
|
|
finalsize = framesinfile;
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
int vecsize;
|
|
|
|
garray_resize(garrays[i], finalsize);
|
|
/* for sanity's sake let's clear the save-in-patch flag here */
|
|
garray_setsaveit(garrays[i], 0);
|
|
garray_getfloatarray(garrays[i], &vecsize, &vecs[i]);
|
|
/* if the resize failed, garray_resize reported the error */
|
|
if (vecsize != framesinfile)
|
|
{
|
|
pd_error(x, "resize failed");
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
if (!finalsize) finalsize = 0x7fffffff;
|
|
if (finalsize > bytelimit / (channels * bytespersamp))
|
|
finalsize = bytelimit / (channels * bytespersamp);
|
|
#ifdef ROCKBOX
|
|
fp = fd;
|
|
#else
|
|
fp = fdopen(fd, "rb");
|
|
#endif
|
|
bufframes = SAMPBUFSIZE / (channels * bytespersamp);
|
|
|
|
for (itemsread = 0; itemsread < finalsize; )
|
|
{
|
|
int thisread = finalsize - itemsread;
|
|
thisread = (thisread > bufframes ? bufframes : thisread);
|
|
#ifdef ROCKBOX
|
|
nitems = read(fp, sampbuf, thisread * bytespersamp * channels) / (bytespersamp * channels);
|
|
#else
|
|
nitems = fread(sampbuf, channels * bytespersamp, thisread, fp);
|
|
#endif
|
|
if (nitems <= 0) break;
|
|
soundfile_xferin(channels, argc, vecs, itemsread,
|
|
(unsigned char *)sampbuf, nitems, bytespersamp, bigendian);
|
|
itemsread += nitems;
|
|
}
|
|
/* zero out remaining elements of vectors */
|
|
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
#ifdef ROCKBOX
|
|
int vecsize;
|
|
#else
|
|
int nzero, vecsize;
|
|
#endif
|
|
garray_getfloatarray(garrays[i], &vecsize, &vecs[i]);
|
|
for (j = itemsread; j < vecsize; j++)
|
|
vecs[i][j] = 0;
|
|
}
|
|
/* zero out vectors in excess of number of channels */
|
|
for (i = channels; i < argc; i++)
|
|
{
|
|
int vecsize;
|
|
t_sample *foo;
|
|
garray_getfloatarray(garrays[i], &vecsize, &foo);
|
|
for (j = 0; j < vecsize; j++)
|
|
foo[j] = 0;
|
|
}
|
|
/* do all graphics updates */
|
|
for (i = 0; i < argc; i++)
|
|
garray_redraw(garrays[i]);
|
|
#ifdef ROCKBOX
|
|
close(fp);
|
|
#else
|
|
fclose(fp);
|
|
#endif
|
|
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);
|
|
outlet_float(x->x_obj.ob_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)
|
|
{
|
|
#ifdef ROCKBOX
|
|
int bytespersamp, bigendian,
|
|
swap, filetype, normalize, i, j, nchannels;
|
|
long onset, nframes,
|
|
itemswritten = 0;
|
|
#else
|
|
int headersize, bytespersamp, bigendian,
|
|
endianness, swap, filetype, normalize, i, j, nchannels;
|
|
long onset, nframes, itemsleft,
|
|
maxsize = DEFMAXSIZE, itemswritten = 0;
|
|
#endif
|
|
t_garray *garrays[MAXSFCHANS];
|
|
t_sample *vecs[MAXSFCHANS];
|
|
char sampbuf[SAMPBUFSIZE];
|
|
#ifdef ROCKBOX
|
|
int bufframes;
|
|
#else
|
|
int bufframes, nitems;
|
|
#endif
|
|
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, "soundfiler_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)
|
|
{
|
|
#ifdef ROCKBOX
|
|
post("%s: %s\n", filesym->s_name, "error");
|
|
#else
|
|
post("%s: %s\n", filesym->s_name, strerror(errno));
|
|
#endif
|
|
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; )
|
|
{
|
|
#ifdef ROCKBOX
|
|
int thiswrite = nframes - itemswritten, nbytes;
|
|
#else
|
|
int thiswrite = nframes - itemswritten, nitems, nbytes;
|
|
#endif
|
|
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)
|
|
{
|
|
#ifdef ROCKBOX
|
|
post("%s: %s", filesym->s_name, "error");
|
|
#else
|
|
post("%s: %s", filesym->s_name, strerror(errno));
|
|
#endif
|
|
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 soundfiler_write(t_soundfiler *x, t_symbol *s,
|
|
int argc, t_atom *argv)
|
|
{
|
|
#ifdef ROCKBOX
|
|
(void) s;
|
|
#endif
|
|
long bozo = soundfiler_dowrite(x, x->x_canvas,
|
|
argc, argv);
|
|
outlet_float(x->x_obj.ob_outlet, (float)bozo);
|
|
}
|
|
|
|
static void soundfiler_setup(void)
|
|
{
|
|
soundfiler_class = class_new(gensym("soundfiler"), (t_newmethod)soundfiler_new,
|
|
0, sizeof(t_soundfiler), 0, 0);
|
|
class_addmethod(soundfiler_class, (t_method)soundfiler_read, gensym("read"),
|
|
A_GIMME, 0);
|
|
class_addmethod(soundfiler_class, (t_method)soundfiler_write,
|
|
gensym("write"), A_GIMME, 0);
|
|
}
|
|
|
|
|
|
#ifndef FIXEDPOINT
|
|
/************************* 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;
|
|
|
|
typedef struct _readsf
|
|
{
|
|
t_object x_obj;
|
|
t_canvas *x_canvas;
|
|
t_clock *x_clock;
|
|
char *x_buf; /* soundfile buffer */
|
|
int x_bufsize; /* buffer size in bytes */
|
|
int x_noutlets; /* number of audio outlets */
|
|
t_sample *(x_outvec[MAXSFCHANS]); /* audio vectors */
|
|
int x_vecsize; /* vector size for transfers */
|
|
t_outlet *x_bangout; /* bang-on-done outlet */
|
|
int x_state; /* opened, running, or idle */
|
|
float x_insamplerate; /* sample rate of input signal if known */
|
|
/* parameters to communicate with subthread */
|
|
int x_requestcode; /* pending request from parent to I/O thread */
|
|
char *x_filename; /* file to open (string is permanently allocated) */
|
|
int x_fileerror; /* slot for "errno" return */
|
|
int x_skipheaderbytes; /* size of header we'll skip */
|
|
int x_bytespersample; /* bytes per sample (2 or 3) */
|
|
int x_bigendian; /* true if file is big-endian */
|
|
int x_sfchannels; /* number of channels in soundfile */
|
|
float x_samplerate; /* sample rate of soundfile */
|
|
long x_onsetframes; /* number of sample frames to skip */
|
|
long x_bytelimit; /* max number of data bytes to read */
|
|
int x_fd; /* filedesc */
|
|
int x_fifosize; /* buffer size appropriately rounded down */
|
|
int x_fifohead; /* index of next byte to get from file */
|
|
int x_fifotail; /* index of next byte the ugen will read */
|
|
int x_eof; /* true if fifohead has stopped changing */
|
|
int x_sigcountdown; /* counter for signalling child for more data */
|
|
int x_sigperiod; /* number of ticks per signal */
|
|
int x_filetype; /* writesf~ only; type of file to create */
|
|
int x_itemswritten; /* writesf~ only; items writen */
|
|
int x_swap; /* writesf~ only; true if byte swapping */
|
|
float x_f; /* writesf~ only; scalar for signal inlet */
|
|
pthread_mutex_t x_mutex;
|
|
pthread_cond_t x_requestcondition;
|
|
pthread_cond_t x_answercondition;
|
|
pthread_t x_childthread;
|
|
} t_readsf;
|
|
|
|
|
|
/************** the child thread which performs file I/O ***********/
|
|
|
|
#if 0
|
|
static void pute(char *s) /* debug routine */
|
|
{
|
|
write(2, s, strlen(s));
|
|
}
|
|
#define DEBUG_SOUNDFILE
|
|
#endif
|
|
|
|
#if 1
|
|
#define sfread_cond_wait pthread_cond_wait
|
|
#define sfread_cond_signal pthread_cond_signal
|
|
#else
|
|
#include <sys/time.h> /* debugging version... */
|
|
#include <sys/types.h>
|
|
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 = zz;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("1\n");
|
|
#endif
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
while (1)
|
|
{
|
|
int fd, fifohead;
|
|
char *buf;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("0\n");
|
|
#endif
|
|
if (x->x_requestcode == REQUEST_NOTHING)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait 2\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
sfread_cond_wait(&x->x_requestcondition, &x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("3\n");
|
|
#endif
|
|
}
|
|
else if (x->x_requestcode == REQUEST_OPEN)
|
|
{
|
|
char boo[80];
|
|
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->x_onsetframes;
|
|
long bytelimit = 0x7fffffff;
|
|
int skipheaderbytes = x->x_skipheaderbytes;
|
|
int bytespersample = x->x_bytespersample;
|
|
int sfchannels = x->x_sfchannels;
|
|
int bigendian = x->x_bigendian;
|
|
char *filename = x->x_filename;
|
|
char *dirname = canvas_getdir(x->x_canvas)->s_name;
|
|
/* alter the request code so that an ensuing "open" will get
|
|
noticed. */
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("4\n");
|
|
#endif
|
|
x->x_requestcode = REQUEST_BUSY;
|
|
x->x_fileerror = 0;
|
|
|
|
/* if there's already a file open, close it */
|
|
if (x->x_fd >= 0)
|
|
{
|
|
fd = x->x_fd;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
close (fd);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
if (x->x_requestcode != REQUEST_BUSY)
|
|
goto lost;
|
|
}
|
|
/* open the soundfile with the mutex unlocked */
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
fd = open_soundfile(dirname, filename,
|
|
skipheaderbytes, &bytespersample, &bigendian,
|
|
&sfchannels, &bytelimit, onsetframes);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("5\n");
|
|
#endif
|
|
/* copy back into the instance structure. */
|
|
x->x_bytespersample = bytespersample;
|
|
x->x_sfchannels = sfchannels;
|
|
x->x_bigendian = bigendian;
|
|
x->x_fd = fd;
|
|
x->x_bytelimit = bytelimit;
|
|
if (fd < 0)
|
|
{
|
|
x->x_fileerror = errno;
|
|
x->x_eof = 1;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("open failed\n");
|
|
pute(filename);
|
|
pute(dirname);
|
|
#endif
|
|
goto lost;
|
|
}
|
|
/* check if another request has been made; if so, field it */
|
|
if (x->x_requestcode != REQUEST_BUSY)
|
|
goto lost;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("6\n");
|
|
#endif
|
|
x->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->x_fifosize = x->x_bufsize - (x->x_bufsize %
|
|
(x->x_bytespersample * x->x_sfchannels * MAXVECSIZE));
|
|
/* arrange for the "request" condition to be signalled 16
|
|
times per buffer */
|
|
#ifdef DEBUG_SOUNDFILE
|
|
sprintf(boo, "fifosize %d\n",
|
|
x->x_fifosize);
|
|
pute(boo);
|
|
#endif
|
|
x->x_sigcountdown = x->x_sigperiod =
|
|
(x->x_fifosize /
|
|
(16 * x->x_bytespersample * x->x_sfchannels *
|
|
x->x_vecsize));
|
|
/* in a loop, wait for the fifo to get hungry and feed it */
|
|
|
|
while (x->x_requestcode == REQUEST_BUSY)
|
|
{
|
|
int fifosize = x->x_fifosize;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("77\n");
|
|
#endif
|
|
if (x->x_eof)
|
|
break;
|
|
if (x->x_fifohead >= x->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->x_fifotail || (fifosize - x->x_fifohead > READSIZE))
|
|
{
|
|
wantbytes = fifosize - x->x_fifohead;
|
|
if (wantbytes > READSIZE)
|
|
wantbytes = READSIZE;
|
|
if (wantbytes > x->x_bytelimit)
|
|
wantbytes = x->x_bytelimit;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
sprintf(boo, "head %d, tail %d, size %d\n",
|
|
x->x_fifohead, x->x_fifotail, wantbytes);
|
|
pute(boo);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait 7a ...\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("signalled\n");
|
|
#endif
|
|
sfread_cond_wait(&x->x_requestcondition,
|
|
&x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("7a done\n");
|
|
#endif
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* otherwise check if there are at least READSIZE
|
|
bytes to read. If not, wait and loop back. */
|
|
wantbytes = x->x_fifotail - x->x_fifohead - 1;
|
|
if (wantbytes < READSIZE)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait 7...\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
sfread_cond_wait(&x->x_requestcondition,
|
|
&x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("7 done\n");
|
|
#endif
|
|
continue;
|
|
}
|
|
else wantbytes = READSIZE;
|
|
if (wantbytes > x->x_bytelimit)
|
|
wantbytes = x->x_bytelimit;
|
|
}
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("8\n");
|
|
#endif
|
|
fd = x->x_fd;
|
|
buf = x->x_buf;
|
|
fifohead = x->x_fifohead;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
sysrtn = read(fd, buf + fifohead, wantbytes);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
if (x->x_requestcode != REQUEST_BUSY)
|
|
break;
|
|
if (sysrtn < 0)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("fileerror\n");
|
|
#endif
|
|
x->x_fileerror = errno;
|
|
break;
|
|
}
|
|
else if (sysrtn == 0)
|
|
{
|
|
x->x_eof = 1;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
x->x_fifohead += sysrtn;
|
|
x->x_bytelimit -= sysrtn;
|
|
if (x->x_bytelimit <= 0)
|
|
{
|
|
x->x_eof = 1;
|
|
break;
|
|
}
|
|
if (x->x_fifohead == fifosize)
|
|
x->x_fifohead = 0;
|
|
}
|
|
#ifdef DEBUG_SOUNDFILE
|
|
sprintf(boo, "after: head %d, tail %d\n",
|
|
x->x_fifohead, x->x_fifotail);
|
|
pute(boo);
|
|
#endif
|
|
/* signal parent in case it's waiting for data */
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
}
|
|
lost:
|
|
|
|
if (x->x_requestcode == REQUEST_BUSY)
|
|
x->x_requestcode = REQUEST_NOTHING;
|
|
/* fell out of read loop: close file if necessary,
|
|
set EOF and signal once more */
|
|
if (x->x_fd >= 0)
|
|
{
|
|
fd = x->x_fd;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
close (fd);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
}
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
|
|
}
|
|
else if (x->x_requestcode == REQUEST_CLOSE)
|
|
{
|
|
if (x->x_fd >= 0)
|
|
{
|
|
fd = x->x_fd;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
close (fd);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
}
|
|
if (x->x_requestcode == REQUEST_CLOSE)
|
|
x->x_requestcode = REQUEST_NOTHING;
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
}
|
|
else if (x->x_requestcode == REQUEST_QUIT)
|
|
{
|
|
if (x->x_fd >= 0)
|
|
{
|
|
fd = x->x_fd;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
close (fd);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
}
|
|
x->x_requestcode = REQUEST_NOTHING;
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("13\n");
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("thread exit\n");
|
|
#endif
|
|
pthread_mutex_unlock(&x->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)
|
|
{
|
|
t_readsf *x;
|
|
int nchannels = fnchannels, bufsize = fbufsize, i;
|
|
char *buf;
|
|
|
|
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;
|
|
buf = getbytes(bufsize);
|
|
if (!buf) return (0);
|
|
|
|
x = (t_readsf *)pd_new(readsf_class);
|
|
|
|
for (i = 0; i < nchannels; i++)
|
|
outlet_new(&x->x_obj, gensym("signal"));
|
|
x->x_noutlets = nchannels;
|
|
x->x_bangout = outlet_new(&x->x_obj, &s_bang);
|
|
pthread_mutex_init(&x->x_mutex, 0);
|
|
pthread_cond_init(&x->x_requestcondition, 0);
|
|
pthread_cond_init(&x->x_answercondition, 0);
|
|
x->x_vecsize = MAXVECSIZE;
|
|
x->x_state = STATE_IDLE;
|
|
x->x_clock = clock_new(x, (t_method)readsf_tick);
|
|
x->x_canvas = canvas_getcurrent();
|
|
x->x_bytespersample = 2;
|
|
x->x_sfchannels = 1;
|
|
x->x_fd = -1;
|
|
x->x_buf = buf;
|
|
x->x_bufsize = bufsize;
|
|
x->x_fifosize = x->x_fifohead = x->x_fifotail = x->x_requestcode = 0;
|
|
pthread_create(&x->x_childthread, 0, readsf_child_main, x);
|
|
return (x);
|
|
}
|
|
|
|
static void readsf_tick(t_readsf *x)
|
|
{
|
|
outlet_bang(x->x_bangout);
|
|
}
|
|
|
|
static t_int *readsf_perform(t_int *w)
|
|
{
|
|
t_readsf *x = (t_readsf *)(w[1]);
|
|
int vecsize = x->x_vecsize, noutlets = x->x_noutlets, i, j,
|
|
bytespersample = x->x_bytespersample,
|
|
bigendian = x->x_bigendian;
|
|
float *fp;
|
|
if (x->x_state == STATE_STREAM)
|
|
{
|
|
int wantbytes, nchannels, sfchannels = x->x_sfchannels;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
wantbytes = sfchannels * vecsize * bytespersample;
|
|
while (
|
|
!x->x_eof && x->x_fifohead >= x->x_fifotail &&
|
|
x->x_fifohead < x->x_fifotail + wantbytes-1)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait...\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
sfread_cond_wait(&x->x_answercondition, &x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("done\n");
|
|
#endif
|
|
}
|
|
if (x->x_eof && x->x_fifohead >= x->x_fifotail &&
|
|
x->x_fifohead < x->x_fifotail + wantbytes-1)
|
|
{
|
|
int xfersize;
|
|
if (x->x_fileerror)
|
|
{
|
|
pd_error(x, "dsp: %s: %s", x->x_filename,
|
|
(x->x_fileerror == EIO ?
|
|
"unknown or bad header format" :
|
|
strerror(x->x_fileerror)));
|
|
}
|
|
clock_delay(x->x_clock, 0);
|
|
x->x_state = STATE_IDLE;
|
|
|
|
/* if there's a partial buffer left, copy it out. */
|
|
xfersize = (x->x_fifohead - x->x_fifotail + 1) /
|
|
(sfchannels * bytespersample);
|
|
if (xfersize)
|
|
{
|
|
soundfile_xferin(sfchannels, noutlets, x->x_outvec, 0,
|
|
(unsigned char *)(x->x_buf + x->x_fifotail), xfersize,
|
|
bytespersample, bigendian);
|
|
vecsize -= xfersize;
|
|
}
|
|
/* then zero out the (rest of the) output */
|
|
for (i = 0; i < noutlets; i++)
|
|
for (j = vecsize, fp = x->x_outvec[i] + xfersize; j--; )
|
|
*fp++ = 0;
|
|
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
return (w+2);
|
|
}
|
|
|
|
soundfile_xferin(sfchannels, noutlets, x->x_outvec, 0,
|
|
(unsigned char *)(x->x_buf + x->x_fifotail), vecsize,
|
|
bytespersample, bigendian);
|
|
|
|
x->x_fifotail += wantbytes;
|
|
if (x->x_fifotail >= x->x_fifosize)
|
|
x->x_fifotail = 0;
|
|
if ((--x->x_sigcountdown) <= 0)
|
|
{
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
x->x_sigcountdown = x->x_sigperiod;
|
|
}
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
}
|
|
else
|
|
{
|
|
idle:
|
|
for (i = 0; i < noutlets; i++)
|
|
for (j = vecsize, fp = x->x_outvec[i]; 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->x_state == STATE_STARTUP)
|
|
x->x_state = STATE_STREAM;
|
|
else pd_error(x, "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->x_mutex);
|
|
x->x_state = STATE_IDLE;
|
|
x->x_requestcode = REQUEST_CLOSE;
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
pthread_mutex_unlock(&x->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 bytespersamp 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 bytespersamp = atom_getfloatarg(4, argc, argv);
|
|
t_symbol *endian = atom_getsymbolarg(5, argc, argv);
|
|
if (!*filesym->s_name)
|
|
return;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_requestcode = REQUEST_OPEN;
|
|
x->x_filename = filesym->s_name;
|
|
x->x_fifotail = 0;
|
|
x->x_fifohead = 0;
|
|
if (*endian->s_name == 'b')
|
|
x->x_bigendian = 1;
|
|
else if (*endian->s_name == 'l')
|
|
x->x_bigendian = 0;
|
|
else if (*endian->s_name)
|
|
pd_error(x, "endianness neither 'b' nor 'l'");
|
|
else x->x_bigendian = garray_ambigendian();
|
|
x->x_onsetframes = (onsetframes > 0 ? onsetframes : 0);
|
|
x->x_skipheaderbytes = (headerbytes > 0 ? headerbytes :
|
|
(headerbytes == 0 ? -1 : 0));
|
|
x->x_sfchannels = (channels >= 1 ? channels : 1);
|
|
x->x_bytespersample = (bytespersamp > 2 ? bytespersamp : 2);
|
|
x->x_eof = 0;
|
|
x->x_fileerror = 0;
|
|
x->x_state = STATE_STARTUP;
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
}
|
|
|
|
static void readsf_dsp(t_readsf *x, t_signal **sp)
|
|
{
|
|
int i, noutlets = x->x_noutlets;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_vecsize = sp[0]->s_n;
|
|
|
|
x->x_sigperiod = (x->x_fifosize /
|
|
(x->x_bytespersample * x->x_sfchannels * x->x_vecsize));
|
|
for (i = 0; i < noutlets; i++)
|
|
x->x_outvec[i] = sp[i]->s_vec;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
dsp_add(readsf_perform, 1, x);
|
|
}
|
|
|
|
static void readsf_print(t_readsf *x)
|
|
{
|
|
post("state %d", x->x_state);
|
|
post("fifo head %d", x->x_fifohead);
|
|
post("fifo tail %d", x->x_fifotail);
|
|
post("fifo size %d", x->x_fifosize);
|
|
post("fd %d", x->x_fd);
|
|
post("eof %d", x->x_eof);
|
|
}
|
|
|
|
static void readsf_free(t_readsf *x)
|
|
{
|
|
/* request QUIT and wait for acknowledge */
|
|
void *threadrtn;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_requestcode = REQUEST_QUIT;
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
while (x->x_requestcode != REQUEST_NOTHING)
|
|
{
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
sfread_cond_wait(&x->x_answercondition, &x->x_mutex);
|
|
}
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
if (pthread_join(x->x_childthread, &threadrtn))
|
|
error("readsf_free: join failed");
|
|
|
|
pthread_cond_destroy(&x->x_requestcondition);
|
|
pthread_cond_destroy(&x->x_answercondition);
|
|
pthread_mutex_destroy(&x->x_mutex);
|
|
freebytes(x->x_buf, x->x_bufsize);
|
|
clock_free(x->x_clock);
|
|
}
|
|
|
|
static void readsf_setup(void)
|
|
{
|
|
readsf_class = class_new(gensym("readsf~"), (t_newmethod)readsf_new,
|
|
(t_method)readsf_free, sizeof(t_readsf), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
|
|
class_addfloat(readsf_class, (t_method)readsf_float);
|
|
class_addmethod(readsf_class, (t_method)readsf_start, gensym("start"), 0);
|
|
class_addmethod(readsf_class, (t_method)readsf_stop, gensym("stop"), 0);
|
|
class_addmethod(readsf_class, (t_method)readsf_dsp, gensym("dsp"), 0);
|
|
class_addmethod(readsf_class, (t_method)readsf_open, gensym("open"),
|
|
A_GIMME, 0);
|
|
class_addmethod(readsf_class, (t_method)readsf_print, gensym("print"), 0);
|
|
}
|
|
|
|
/******************************* writesf *******************/
|
|
|
|
static t_class *writesf_class;
|
|
|
|
#define t_writesf t_readsf /* just re-use the structure */
|
|
|
|
/************** the child thread which performs file I/O ***********/
|
|
|
|
static void *writesf_child_main(void *zz)
|
|
{
|
|
t_writesf *x = zz;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("1\n");
|
|
#endif
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
while (1)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("0\n");
|
|
#endif
|
|
if (x->x_requestcode == REQUEST_NOTHING)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait 2\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
sfread_cond_wait(&x->x_requestcondition, &x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("3\n");
|
|
#endif
|
|
}
|
|
else if (x->x_requestcode == REQUEST_OPEN)
|
|
{
|
|
char boo[80];
|
|
int fd, sysrtn, writebytes;
|
|
|
|
/* copy file stuff out of the data structure so we can
|
|
relinquish the mutex while we're in open_soundfile(). */
|
|
long onsetframes = x->x_onsetframes;
|
|
long bytelimit = 0x7fffffff;
|
|
int skipheaderbytes = x->x_skipheaderbytes;
|
|
int bytespersample = x->x_bytespersample;
|
|
int sfchannels = x->x_sfchannels;
|
|
int bigendian = x->x_bigendian;
|
|
int filetype = x->x_filetype;
|
|
char *filename = x->x_filename;
|
|
t_canvas *canvas = x->x_canvas;
|
|
float samplerate = x->x_samplerate;
|
|
|
|
/* alter the request code so that an ensuing "open" will get
|
|
noticed. */
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("4\n");
|
|
#endif
|
|
x->x_requestcode = REQUEST_BUSY;
|
|
x->x_fileerror = 0;
|
|
|
|
/* if there's already a file open, close it */
|
|
if (x->x_fd >= 0)
|
|
{
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
close (x->x_fd);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
if (x->x_requestcode != REQUEST_BUSY)
|
|
continue;
|
|
}
|
|
/* open the soundfile with the mutex unlocked */
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
fd = create_soundfile(canvas, filename, filetype, 0,
|
|
bytespersample, bigendian, sfchannels,
|
|
garray_ambigendian() != bigendian, samplerate);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("5\n");
|
|
#endif
|
|
|
|
if (fd < 0)
|
|
{
|
|
x->x_fd = -1;
|
|
x->x_eof = 1;
|
|
x->x_fileerror = errno;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("open failed\n");
|
|
pute(filename);
|
|
#endif
|
|
x->x_requestcode = REQUEST_NOTHING;
|
|
continue;
|
|
}
|
|
/* check if another request has been made; if so, field it */
|
|
if (x->x_requestcode != REQUEST_BUSY)
|
|
continue;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("6\n");
|
|
#endif
|
|
x->x_fd = fd;
|
|
x->x_fifotail = 0;
|
|
x->x_itemswritten = 0;
|
|
x->x_swap = garray_ambigendian() != bigendian;
|
|
/* in a loop, wait for the fifo to have data and write it
|
|
to disk */
|
|
while (x->x_requestcode == REQUEST_BUSY ||
|
|
(x->x_requestcode == REQUEST_CLOSE &&
|
|
x->x_fifohead != x->x_fifotail))
|
|
{
|
|
int fifosize = x->x_fifosize, fifotail;
|
|
char *buf = x->x_buf;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("77\n");
|
|
#endif
|
|
|
|
/* 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->x_fifohead < x->x_fifotail ||
|
|
x->x_fifohead >= x->x_fifotail + WRITESIZE
|
|
|| (x->x_requestcode == REQUEST_CLOSE &&
|
|
x->x_fifohead != x->x_fifotail))
|
|
{
|
|
writebytes = (x->x_fifohead < x->x_fifotail ?
|
|
fifosize : x->x_fifohead) - x->x_fifotail;
|
|
if (writebytes > READSIZE)
|
|
writebytes = READSIZE;
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait 7a ...\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("signalled\n");
|
|
#endif
|
|
sfread_cond_wait(&x->x_requestcondition,
|
|
&x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("7a done\n");
|
|
#endif
|
|
continue;
|
|
}
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("8\n");
|
|
#endif
|
|
fifotail = x->x_fifotail;
|
|
fd = x->x_fd;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
sysrtn = write(fd, buf + fifotail, writebytes);
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
if (x->x_requestcode != REQUEST_BUSY &&
|
|
x->x_requestcode != REQUEST_CLOSE)
|
|
break;
|
|
if (sysrtn < writebytes)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("fileerror\n");
|
|
#endif
|
|
x->x_fileerror = errno;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
x->x_fifotail += sysrtn;
|
|
if (x->x_fifotail == fifosize)
|
|
x->x_fifotail = 0;
|
|
}
|
|
x->x_itemswritten +=
|
|
sysrtn / (x->x_bytespersample * x->x_sfchannels);
|
|
sprintf(boo, "after: head %d, tail %d\n",
|
|
x->x_fifohead, x->x_fifotail);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute(boo);
|
|
#endif
|
|
/* signal parent in case it's waiting for data */
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
}
|
|
}
|
|
else if (x->x_requestcode == REQUEST_CLOSE ||
|
|
x->x_requestcode == REQUEST_QUIT)
|
|
{
|
|
int quit = (x->x_requestcode == REQUEST_QUIT);
|
|
if (x->x_fd >= 0)
|
|
{
|
|
int bytesperframe = x->x_bytespersample * x->x_sfchannels;
|
|
int bigendian = x->x_bigendian;
|
|
char *filename = x->x_filename;
|
|
int fd = x->x_fd;
|
|
int filetype = x->x_filetype;
|
|
int itemswritten = x->x_itemswritten;
|
|
int swap = x->x_swap;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
|
|
soundfile_finishwrite(x, filename, fd,
|
|
filetype, 0x7fffffff, itemswritten,
|
|
bytesperframe, swap);
|
|
close (fd);
|
|
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_fd = -1;
|
|
}
|
|
x->x_requestcode = REQUEST_NOTHING;
|
|
sfread_cond_signal(&x->x_answercondition);
|
|
if (quit)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("13\n");
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("thread exit\n");
|
|
#endif
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
return (0);
|
|
}
|
|
|
|
/******** the object proper runs in the calling (parent) thread ****/
|
|
|
|
static void writesf_tick(t_writesf *x);
|
|
|
|
static void *writesf_new(t_floatarg fnchannels, t_floatarg fbufsize)
|
|
{
|
|
t_writesf *x;
|
|
int nchannels = fnchannels, bufsize = fbufsize, i;
|
|
char *buf;
|
|
|
|
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;
|
|
buf = getbytes(bufsize);
|
|
if (!buf) return (0);
|
|
|
|
x = (t_writesf *)pd_new(writesf_class);
|
|
|
|
for (i = 1; i < nchannels; i++)
|
|
inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
|
|
|
|
x->x_f = 0;
|
|
x->x_sfchannels = nchannels;
|
|
pthread_mutex_init(&x->x_mutex, 0);
|
|
pthread_cond_init(&x->x_requestcondition, 0);
|
|
pthread_cond_init(&x->x_answercondition, 0);
|
|
x->x_vecsize = MAXVECSIZE;
|
|
x->x_insamplerate = x->x_samplerate = 0;
|
|
x->x_state = STATE_IDLE;
|
|
x->x_clock = 0; /* no callback needed here */
|
|
x->x_canvas = canvas_getcurrent();
|
|
x->x_bytespersample = 2;
|
|
x->x_fd = -1;
|
|
x->x_buf = buf;
|
|
x->x_bufsize = bufsize;
|
|
x->x_fifosize = x->x_fifohead = x->x_fifotail = x->x_requestcode = 0;
|
|
pthread_create(&x->x_childthread, 0, writesf_child_main, x);
|
|
return (x);
|
|
}
|
|
|
|
static t_int *writesf_perform(t_int *w)
|
|
{
|
|
t_writesf *x = (t_writesf *)(w[1]);
|
|
int vecsize = x->x_vecsize, sfchannels = x->x_sfchannels, i, j,
|
|
bytespersample = x->x_bytespersample,
|
|
bigendian = x->x_bigendian;
|
|
float *fp;
|
|
if (x->x_state == STATE_STREAM)
|
|
{
|
|
int wantbytes;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
wantbytes = sfchannels * vecsize * bytespersample;
|
|
while (x->x_fifotail > x->x_fifohead &&
|
|
x->x_fifotail < x->x_fifohead + wantbytes + 1)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("wait...\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
sfread_cond_wait(&x->x_answercondition, &x->x_mutex);
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("done\n");
|
|
#endif
|
|
}
|
|
|
|
soundfile_xferout(sfchannels, x->x_outvec,
|
|
(unsigned char *)(x->x_buf + x->x_fifohead), vecsize, 0,
|
|
bytespersample, bigendian, 1.);
|
|
|
|
x->x_fifohead += wantbytes;
|
|
if (x->x_fifohead >= x->x_fifosize)
|
|
x->x_fifohead = 0;
|
|
if ((--x->x_sigcountdown) <= 0)
|
|
{
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("signal 1\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
x->x_sigcountdown = x->x_sigperiod;
|
|
}
|
|
pthread_mutex_unlock(&x->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->x_state == STATE_STARTUP)
|
|
x->x_state = STATE_STREAM;
|
|
else
|
|
pd_error(x, "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->x_mutex);
|
|
x->x_state = STATE_IDLE;
|
|
x->x_requestcode = REQUEST_CLOSE;
|
|
#ifdef DEBUG_SOUNDFILE
|
|
pute("signal 2\n");
|
|
#endif
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
pthread_mutex_unlock(&x->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, bytespersamp, swap, bigendian, normalize;
|
|
long onset, nframes;
|
|
float samplerate;
|
|
if (soundfiler_writeargparse(x, &argc,
|
|
&argv, &filesym, &filetype, &bytespersamp, &swap, &bigendian,
|
|
&normalize, &onset, &nframes, &samplerate))
|
|
{
|
|
pd_error(x,
|
|
"writesf~: usage: open [-bytes [234]] [-wave,-nextstep,-aiff] ...");
|
|
post("... [-big,-little] [-rate ####] filename");
|
|
}
|
|
if (normalize || onset || (nframes != 0x7fffffff))
|
|
pd_error(x, "normalize/onset/nframes argument to writesf~: ignored");
|
|
if (argc)
|
|
pd_error(x, "extra argument(s) to writesf~: ignored");
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_bytespersample = bytespersamp;
|
|
x->x_swap = swap;
|
|
x->x_bigendian = bigendian;
|
|
x->x_filename = filesym->s_name;
|
|
x->x_filetype = filetype;
|
|
x->x_itemswritten = 0;
|
|
x->x_requestcode = REQUEST_OPEN;
|
|
x->x_fifotail = 0;
|
|
x->x_fifohead = 0;
|
|
x->x_eof = 0;
|
|
x->x_fileerror = 0;
|
|
x->x_state = STATE_STARTUP;
|
|
x->x_bytespersample = (bytespersamp > 2 ? bytespersamp : 2);
|
|
if (samplerate > 0)
|
|
x->x_samplerate = samplerate;
|
|
else if (x->x_insamplerate > 0)
|
|
x->x_samplerate = x->x_insamplerate;
|
|
else x->x_samplerate = sys_getsr();
|
|
/* set fifosize from bufsize. fifosize must be a
|
|
multiple of the number of bytes eaten for each DSP
|
|
tick. */
|
|
x->x_fifosize = x->x_bufsize - (x->x_bufsize %
|
|
(x->x_bytespersample * x->x_sfchannels * MAXVECSIZE));
|
|
/* arrange for the "request" condition to be signalled 16
|
|
times per buffer */
|
|
x->x_sigcountdown = x->x_sigperiod =
|
|
(x->x_fifosize /
|
|
(16 * x->x_bytespersample * x->x_sfchannels *
|
|
x->x_vecsize));
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
}
|
|
|
|
static void writesf_dsp(t_writesf *x, t_signal **sp)
|
|
{
|
|
int i, ninlets = x->x_sfchannels;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_vecsize = sp[0]->s_n;
|
|
|
|
x->x_sigperiod = (x->x_fifosize /
|
|
(x->x_bytespersample * ninlets * x->x_vecsize));
|
|
for (i = 0; i < ninlets; i++)
|
|
x->x_outvec[i] = sp[i]->s_vec;
|
|
x->x_insamplerate = sp[0]->s_sr;
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
dsp_add(writesf_perform, 1, x);
|
|
}
|
|
|
|
static void writesf_print(t_writesf *x)
|
|
{
|
|
post("state %d", x->x_state);
|
|
post("fifo head %d", x->x_fifohead);
|
|
post("fifo tail %d", x->x_fifotail);
|
|
post("fifo size %d", x->x_fifosize);
|
|
post("fd %d", x->x_fd);
|
|
post("eof %d", x->x_eof);
|
|
}
|
|
|
|
static void writesf_free(t_writesf *x)
|
|
{
|
|
/* request QUIT and wait for acknowledge */
|
|
void *threadrtn;
|
|
pthread_mutex_lock(&x->x_mutex);
|
|
x->x_requestcode = REQUEST_QUIT;
|
|
/* post("stopping writesf thread..."); */
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
while (x->x_requestcode != REQUEST_NOTHING)
|
|
{
|
|
/* post("signalling..."); */
|
|
sfread_cond_signal(&x->x_requestcondition);
|
|
sfread_cond_wait(&x->x_answercondition, &x->x_mutex);
|
|
}
|
|
pthread_mutex_unlock(&x->x_mutex);
|
|
if (pthread_join(x->x_childthread, &threadrtn))
|
|
error("writesf_free: join failed");
|
|
/* post("... done."); */
|
|
|
|
pthread_cond_destroy(&x->x_requestcondition);
|
|
pthread_cond_destroy(&x->x_answercondition);
|
|
pthread_mutex_destroy(&x->x_mutex);
|
|
freebytes(x->x_buf, x->x_bufsize);
|
|
}
|
|
|
|
static void writesf_setup(void)
|
|
{
|
|
writesf_class = class_new(gensym("writesf~"), (t_newmethod)writesf_new,
|
|
(t_method)writesf_free, sizeof(t_writesf), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
|
|
class_addmethod(writesf_class, (t_method)writesf_start, gensym("start"), 0);
|
|
class_addmethod(writesf_class, (t_method)writesf_stop, gensym("stop"), 0);
|
|
class_addmethod(writesf_class, (t_method)writesf_dsp, gensym("dsp"), 0);
|
|
class_addmethod(writesf_class, (t_method)writesf_open, gensym("open"),
|
|
A_GIMME, 0);
|
|
class_addmethod(writesf_class, (t_method)writesf_print, gensym("print"), 0);
|
|
CLASS_MAINSIGNALIN(writesf_class, t_writesf, x_f);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* ------------------------ global setup routine ------------------------- */
|
|
|
|
void d_soundfile_setup(void)
|
|
{
|
|
soundfiler_setup();
|
|
#ifndef FIXEDPOINT
|
|
readsf_setup();
|
|
writesf_setup();
|
|
#endif
|
|
}
|
|
|